<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jaeyoun Nam</title>
    <description>The latest articles on DEV Community by Jaeyoun Nam (@siisee11).</description>
    <link>https://dev.to/siisee11</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1899729%2F0a13f817-716d-4709-9ef0-fdf79ed9be7f.png</url>
      <title>DEV Community: Jaeyoun Nam</title>
      <link>https://dev.to/siisee11</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siisee11"/>
    <language>en</language>
    <item>
      <title>HAOS 입문기 네트워크 설정</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Wed, 30 Jul 2025 12:41:39 +0000</pubDate>
      <link>https://dev.to/siisee11/haos-ibmungi-neteuweokeu-seoljeong-26oa</link>
      <guid>https://dev.to/siisee11/haos-ibmungi-neteuweokeu-seoljeong-26oa</guid>
      <description>&lt;h3&gt;
  
  
  공유기에서 static 주소 할당
&lt;/h3&gt;

&lt;p&gt;HAOS는 항상 같은 IP로 물려있는게 좋음.&lt;/p&gt;

&lt;p&gt;DHCP 할당 정보 탭으로 가서&lt;br&gt;
현재 homeassistant로 되어 있는 녀석의 Mac 주소를 알아낸 후&lt;br&gt;
DHCP 고정할당으로 가서 '192.168.xxx.100'을 부여한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  외부 접속
&lt;/h3&gt;

&lt;p&gt;Port fowarding으로 뚫는 방법도 있지만, 그보다 설정이 더 쉬운 tunneling을 통해 외부와 연결해줄 것이다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/brenner-tobias/addon-cloudflared/wiki/How-tos" rel="noopener noreferrer"&gt;https://github.com/brenner-tobias/addon-cloudflared/wiki/How-tos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;cloudflare의 tunnel을 사용할껀데, 위의 링크를 따라하면 된다.&lt;/p&gt;

</description>
      <category>network</category>
      <category>smarthomes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AIDER가 코드베이스 인덱싱 하는 법</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Sat, 26 Apr 2025 08:19:34 +0000</pubDate>
      <link>https://dev.to/siisee11/aiderga-kodeubeiseu-indegsing-haneun-beob-f2e</link>
      <guid>https://dev.to/siisee11/aiderga-kodeubeiseu-indegsing-haneun-beob-f2e</guid>
      <description>&lt;p&gt;Cursor같은 Code agent가 어떻게 코드 베이스를 검색하고 관련 코드를 읽어서 동작하는지 알고싶어서, 비슷한데 오픈소스인 aider를 둘러보다가 블로그 글을 찾아서 정리한다.&lt;/p&gt;

&lt;p&gt;aider는 repository map이라는 것을 만들어서 AI Agent가 관련된 코드를 찾아서 활용할 수 있도록 한다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aider.chat/docs/repomap.html" rel="noopener noreferrer"&gt;블로그 링크&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;AI는 코드베이스 전부를 알고 있을 때, 어떤 테스크가 주어졌을 때 해결하는 걸 잘한다.&lt;br&gt;
하지만 코드베이스가 커지면 테스크와 관련된 정보 중 누락하는게 발생하게되고, 그러면 성능이 떨어진다.&lt;br&gt;
따라서 관련 정보를 최대한 잘 줄 수 있도록 AI를 위한 code search 툴을 매우 잘 제공할 필요가 있다. &lt;/p&gt;



&lt;p&gt;Repo Map 이라는 걸 빌드해서 컨텍스트를 제공한다.&lt;/p&gt;

&lt;p&gt;repo map에는 중요한 심볼들이 저장되어 있고, 이를 LLM에게 넘겨주는 방식으로 코드베이스 전반에 대한 지식을 넘겨준다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aider/coders/base_coder.py:
    class Coder:
        abs_fnames = None
    ...
    @classmethod
    def create(self, main_model, edit_format, io, skip_model_availabily_check=False, **kwargs):
    ...
    def abs_root_path(self, path):
    ...
    def run(self, with_message=None):
    ...

aider/commands.py:
    class Commands:
        voice = None
    ...
    def get_commands(self):
    ...
    def get_command_completions(self, cmd_name, partial):
    ...
    def run(self, inp):
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;하지만 레포의 사이즈가 크다면 전부 넘겨주지는 못할 것이다. 그래서 repo map 에서도 관련이 있는 부분을 ranking하는 로직이 있어서, 관련성이 높은 부분만 추려서 보낸다.&lt;/p&gt;

&lt;h3&gt;
  
  
  좋은 Repo map 만들기
&lt;/h3&gt;

&lt;p&gt;Aider는 &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;Tree-sitter&lt;/a&gt;를 이용해서 Repo map을 만든다. &lt;/p&gt;

&lt;p&gt;Tree sitter는 전체 코드베이스를 AST(Abstract Syntax Tree)로 만드는데, AST를 이용하면 우리는 함수, 클래스, 변수, 타입 등이 어디에 쓰이는 지 알 수 있다. &lt;/p&gt;

&lt;h2&gt;
  
  
  AI code search
&lt;/h2&gt;

&lt;p&gt;결국 어떤 정보들을 잘 넣어주느냐가 아직도 중요하다. AI가 기존 코드베이스에서 잘 찾아서 일을 수행할 수 있게 하려면 좋은 AI code search 툴이 필요하다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/buger/probe" rel="noopener noreferrer"&gt;probe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sourcegraph.com/docs/code-search/features" rel="noopener noreferrer"&gt;https://sourcegraph.com/docs/code-search/features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Why MCP</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Wed, 12 Mar 2025 11:43:50 +0000</pubDate>
      <link>https://dev.to/siisee11/why-mcp-2o2i</link>
      <guid>https://dev.to/siisee11/why-mcp-2o2i</guid>
      <description>&lt;p&gt;MCP가 11월 25 (한국시간)에 발표되고 나서 이제 3달정도가 지났다.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP is Protocol
&lt;/h3&gt;

&lt;p&gt;MCP는 Model Context Protocol의 약자로 Protocol이 함유하는 뜻 그대로 통신 규약이다.&lt;/p&gt;

&lt;p&gt;규약이기 때문에 사실 그 자체로 대단한 기술은 아니다. 슈퍼파워네 뭐네 하는 소리가 있어도 그건 조금 과장된 것이다. 하지만 (슈퍼)파워를 AI Agent한테 부여하기 쉬워진것은 맞다.&lt;/p&gt;

&lt;p&gt;규약이 유용하려면 프로토콜이 잘 설계 되야하는 것도 있지만 한가지 조건이 더 있다. 바로 유명해야 한다는 것이다.&lt;/p&gt;

&lt;p&gt;우리가 인터넷 통신할때 쓰는 TCP/IP, RestAPI, 코딩할 때 쓰는 다양한 Interface, 조금 만 더 나가면 우리가 대화에 쓰는 언어까지 서로 내용을 공유하는 사람이 많으면 그 가치가 올라가는 것들이다.&lt;/p&gt;

&lt;p&gt;거기서 오는 특성이 하나 더 있다. 점점 대체 불가능해진다는 것이다. &lt;/p&gt;

&lt;p&gt;TCP/IP는 말해 뭐하고, OpenAI가 처음 GPT를 발표하면서 내놓은 API는 그 형태 그대로 다른 모델들에서도 사용해서 이제 OpenAI의 API 형식을 따르지 않으면 안된다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why MCP?
&lt;/h3&gt;

&lt;p&gt;MCP는 LLM이 활용할 수 있는 Tool, Prompt, Resource(Data) 관리자와 소통할 수 있게 해주는 프로토콜이다. Tool, Prompt, Resource를 쉽게 AI가 가져다 쓸 수 있도록 돕는다고 생각하면 된다.&lt;/p&gt;

&lt;p&gt;Tool의 개념은 GPT 서기 1년부터 존재해오던 개념이다. 사실 Agent의 개념과 거의 동시에 나왔다. Agent로써의 LLM은 두뇌의 역할이고, 인간이 도구를 사용해서 문제를 해결하듯 Agent가 Tool을 사용한다는 개념은 아주 직관적인 개념이다.&lt;/p&gt;

&lt;p&gt;그런데 문제는 이 Tool을 모두가 다른 방식으로 비슷한 것을 만들고 있었다는 것이다. 아마 슬랙 연동하는 툴을 몇만명이 만들지 않았을까... &lt;/p&gt;

&lt;p&gt;이런 문제의식을 가지고 이런 툴들을 묶어서 제공하려는 곳들도 있었고 &lt;a href="https://composio.dev/" rel="noopener noreferrer"&gt;Composio&lt;/a&gt;, &lt;a href="https://platform.openai.com/docs/actions/introduction" rel="noopener noreferrer"&gt;GPTs action&lt;/a&gt;(못들어 봤을 수 있는데 OpenAI꺼다)은 Interface를 제공하고 이를 구현하면 GPT가 사용할 수 있도록 만들어주기도 했다. MCP 비슷하게 표준을 제안한 곳도 있다.&lt;a href="https://docs.wild-card.ai/agentsjson/introduction" rel="noopener noreferrer"&gt;AgentsJson&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;하지만 너무 닫혀있는 시스템이다거나 안유명하거나의 이유로 이들은 표준이 되지 못했다. MCP도 발표할때는 표준은 아니었지만 이제 표준에 가까워졌다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challange of MCP
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;지금 MCP의 상태는 초기 생태계 상태이다. MCP Server를 아무나 만들어서 공유할 수 있는데, 이 공유하는 생태계가 아직 부족하다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MCP server는 기본적으로 남의 코드이다. 그래서 아무 MCP Server나 좋아보여서 돌렸다가는 거덜날 가능성이 있다. npm 같은 marketplace가 있고 보안 검증들도 철저하게 이루어져야한다.&lt;/p&gt;

&lt;p&gt;대부분의 MCP Server는 퀄리티가 안좋다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;핫해지고 좀 알려지긴했지만 표준은 아니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MCP 등록을 해야하고 등록시 인증 과정이 불편하다. 내 Agent가 어떤 MCP Server의 툴을 쓰게 하고 싶으면 그 서버를 이용하기 위한 토큰을 포함하여 MCP서버를 등록해야한다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이건 하면 되지 싶지만, 사실 완전한 Agent라면 필요한 툴을 찾아서 알아서 척척 내가 주문한 내용을 완수해야한다.&lt;/p&gt;

&lt;p&gt;"깃헙의 내 레포의 가장 최신 이슈를 해결해서 PR을 올리고 슬랙으로 알림을 보내줘"라는 테스크를 주는 상황이다. &lt;/p&gt;

&lt;p&gt;완전한 Agent: "깃헙을 써야겠네, 깃헙 MCP Server에서 정보를 가져와 볼까, 토큰이 필요하네, (뭐 어케해서 PAT얻어옴), 깃헙 MCP로 최신이슈 조회, ...., 어 슬랙도 필요하네, 슬랙 MCP Server를 찾아봐야지, ...., 전송 완료" &lt;/p&gt;

&lt;p&gt;지금: 깃허브도 필요하고 슬렉도 필요하니까 MCP서버 설치링크를 찾아서 설치하고 인증 등록하고 "해줘"&lt;/p&gt;

&lt;h3&gt;
  
  
  Future of MCP
&lt;/h3&gt;

&lt;p&gt;위의 문제들은 centralized registry 와 well-known 파일들로 해결을 하려고 하는 것으로 보인다. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why Now?
&lt;/h3&gt;

&lt;p&gt;지금 MCP를 도입해봐야하는 이유는 지금 꽤 괜찮은 걸 올리면 쉽게 유명해질 수 있다는 것이다.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not API?
&lt;/h3&gt;

&lt;p&gt;API도 사실 OpenAPI 같은 스펙이 있고, 사람도 충분히 이해할 수 있기에 당연히 LLM도 이해하고 사용할 수 있다. &lt;/p&gt;

&lt;p&gt;1년전에는 AI가 웹을 돌아다닐 수 있기 때문에, 그냥 쓰고 싶은 API찾아서 쓰면 된다고 생각했다. 아니면 &lt;a href="https://github.com/public-apis/public-apis?tab=readme-ov-file" rel="noopener noreferrer"&gt;이런 퍼블릭 API 리스트&lt;/a&gt;도 학습해서 그냥 알고 있어서 쓰거나 하게 될 줄 알았다.&lt;/p&gt;

&lt;p&gt;근데 지금와서 API를 툴로 활용하도록 발전한게 아니라 MCP라는 새로운 규약이 등장했다. 어떤 의미에서 MCP가 API보다 더 가치 있을까 생각해보았다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API가 Agent의 비해 상대적으로 Low level이다. 우리가 Agent한테 명령하는 일들은 우리가 수 분을 소비해야하는 일들이다. 이 고수준의 일을 상대적으로 저수준인 API의 집합으로 나누려면 수십개의 API 호출을 해야할지도 모른다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;사람은 일을 할때 UI를 통해서 작업을 한다. UI에 있는 버튼 하나에 사실 많은 API들이 숨겨져 있는 거라고 볼 수 있다. Agent도 그런 버튼 같은게 필요하다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API는 바뀔 확률이 높다. API가 바뀔때 마다 모든 Agent는 그 정보를 업데이트 해야한다. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;반면 MCP는 추상화 레벨이 높아서 잘 바뀌지 않는다. 예를 들어서, "컴퓨터를 켠다" 라는 액션이 있다. 그 컴퓨터의 부품들의 키는 방식이 어떻게 바뀌든 유저는 알필요 없다. "컴퓨터를 키는 버튼"은 항상 그대로다. 컴퓨터를 조립하는 곳 한군데서만 신경 쓰면 된다. &lt;/p&gt;

&lt;p&gt;디자인 패턴의 Facade패턴과 비슷하다.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>korean</category>
    </item>
    <item>
      <title>Consistency Models and Replicache</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Wed, 01 Jan 2025 12:12:24 +0000</pubDate>
      <link>https://dev.to/siisee11/consistency-models-and-replicache-1l42</link>
      <guid>https://dev.to/siisee11/consistency-models-and-replicache-1l42</guid>
      <description>&lt;h1&gt;
  
  
  Consistency Model
&lt;/h1&gt;

&lt;p&gt;Consistency Model은 분산 시스템에서 어느정도의 Consistency를 제공할 것이냐에 대한 여러가지 규약이다. 이전 포스트 중 PACELC 이론에서 Latency와 Consistency를 Trade off 하는 내용이 있는데, 이 Trade off 에 따라서 어느정도의 Consistency 를 제공할 것인가에 대한 내용이다.&lt;/p&gt;

&lt;p&gt;이전 포스트에서 다룬 Replicache도 분산 시스템의 일종이다. Replicache는 Causal+ Consistency를 따른다고 한다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse1n7whzwq1r0wvp2soi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse1n7whzwq1r0wvp2soi.png" alt="Consistency Models" width="800" height="187"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.cs.columbia.edu/~du/ds/assets/lectures/lecture12.pdf" rel="noopener noreferrer"&gt;출처&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Strict Consistency
&lt;/h2&gt;

&lt;p&gt;항상 시간 순서대로 값을 읽을 수 있는 Consistency이다. 값이 쓰여진 시점 이후에 발생하는 모든 읽기는 항상 그 값을 읽는다. 이를 만족 하려면 두가지의 조건이 필요하다. 모든 노드가 공유하는 global clock이 존재하고 이에 동기화 해야하며, Write가 즉시 수행되어야한다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;w(x)a: x에 a라는 값을 write, r(x)a: x에서 a란 값을 read&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;t시간에 w(x)a 이 1번 노드에서 발생하고, t+delta(0.0000000000000001s)후에 2번 노드에서 r(x)가 발생했을때, 2번노드는 값 a를 읽어야한다.&lt;/p&gt;

&lt;p&gt;같은 t시간에 operation이 수행되지 않도록 global clock이 존재해서 operation을 직렬화해야하고, write가 즉시 수행되어서 t+delta 시간에 write한 값을 읽을 수 있어야한다.&lt;/p&gt;

&lt;p&gt;하지만 이는 실제로 불가능한 일이다. 따라서 Strict Consistency는 이론으로 존재한다.&lt;/p&gt;

&lt;p&gt;아래 상황에서 Strict Consistency를 준수한다면&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6t4d78frh1b3jxj28rfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6t4d78frh1b3jxj28rfa.png" alt="Image description" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;정답은 아래와 같다.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrxsvmzywsac6xiuxxw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrxsvmzywsac6xiuxxw5.png" alt="Image description" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sequential Consistency
&lt;/h2&gt;

&lt;p&gt;Sequential Consistency의 핵심 키워드는 'Global Ordering'이다. 모든 쓰기/읽기 연산은 하나의 통합된 순서가 있는 것 처럼 동작해야한다. 또 각각의 프로세스(클라이언트)가 수행하는 연산은 그 순서대로 실행되어야 한다.&lt;/p&gt;

&lt;p&gt;아래의 경우 p3와 p4는 어떤 값을 읽을 수 있을까?&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblr22tbdt3we21601wtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblr22tbdt3we21601wtv.png" alt="Image description" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;아래는 Strict Consistency와 같으므로 Sequential Consistency도 만족한다.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F173ommwfuoyfyur3jn03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F173ommwfuoyfyur3jn03.png" alt="Image description" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;아래는 Global Ordering을 찾을 수 있으므로 Sequential Consistency가 맞다.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakm0omdquw13slgbooda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakm0omdquw13slgbooda.png" alt="Image description" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Global Ordering: w(x)a, r(x)a, w(x)b, r(x)b&lt;/p&gt;

&lt;p&gt;하지만 아래의 경우에는 a가 b보다 먼저 쓰여졌는데 나중에 읽힌다. 이런 경우는 Sequential Consistency를 만족하지 않는다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62i8fyza9oa16hy1b9l5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62i8fyza9oa16hy1b9l5.png" alt="Image description" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Causal Consistency
&lt;/h2&gt;

&lt;p&gt;Causal 이란 원인-결과 관계를 의미한다. &lt;em&gt;"Happens-before (→)"&lt;/em&gt; 관계라는 것을 이해하는 것이 중요하다. &lt;/p&gt;

&lt;p&gt;A → B 는 다음과 같은 경우에 성립한다. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;같은 프로세스(클라이언트)에서 A가 B보다 먼저 발생됨&lt;/li&gt;
&lt;li&gt;A 후에 메세지를 보내고 메세지를 받은 쪽이 B를 수행함.&lt;/li&gt;
&lt;li&gt;A → B 이고 B → C 이면 A → C 임.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Causal Consistency란 이런 원인-결과 관계가 모든 노드에서 같이 보이는 규약이다.&lt;/p&gt;

&lt;p&gt;아래 경우는 Causal Consistency이다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sl4xvkfi1t41ecsko6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sl4xvkfi1t41ecsko6e.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;여기에서 찾을 수 있는 "Happens-before" 관계는 w(x)a → r(x)a, w(x)b → r(x)b 뿐이고 이는 모든 프로세스가 만족 한다. P3 가 b를 먼저 읽는 것은 괜찮다. 두 쓰기 연산은 독립적이므로 w(x)a → w(x)b의 원인-결과 관계는 없기 때문이다.&lt;/p&gt;

&lt;p&gt;아래는 Causal Consistency를 성립하지 않는다. 이유는 w(x)a → w(x)c의 원인-결과 관계가 존재하는데, P3에서 c가 a보다 먼저 읽히기 때문이다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2xg3os4eaabq5smvre4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2xg3os4eaabq5smvre4.png" alt="Image description" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Eventual Consistency
&lt;/h2&gt;

&lt;p&gt;Eventual (결국) 일관성을 가지게 되는 규약이다. 언젠가 쓰기 연산이 없고 네트워크가 안정화 되면 결국 모든 노드는 같은 값을 보게된다. 매우 유한 규약이다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Consistency in Replicache
&lt;/h1&gt;

&lt;p&gt;Replicache는 Causal+ Consistency를 따른다고 한다. 이건 조금더 강한 Causal Consistency라는데 나중에 알아보도록하자.&lt;/p&gt;

</description>
      <category>korean</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Replicache</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Tue, 31 Dec 2024 13:25:12 +0000</pubDate>
      <link>https://dev.to/siisee11/replicache-big-picture-1nka</link>
      <guid>https://dev.to/siisee11/replicache-big-picture-1nka</guid>
      <description>&lt;h1&gt;
  
  
  Replicache
&lt;/h1&gt;

&lt;p&gt;Local First Software 를 구현하는 데 도움을 주는 프레임워크이다. Git가 유사하게 push, pull 을 통해서 싱크를 맞추는 작업을 구성하는데 도움을 준다.&lt;/p&gt;

&lt;p&gt;Replicache 는 서버 데이터의 동기화를 뒤에서 비동기적으로 수행하여 Server의 Round trip을 없애고 즉각적인 UI변경을 가능하게 한다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Replicache Parts
&lt;/h1&gt;

&lt;p&gt;Replicache는 여러개의 요소로 구성된다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replicache
&lt;/h2&gt;

&lt;p&gt;Replicache는 내부에 git 같은 동작을 포함하는 In browser Key-Value 스토어라고 볼 수 있다. 메모리에 먼저쓰고 나중에 동기화한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your application
&lt;/h2&gt;

&lt;p&gt;웹 어플리케이션 같은 우리가 만든 응용프로그램이다. Replicache에 상태를 저장하는 주체이다. Mutator 와 Subscription를 구현해서 상태의 변경과 대응을 한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your server
&lt;/h2&gt;

&lt;p&gt;가장 신뢰할 수 있는 데이터를 저장하기 위해 존재한다. 서버에 연결된 데이터베이스에 저장된 상태는 어플리케이션에 있는 상태보다 우선시 된다.&lt;/p&gt;

&lt;p&gt;서버는 push(upstream)와 pull(downstream)을 구현해서 클라이언트의 Replicache와 소통해야한다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;push(upstream): Replicache 는 변경 사항을 push endpoint로 보낸다. 서버에도 어플리케이션과 같이 Mutator를 구현하는데 이 push endpoint는 이 mutator를 실행시켜서 database의 상태를 변경한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pull(downstream): 주기적으로 혹은 명시적으로 요청할 때, Replicache는 서버에 Pull 요청을 날린다. 서버는 클라이언트가 서버의 상태와 동일해지기 위해 필요한 변경사항을 반환한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;poke: 주기적으로 클라이언트가 pull요청을 보내긴 하지만, 좀 더 realtime으로 보여주기 위해서 서버에 변경이 있을때, 서버가 클라이언트에게 pull 요청을 하라고 힌트를 주는 신호이다. 아무런 데이터를 수반하지 않는다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sync
&lt;/h2&gt;

&lt;p&gt;어플리케이션과 서버가 최신의 상태로 동기화 한다. 아래 그림이 이 과정을 잘 보여준다. 서버에서 주기적으로 상태변화를 pull 받아서 UI를 업데이트하는 과정, 클라이언트에서의 상태변경이 먼저 UI를 업데이트하고 서버에 push 되는 과정을 보여준다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6ksmxdv6367fzaurh7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6ksmxdv6367fzaurh7r.png" alt="Image description" width="800" height="286"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://doc.replicache.dev/concepts/how-it-works" rel="noopener noreferrer"&gt;출처&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Clients, ClientGroups, Caches
&lt;/h1&gt;

&lt;p&gt;메모리에 있는 Replicache를 Client 라고 한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Replicache&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replicache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Replicache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;클라이언트는 보통 한탭 당 하나 존재한다. 클라이언트는 휘발성이고 탭의 생명주기와 함께한다. 유일한 clientID를 가진다.&lt;/p&gt;

&lt;p&gt;Client Group은 Local data를 공유하는 클라이언트의 집합이다. 이 클라이언트 그룹 안에 있는 클라이언트들은 오프라인 상태라도 상태를 공유한다. &lt;/p&gt;

&lt;p&gt;Client Group은 Replicache의 생성자의 name parameter에 의해 구별되는 on-disk persistent cache를 사용한다. 같은 name을 가지는 Client Group에 속하는 모든 클라이언트는 같은 캐시를 공유한다.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Client View
&lt;/h1&gt;

&lt;p&gt;Client는 &lt;code&gt;ordered map of key value pair&lt;/code&gt; 를 persistent cache에 가지고 있는데 이를 Client View 라고 부른다. Client View는 어플리케이션의 데이터이고 서버의 데이터와 싱크된다. Client View라고 부르는 이유는 서로 다른 클라이언트 마다 서버의 데이터를 다른 Client View로 가지고 있을 수 있기 때문이다. 각 클라이언트가 서버의 상태를 보는 게 다르다는 의미다.&lt;/p&gt;

&lt;p&gt;Client View에 접근하는 것은 매우 빠르다. Read latency 는 1ms 미만이고 대부분의 장치에서 500MB/s 의 Throughput을 가진다.&lt;/p&gt;

&lt;p&gt;React 같은 곳에서 useState로 따로 Client View를 카피해서 메모리에 올려서 사용하지말고, Client View에서 바로 읽어서 사용하길 권장한다. Client View에 mutator가 변경을 가하면 subscription이 발동되어 UI가 업데이트 되도록 하면 된다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Subscriptions
&lt;/h1&gt;

&lt;p&gt;Subscribe 함수는 ReadTransaction 인자를 받아서 Replicache에서 읽는 것을 구현한다. Replicache의 데이터가 변해서 이 subscription이 out of date 될때마다 subscribe 함수가 다시 수행된다. 만약 이 결과가 바뀐다면, 값이 업데이트되고 UI도 업데이트 된다.&lt;/p&gt;

&lt;p&gt;Subscription을 통해 UI를 구성하면, 항상 최신의 상태로 유지할 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todo/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;))}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Mutations
&lt;/h1&gt;

&lt;p&gt;Mutation은 Replicache의 데이터를 변화시키는 작업을 뜻한다. Mutation을 받아서 실제로 데이터를 변화시키는 주체를 Mutator 라고 한다.&lt;/p&gt;

&lt;p&gt;시작할 때 Replicache에 여러 Mutator를 등록하는데, 사실 그냥 named function이다. 아래 &lt;code&gt;createTodo&lt;/code&gt;와 &lt;code&gt;markTodoComplete&lt;/code&gt;는 모두 mutator로 WriteTransaction을 통해서 Replicache의 데이터를 변화시킨다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Replicache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;mutators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;markTodoComplete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WriteTransaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/todo/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;markTodoComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WriteTransaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/todo/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mutator는 아래와 같이 작동 시킨다. Mutator가 작동하면 데이터가 변경되고, 그와 연관된 subscription들이 트리거 되면서 UI 또한 변경된다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nanoid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;take out the trash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;내부적으로 Mutator는 &lt;code&gt;mutation&lt;/code&gt;이라는 것을 만든다. 수행 기록 같은건데, Replicache는 아래와 같은 mutation을 생성한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createTodo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;t1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;take out the trash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;markTodoComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;t1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 mutation들이 서버에 push 되어 완전히 sync되기 전까지 이 mutation들은 pending 상태로 표기된다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sync Details
&lt;/h1&gt;

&lt;p&gt;이제 Replicache의 핵심이라고 할 수 있는 Sync의 자세한 내용이다. Sync는 서버에서 이루어진다.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Replicache Sync Model
&lt;/h2&gt;

&lt;p&gt;(이제부터 '상태'라는 표현은 여러 key과 value 쌍으로 된 데이터(key value space)의 상태를 뜻 한다.)  &lt;/p&gt;

&lt;p&gt;Replicache가 풀려고 하는 Sync 문제는 여러 클라이언트가 동시에 같은 상태를 변화시키는 상황이고 아래와 같은 조건을 가질 때 발생한다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;서버가 가지고 있는 상태가 source of truth이다. canonical(표준)이라고 표현한다.&lt;/li&gt;
&lt;li&gt;클라이언트의 로컬 상태의 변화는 즉각적으로 반영된다. 이를 speculative(추측)라고 부른다.&lt;/li&gt;
&lt;li&gt;서버는 변경사항을 정확히 한번만 적용해야하고 그 결과가 예측 가능해야한다. 서버에 적용된 변경사항이 클라이언트의 로컬 change와 합리적으로 병합될 수 있어야한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 중에 마지막 항목에서 서버의 변화를 로컬 상태와 '합리적으로 병합'이라는 것은 흥미로운 주제이다. '합리적 병합'을 위해서 다음과 같은 상황들을 고려해야한다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;로컬의 변화가 아직 서버에 적용되지 않은 경우. 이 경우는 서버로부터 새로운 상태를 가져오더라도 로컬에서의 변경 사항이 앱의 UI에서 사라지지 않도록 해야 한다. 서버로부터 새로운 상태를 수신한 후, 기존의 로컬 변경 사항을 서버 상태 위에 다시 실행해야 한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;클라이언트에서 발생한 로컬 변경 사항이 이미 서버에 전송되고, 서버 상태에 반영된 경우. 이 경우는 로컬 변경 사항을 중복 적용하는 것을 주의 해야한다. 로컬 변경 사항을 다시 적용하지 않아야한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;동일한 상태에 대해 서버 상태를 변경한 다른 클라이언트가 존재하는 경우. 이 경우에도 첫번째 경우 처럼 서버에서 수신한 상태를 기준으로 로컬 변경 사항을 재 실행해야 한다. 하지만 같은 리소스에 대해서 충돌이 발생할 수 있기 때문에 병합 논리를 잘 짜야한다. Mutator 내부에 이 로직을 작성한다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mutator의 동작 과정을 따라가보자.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local execution
&lt;/h2&gt;

&lt;p&gt;로컬에서 Mutator 가 동작하고 mutator로직에 따라서 replicache의 값이 변경된다. 동시에 이 클라이언트에서 sequential하게 증가하는 mutationId를 가진 mutation을 생성한다. mutation은 pending mutation으로 queuing된다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push
&lt;/h2&gt;

&lt;p&gt;Pending mutations은 server에 구현된 &lt;code&gt;push&lt;/code&gt; endpoint(replicache-push)로 보내진다. &lt;/p&gt;

&lt;p&gt;mutation은 서버에 구현된 mutator를 실행시켜서 canonical 상태를 변경한다. Mutation을 적용하면서 이 클라이언트의 last mutation id를 업데이트하고, 이 클라언트가 다음 pull을 할 때 어느 mutation부터 재 적용할지 알 수 있는 값이 된다.&lt;/p&gt;

&lt;p&gt;로컬에 적용된 pending mutation은 speculative result를 생성하고, 서버에 적용된 mutation은 canonical result를 생성한다. 서버에 적용된 mutation은 confirmed 되어 다시 로컬에서 실행되지 않는다. 만약 같은 mutation이 다른 결과를 반환하더라도 server의 canonical result가 우선되므로 클라이언트의 결과는 바뀌게 된다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull
&lt;/h2&gt;

&lt;p&gt;Replicache는 최신 상태를 가져와 UI를 업데이트하기 위해서 주기적으로 &lt;code&gt;pull&lt;/code&gt; endpoint(replicache-pull)로 요청을 보낸다.&lt;/p&gt;

&lt;p&gt;Pull request는 cookie, clientGroupId를 담아서 요청하고, new cookie, patch, lastMutationIDChanges를 반환받는다.&lt;/p&gt;

&lt;p&gt;cookie는 클라이언트가 가지고 있는 서버 상태를 구별하는데 사용한다. 서버와 클라이언트 상태가 얼마나 차이나는 지 추적할 수 있는 값이면 된다. 데이터베이스의 상태가 바뀔때마다 변경되는 global 'version'이라고 생각해도 된다. 아니면 좀 더 특정 범위의 데이터를 추적하는 쿠키 전략을 사용해도 된다.&lt;/p&gt;

&lt;p&gt;lastMutationIdChanges는 각 클라이언트의 마지막으로 서버에서 적용된 mutation ID를 나타내는 값이다. 이 값보다 작은 mutationID를 가진 mutation은 모두 더 이상 pending이 아닌 confirmed로 간주해야한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebase
&lt;/h2&gt;

&lt;p&gt;클라이언트가 pull을 받으면 로컬의 상태에 patch를 적용해야한다. 하지만 pending mutation이 현재 로컬 상태 영향을 줬을 것이기에 로컬 상태에 바로 patch를 적용할 수는 없다. 대신에 로컬의 pending mutation을 되돌리고 pull로 받은 patch를 먼저 적용한 후에 다시 로컬 pending mutation을 적용한다. &lt;/p&gt;

&lt;p&gt;이런 되돌리기와 재적용을 가능하게 하기 위해서 Replicache는 깃과 유사하게 설계되었다. 서버의 상태가 main branch, 로컬에 팬딩된 mutation으로 변경된 상태를 develop 브랜치라고 생각하고 서버로 부터 main에 pull을 받고 develop을 main으로 rebase한다고 생각하면 된다.&lt;/p&gt;

&lt;p&gt;리베이스하면서 발생할 수 있는 컨플릭트는 아래에서 따로 알아본다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Poke
&lt;/h2&gt;

&lt;p&gt;Poke는 위에서 설명했듯이 서버가 클라이언트에게 pull을 하라고 알려주는 힌트 메세지이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflict Resolution
&lt;/h2&gt;

&lt;p&gt;Replicache과 같은 분산시스템에서 Merge conflict는 피할 수 없다. &lt;code&gt;pull&lt;/code&gt;과 &lt;code&gt;push&lt;/code&gt;과정에서 병합이 필요하다. 병합은 병합 결과가 예측 가능해야하고 앱의 목적에 맞는 방식으로 진행되어야한다.&lt;/p&gt;

&lt;p&gt;만약 회의실 예약 앱 이라면, 컨플릭이 발생했을때 하나의 요청만 승인되어야 한다. 그렇기 때문에 먼저 예약한 클라이언트만 승인하는 병합 방법을 채택해야한다.&lt;/p&gt;

&lt;p&gt;반대로 Todo 앱이라면, 투두리스트는 동시에 추가가 일어나더라도, 두 변경 모두 승인되는 것이 목적에 맞다.&lt;/p&gt;

&lt;p&gt;Merge Conflict는 다음 두 상황에서 발생한다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;로컬 변경 사항이 서버에 적용될 시점. 로컬에서 적용할 때의 상태와 서버에서 적용할 때의 상태가 다를 수 있기 때문이다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rebase할 때. 역시 적용할때 상태가 다를 수 있기 때문이다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Replicache는 앱의 목적에 따라 병합 방법을 다르게 구현해야 함을 인지하기 때문에, 개발자가 이를 구현할 수 있도록 한다. 개발자는 Mutator를 통해 이 로직을 구현하면 된다.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>korean</category>
    </item>
    <item>
      <title>Local First Software</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Sat, 21 Dec 2024 07:15:07 +0000</pubDate>
      <link>https://dev.to/siisee11/local-first-software-497a</link>
      <guid>https://dev.to/siisee11/local-first-software-497a</guid>
      <description>&lt;h1&gt;
  
  
  State (상태)
&lt;/h1&gt;

&lt;p&gt;웹이 점점 발전하면서 유저와 상호작용하는 요소, 보여지는 요소가 많아지게 되었다. 이런 요소 들은 유저가 보는 화면을 변화시킨다. 화면을 변화시키는 것들을 '상태'라고 정의 할 수 있다. &lt;/p&gt;

&lt;p&gt;예를 들어서 랜딩 페이지 같은 정보성 웹페이지 인 경우에 '상태'라고 할 수 있는건 보여줄 정보 하나이다. &lt;/p&gt;

&lt;p&gt;다음으로 깃헙 같은 경우에는 내 정보, 내 레포지토리 정보, star 수 등 다양한 정보들이 있는데 이들에 따라 유저에게 보여질 화면이 달라지기 때문에 이 들을 모두 '상태'라고 볼 수 있다. &lt;/p&gt;

&lt;p&gt;더 복잡한 예시로, 피그마 같은 예시를 들 수 있다. 화면에 모든 점, 선, 면등 그래픽들은 모두 '상태'이다. 게다가 협업 기능은 나 이외의 다른 사람의 상태까지도 공유해야한다.&lt;/p&gt;

&lt;h1&gt;
  
  
  State &amp;amp; Data
&lt;/h1&gt;

&lt;p&gt;상태는 모두 데이터다. 유저에 대한 정보, 유저 맞춤 정보 등 모두 어딘가 저장되어 있는 데이터이고 이 데이터는 곧 유저가 보는 화면의 상태가 된다. 보통 이 데이터라는 건 서버에 Single Source of Truth 로 저장된다. 어떤 웹사이트에 로그인을 했다면 그 사이트의 서버의 users 테이블에 하나의 행으로 저장될 것이다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Data is too far
&lt;/h1&gt;

&lt;p&gt;최근 웹은 복잡하다. 버튼은 셀 수 없을 만큼 많고, 한 화면에 보여주는 데이터도 많다. 시의성이 중요한 정보들도 많다. 이 상태들이 변경될 때 마다 데이터의 정합성을 위해 서버에 왔다 갔다 해야한다. 도큐먼트처럼 1분에 '다음페이지'만 받아오면 되는 경우에는 큰 문제가 되지 않는다. 하지만 노션같이 유저가 계속 데이터를 수정하는 경우라면 큰 문제가 된다. 페이지에서 특성같은 걸 설정할 때마다 로딩 해야 한다면 속이 뒤집어 질 것이다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Optimistic Update
&lt;/h1&gt;

&lt;p&gt;인스타그램 같은 소셜 미디어 사이트에서 좋아요를 누르는 것을 생각해보자. 좋아요를 누르면 서버에 가서 내가 그 포스트를 좋아요 했다는 정보를 저장하고, 그 포스트의 좋아요 숫자를 하나 올려준 다음, 현재 포스트의 좋아요를 가져와서 나한테 보여줘야 한다.&lt;/p&gt;

&lt;p&gt;하지만 인스타그램은 0.001초 만에 애니메이션과 함께 좋아요가 눌리고 카운트가 올라간다.&lt;/p&gt;

&lt;p&gt;이는 서버에 정보가 도달하기도 전에 클라이언트의 상태를 업데이트 하는 것으로 가능하다. 좋아요를 누른 데이터가 서버에 잘 기록되겠지 하고 클라이언트의 상태를 업데이트하는 것이다. 대부분의 경우에는 서버와의 통신이 성공할 것이기 때문에 이를 낙관적으로 성공이라고 판단하는 것이다.&lt;/p&gt;

&lt;p&gt;물론 서버에 보낸 요청이 실패할 경우도 있기 때문에, 실패할 시 클라이언트의 상태를 롤백하는 것은 신경 써주어야한다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Responsiveness Over Consistency
&lt;/h1&gt;

&lt;p&gt;내가 좋아요를 눌렀는지 아닌지를 Optimistic 하게 보여주는 건 매우 합리적이다. 근데 내가 누를 때 다른 사람도 눌러서 좋아요가 1개 이상 늘었을 수도 있는데 이건 어떻게 처리하는 걸까.&lt;/p&gt;

&lt;p&gt;이건 그냥 데이터 정합성을 약간 무시하는 것으로 간단하게 해결이 된다. 그 게시글이 인기 게시글이라면 내가 포스트를 보는 그 시간 동안 좋아요 개수가 안늘었을 리가 없다. 이건 그냥 그 소프트웨어의 정책이다. 빠른 응답을 위해서 약간의 데이터 정합성은 희생하는 것이다.&lt;/p&gt;

&lt;h1&gt;
  
  
  CAP theorem
&lt;/h1&gt;

&lt;p&gt;분산 시스템 학문에는 CAP 이론이 있다. 이 이론은 분산 시스템을 구성할 때 C, A, P 중 최대 두개만 취할 수 있다는 이론이다.&lt;/p&gt;

&lt;p&gt;C는 Consistency로 정합성이다. 어떤 노드에서 데이터를 읽던지 간에 같은 데이터를 읽어야한다.&lt;/p&gt;

&lt;p&gt;A는 Availability로 어떤 노드가 죽더라도 모든 요청에 응답할 수 있는 가 이다.&lt;/p&gt;

&lt;p&gt;P는 Partition-tolerance로 몇 노드의 네트워크 연결이 끊기는 경우에 동작할 수 있는가, 네트워크 연결 후에 다시 복구할 수 있는가 이다.&lt;/p&gt;

&lt;p&gt;이 이론에 따르면 결국 CA, AP, CP 이렇게 세가지의 시스템이 가능하다.&lt;/p&gt;

&lt;h2&gt;
  
  
  CA
&lt;/h2&gt;

&lt;p&gt;이론상은 분산 시스템이 CA를 택할 수 있지만, 네트워크 연결이 끊기면 동작하지 않는 시스템은 우리는 분산 시스템이라고 부르지 않기로 했다.&lt;/p&gt;

&lt;p&gt;결국 분산 시스템이라면 P는 보장되어야한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  AP
&lt;/h2&gt;

&lt;p&gt;Availability Over Consistency&lt;/p&gt;

&lt;p&gt;몇 노드의 네트워크 연결이 끊어졌을 때, 모든 노드가 그 값의 최신 상태에 대해서 동의하지 않았더라도 일단 연결되어 있는 노드의 값을 내려주는 것이다. 그렇기 때문에 끊어져있는 노드들 간에 최신 데이터는 일치하지 않을 수 있다. 하지만 사용자는 마치 최신의 데이터를 받는 것 처럼 서비스를 계속 이용할 수 있다.&lt;/p&gt;

&lt;p&gt;대표적인 예시는 소셜미디어이다. 현실에서는 일어나지 않을 법한 일이지만, 유럽에 있는 인스타그램의 노드들과 아시아의 노들들의 네트워크 연결이 끊어져버렸다고 가정하자. 아시아에서 접근하는 유저들과 유럽에서 접근하는 유저가 보는 팔로우수, 좋아요 수 등은 이 장애기간동안 조금 달라도 괜찮다. 하지만 기능은 여전히 동작해야할 것 이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  CP
&lt;/h2&gt;

&lt;p&gt;Consistency Over Availability&lt;/p&gt;

&lt;p&gt;네트워크 장애 상황에 최신 데이터에 대해서 확신을 할 수 없는 상황에서, 유저의 요청에 응답하지 않는 시스템이다. &lt;/p&gt;

&lt;p&gt;예시는 보통 돈(거래)과 관련된 것들이다. 50%할인 하는 호텔 방이 하나 남은 상황에서 네트워크 단절이 일어났다고 하자. AP시스템에서는 둘 다 방이 있겠거니 하고 예약을 받아서 오버부킹을 받아버릴 가능성이 있다. CP 시스템은 이 데이터의 최신 상태에 대해서 확신할 수 없으니, 이에 대한 요청을 연기 시키거나 거부한다.&lt;/p&gt;

&lt;h1&gt;
  
  
  PACELC Theorem
&lt;/h1&gt;

&lt;p&gt;CAP 이론은 사실 Partition에 대한 이론이다. 만약 Partition이 일어났다면 A나 C를 택해야한다는 것이다.&lt;/p&gt;

&lt;p&gt;근데 사실 보통의 상황의 경우 Partition은 일어나지 않는다. 그런 상황에서 적용할 수 있는 이론이 PACELC 이론이다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if (P) then (AC) else (LC)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;즉, Partition인 경우에는 AC를 고려하고 아니면 LC를 고려하라는 뜻이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  LC
&lt;/h2&gt;

&lt;p&gt;Latency &amp;amp; Consistency&lt;/p&gt;

&lt;p&gt;보통의 상황에서 시스템은 Latency와 Consistency를 trade off 한다. 거창하게 이론이지만 사실 이건 컴퓨터 공학 전반에 걸친 진리 같은 것이다.&lt;/p&gt;

&lt;p&gt;Trade off를 생각한다는 것은 이 두가지 기준의 어느 정도로 타협을 본다는 것을 의미한다.&lt;/p&gt;

&lt;p&gt;Latency는 느림에서 빠름의 정도를 직관적으로 알 수 있는데 Consistency는 어떤 정도를 가지는 지 직관적으로 알기는 힘들다. &lt;/p&gt;

&lt;h3&gt;
  
  
  Strong Consistency
&lt;/h3&gt;

&lt;p&gt;강한 정합성은 이름만 들어도 감이 잡힌다. 그 어떤 노드로 접근하든 무조건 같은 데이터를 봐야한다. 즉 모든 노드가 같은 데이터를 가지고 있어야 가능한 Consistency이다.&lt;/p&gt;

&lt;p&gt;은행을 생각해보면 될 것 같다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eventual Consistency
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;언젠가는 정합함&lt;/em&gt; 이라는 이름의 정합성이다. 어떤 변경점에 대해서 같은 시각 모든 클라이언트가 같은 값을 보지는 않지만, 동기화가 끝난 후엔 결국 같은 값을 보게 된다는 뜻이다. &lt;/p&gt;

&lt;p&gt;따라서 소프트웨어의 특성에 따라서 Latency를 희생하면서 정합성을 챙길 것이냐 아니면 빠른 응답을 위해 정합성을 희생할 것이냐가 결정된다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Client Server can be seen as Distributed system
&lt;/h1&gt;

&lt;p&gt;일반적인 소프트웨어는 클라이언트와 서버가 있다. 이는 2개의 노드를 가진 분산 시스템으로 생각할 수도 있다. 위의 분산 시스템 이론을 적용하여 Local First Software를 생각해볼 수 있다.&lt;/p&gt;

&lt;h1&gt;
  
  
  Local First Software
&lt;/h1&gt;

&lt;p&gt;우선 Local First Software의 특징 부터 알아보자.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data on the local
&lt;/h2&gt;

&lt;p&gt;Local First Software에서는 데이터가 멀리있어서 통신 비용이 발생하는 것을 피하기 위해서 데이터(상태)를 Local에 둔다. 유저의 인터렉션은 로컬의 데이터를 즉시 변화시키고 그에 따라서 UI도 즉시 반응한다. 심지어 로컬에 둘때 메모리에 두는것이 일반적이기 때문에 말그대로 즉시에 반응하는 소프트웨어를 만들 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still working on Network disconnection
&lt;/h2&gt;

&lt;p&gt;Local First Software는 네트워크 단절 상태에서도 로컬에 계속 업데이트하기 때문에 여전히 작동한다. 대신 네트워크에 다시 연결 되었을 때 동기화하는 작업이 발생한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply PACELC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Network Partitioned
&lt;/h3&gt;

&lt;p&gt;네트워크가 단절된 상황에서 가장 최신 정보라고 확신할 수 없어도 로컬에 있는 데이터를 활용해서 유저가 계속 사용할 수 있도록 한다. 즉 Partitioned 일때는 Availability를 택한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network not Partitioned (Normal)
&lt;/h3&gt;

&lt;p&gt;네트워크가 연결된 정상적인 상황에서 Local First Software는 Latency를 택한다. 로컬에 데이터를 바로 업데이트하고 서버의 데이터는 비동기적으로 처리함으로써 로컬의 데이터와 서버의 데이터가 합의(일치)가 되지 않은 상황을 용인한다. 변경내역들이 서버에 쌓이고 결국에는 모든 노드의 데이터가 동기화 된다.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Claude MCP</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Wed, 27 Nov 2024 08:59:48 +0000</pubDate>
      <link>https://dev.to/siisee11/claude-mcp-3p8i</link>
      <guid>https://dev.to/siisee11/claude-mcp-3p8i</guid>
      <description>&lt;p&gt;11월 25일 엔트로픽(클로드)에서 MCP라는 것에 대한 공지가 올라왔다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.anthropic.com/news/model-context-protocol" rel="noopener noreferrer"&gt;https://www.anthropic.com/news/model-context-protocol&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP
&lt;/h3&gt;

&lt;p&gt;MCP는 오픈소스 Model Context Protocol이고 이 프로토콜을 통해서 모델이 어떤 데이터(기능)에 직접 접근 할 수 있도록 해준다.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP 와 Tool
&lt;/h3&gt;

&lt;p&gt;이전에도 Tool 이라는 개념으로 LLM이 툴 리스트를 보고 사용하면 좋을만한 툴을 골라서 사용하는 방식으로 외부 데이터에 접근할 수 있었다. 그러나 툴은 그냥 정해진 규약 없이 개발자들이 원하는 방식으로 만드는 것이라, 재사용성이 적다고 할 수 있다.&lt;/p&gt;

&lt;p&gt;사실 MCP는 Tool의 정형화된 규약이라고 볼 수 있다. 마치 RESTFul이나 OpenTelemetry처럼 개발자들이 공통으로 사용할 규약이고 이에 맞춰서 개발하면 내껄 공유하기도 다른 사람것을 가져다 쓰기도 좋다. LLM과 데이터 소스의 통신 규약이라고 생각하면 될 것 같다.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Protocol
&lt;/h3&gt;

&lt;p&gt;MCP 프로토콜은 크게 서버, 클라이언트, 리소스로 나뉜다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/quickstart" rel="noopener noreferrer"&gt;출처&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;클라이언트는 MCP로 서버에게 리소스를 요청하고 서버는 MCP를 보고 실제로 리소스에 접근하는 코드를 통해서 리소스를 가져온 후, 클라이언트에게 프로토콜에 명시된 형태로 리소스를 전달한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL example
&lt;/h3&gt;

&lt;p&gt;&lt;a href="![Image%20description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rto4f6tlqnota9pc7hy.png)"&gt;도식&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MySQL 데이터에 접근하는 것을 MCP 서버를 이용해서 하는 과정은 위와 같다. 누군가 MySQL과 통신해서 데이터를 가져오는 table list, query, search 등을 MCP 서버로 구현해놨다. LLM이 필요한 데이터가 MySQL에 있으면 우리는 이미 구현되어있는 MCP 서버를 내 로컬의 MySQL 데이터베이스에 붙히는것만 설정해주면 된다. 아래는 Claude Desktop App으로 LLM을 사용할 때 설정하면 되는 것이다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sqlite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp-server-sqlite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--db-path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/YOUR_USERNAME/test.db"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 설정해주면 Claude는 MCP 서버가 어떤 일을 할 수 있는 지 알게 되고, 해당 기능이 필요할때 MCP 서버에게 요청을 보낼 수 있게 된다.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Good
&lt;/h3&gt;

&lt;p&gt;"LLM이 데이터에 접근할 수 있게 하는 MCP"라는 멋있어보이는 타이틀이 있지만 사실 엄청 신기하고 대단한것은 아니다. 기존에 Tool로 충분히 접근하고 있던 그 방식에 권위있는 단체에서 Open된 정형화된 형식을 공표했다는데 큰 의의가 있다.&lt;/p&gt;

&lt;p&gt;API호출로 서로 다른 서비스를 연결하는 것이 간단해졌듯이, 이런 표준화된 프로토콜은 LLM과의 통합을 더 간단하게 만들어줄 것이라고 생각한다.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>korean</category>
      <category>llm</category>
    </item>
    <item>
      <title>Langgraph Human In The Loop with socket</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Wed, 13 Nov 2024 13:12:29 +0000</pubDate>
      <link>https://dev.to/siisee11/langgraph-human-in-the-loop-with-socket-4779</link>
      <guid>https://dev.to/siisee11/langgraph-human-in-the-loop-with-socket-4779</guid>
      <description>&lt;p&gt;langgraph 의 interruption 기능을 통해서 Agent의 수행 중간에 human이 개입할 수 있다는 것을 알았다.&lt;/p&gt;

&lt;p&gt;하지만 예시들을 보면 전부 human interaction은 한 셈치고~ 넘어간다. 실제로 User에게 확인을 받으려면 어떻게 해야할까? 크게 세가지 방법이 있을 것 같다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Langgraph API 서버 사용
&lt;/h3&gt;

&lt;p&gt;langgraph cli 로 &lt;a href="https://langchain-ai.github.io/langgraph/concepts/langgraph_server/#application-structure" rel="noopener noreferrer"&gt;langgraph API 서버&lt;/a&gt;를 docker로 실행한 후 &lt;a href="https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-js" rel="noopener noreferrer"&gt;langgraph SDK&lt;/a&gt;로 그래프를 실행하고, 스테이트를 변경하고, 재게하고 할 수 있다.&lt;/p&gt;

&lt;p&gt;langgraph에서 제공하는 것들을 제공하는 방법대로 사용해야한다. 뭔간 설정이 많아지고, 내 코드랑 융합하기 까다로울 수 있어보인다.&lt;/p&gt;

&lt;h3&gt;
  
  
  서버에서 그래프 관리
&lt;/h3&gt;

&lt;p&gt;위의 Langgraph API 서버에서 필요한 부분만 내 커스텀 서버에 구현하는 방법이다. 예를 들어 그래프 실행하면 그래프를 실행한 클라이언트와 그래프 체크포인트를 저장해야하고, 유저의 확인 후에 다시 그래프를 불러와서 유저의 응답에 맞게 상태를 변경해서 재게해야한다.&lt;/p&gt;

&lt;p&gt;짜야할게 은근 많을 수도 있다.&lt;/p&gt;

&lt;h3&gt;
  
  
  소켓 연결
&lt;/h3&gt;

&lt;p&gt;Agent실행 시에 소켓을 연결하고 소켓을 통해서 유저와 인터렉션 하는 것이다. 기존 예시 코드에서 소켓연결과 소켓 통신으로 유저 확인 받는 단계만 추가하면 동작한다.&lt;/p&gt;

&lt;p&gt;대신, 글자 타이핑하듯 쳐지는 streaming을 구현하기 까다로울 수도 있다.  &lt;/p&gt;

&lt;h1&gt;
  
  
  소켓 연결로 구현
&lt;/h1&gt;

&lt;p&gt;일단 최대한 복잡성을 늘리지 않는 방향에서 구현을 해보고 싶어서 소켓연결로 구현해보았다.&lt;/p&gt;

&lt;p&gt;서버는 NestJs를 사용하고 클라이언트는 NextJs를 사용한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  서버
&lt;/h3&gt;

&lt;p&gt;일단 Websocket 연결을 위해 Gateway를 만든다. agent/start 시에 커넥션을 만들고 바로 agent를 시행하도록 했다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;WebSocketGateway&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;websocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;polling&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/agent/start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentGateway&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnGatewayConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnGatewayDisconnect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;agentFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentFactory&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle new connections&lt;/span&gt;
  &lt;span class="nf"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Client connected: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Option 1: Get actionData from query parameters&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAgentProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If no actionData is provided, you can wait for an event&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No action data provided&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle disconnections&lt;/span&gt;
  &lt;span class="nf"&gt;handleDisconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Client disconnected: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Send confirmation request&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;sendConfirmationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmation_request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Optional timeout for response&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Default to 'false' if timeout occurs&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3000000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3000 seconds timeout&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle client's confirmation response&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;SubscribeMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmation_response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;handleClientResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;MessageBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ConnectedSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingConfirmations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Start the agent process&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;startAgentProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Thread&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphStateConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;configurable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;thread_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;streamMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the graph until the first interruption&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;initialInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;graphStateConfig&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logAndEmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`--- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ---`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Will log when the graph is interrupted, after step 2.&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logAndEmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---GRAPH INTERRUPTED---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userConfirmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendConfirmationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Do you want to proceed with this action?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userConfirmed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If approved, continue the graph execution. We must pass `null` as&lt;/span&gt;
      &lt;span class="c1"&gt;// the input here, or the graph will&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;graphStateConfig&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logAndEmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`--- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ---`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logAndEmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---ACTION EXECUTED---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logAndEmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---ACTION CANCELLED---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Optionally disconnect the client&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;logAndEmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;핵심은 간단하다. Socket이 연결되면 바로 agent를 생성하여 수행하고, 수행해서 interrupt 당하면 Client에게 confirmation request message를 보내고 기다린다. confirmation이 resolve되면 이어서 graph를 진행한다. &lt;/p&gt;

&lt;p&gt;위 코드에서 사용한 agent는 langgraph 문서에 있는 아래 스텝 1 2 3 을 순차적으로 사용하는 에이전트이다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GraphState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Annotation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;GraphState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---Step 1---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;GraphState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---Step 2---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;GraphState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---Step 3---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GraphState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Set up memory&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphStateMemory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MemorySaver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;checkpointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphStateMemory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;interruptBefore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  클라이언트
&lt;/h3&gt;

&lt;p&gt;클라이언트에서는 훅을 만들어서 agent start와 그 상태를 관리한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;socket.io-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useAgentSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socketRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Socket&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;confirmationRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setConfirmationRequest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connectAndRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/agent/start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;websocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;polling&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connect_error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Listen for confirmation requests&lt;/span&gt;
      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmation_request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setConfirmationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Listen for messages&lt;/span&gt;
      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received message:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevMessages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prevMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disconnect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected from server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendConfirmationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmation_response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;confirmed&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;setConfirmationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;disconnectSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;socketRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;confirmationRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sendConfirmationResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;connectAndRun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;disconnectSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;clearMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;커넥션을 맺고, confirmation request가 오면 confirmationRequest 상태를 업데이트한다. UI component에서 confirmationRequest 상태를 보고 유저에게 창을 띄워주면 된다.&lt;/p&gt;

</description>
      <category>langgraph</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Langgraph - Human In the Loop</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Tue, 12 Nov 2024 14:33:00 +0000</pubDate>
      <link>https://dev.to/siisee11/langgraph-human-in-the-loop-c5a</link>
      <guid>https://dev.to/siisee11/langgraph-human-in-the-loop-c5a</guid>
      <description>&lt;p&gt;Agent system 을 만들다보면 자동으로 결과까지 완벽하게 돌아가주면 좋겠지만 지금의 정확도로는 힘들다. 만약 전체 과정을 Agent가 생각하여 진행하도록 한다면, 중간에 한번의 실수만 있어도 의도와는 매우 다른 결과를 얻게 될 것이다.&lt;br&gt;
그리고 Agent에게 권한을 전적으로 위임하기엔 무서운 오퍼레이션들도 있다. API콜이나, DB 수정, Bash 스크립팅등... 이 있다.&lt;/p&gt;

&lt;p&gt;그렇기에 지금(어쩌면 꽤 오랫동안)은 Agent의 수행과정 중 인간의 간섭은 필연적이다.&lt;/p&gt;
&lt;h1&gt;
  
  
  Langgraph의 Human-in-the-loop
&lt;/h1&gt;

&lt;p&gt;인간이 간섭하는 5가지의 상황을 지원한다.&lt;/p&gt;
&lt;h3&gt;
  
  
  Approval
&lt;/h3&gt;

&lt;p&gt;다음 실행을 할 것인지 판단을 인간에게 맡김.&lt;/p&gt;
&lt;h3&gt;
  
  
  Editing
&lt;/h3&gt;

&lt;p&gt;현재 상태를 유저한테 보여주고 수정할 수 있도록 함.&lt;/p&gt;
&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;User Input을 받는 단계를 명시하고 실제로 유저한테 값을 받는 것.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reviewing tool calls
&lt;/h3&gt;

&lt;p&gt;유저가 Tool의 결과 값을 보고 수정할 수 있도록 함.&lt;/p&gt;
&lt;h3&gt;
  
  
  Time travel
&lt;/h3&gt;

&lt;p&gt;이전 상태(노드 수행전)로 돌아가던가, 돌아가서 다른 진행을 할 수 있도록 하는 것. Like multi-verse.&lt;/p&gt;
&lt;h1&gt;
  
  
  Persistant Layer
&lt;/h1&gt;

&lt;p&gt;이런 인간의 간섭이 가능하게 하는 건 Langgraph의 persistant layer 덕이다. 인간의 간섭이 필요한 상태를 저장해놨다가 유저의 승인, 수정이 끝나면 다시 그 지점부터 시작이 가능하다. 게임에서 체크포인트 같은 개념이다.&lt;/p&gt;
&lt;h1&gt;
  
  
  Breakpoint
&lt;/h1&gt;

&lt;p&gt;디버깅 툴에서 Breakpoint 설정하는 것과 같이 breakpoint는 그 지점까지만 신나게 수행하고 잠시 멈추라는 표시이다.&lt;/p&gt;

&lt;p&gt;langgraph에서는 graph를 컴파일할때 break point를 명시할 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interrupt_before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step_for_human_in_the_loop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Dynamic Breakpoint
&lt;/h1&gt;

&lt;p&gt;컴파일 할 때 선언하는 것은 정적이다. 실행 중에 변경되는 state에 따라서 멈추는 것이 어렵다. Dynamic Breakpoint는 state에 따라서 설정할 수 있는 breakpoint이다. &lt;code&gt;NodeInterrupt&lt;/code&gt;라는 특별한 exception이 발생하면 그래프 수행을 멈추고 유저 간섭을 기다린다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;NodeInterrupt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received input that is longer than 5 characters: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Patterns
&lt;/h1&gt;

&lt;p&gt;그림을 보면 좀 더 쉽다. &lt;/p&gt;

&lt;h3&gt;
  
  
  Approval
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Compile our graph with a checkpoitner and a breakpoint before the step to approve
&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;checkpoitner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interrupt_before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Run the graph up to the breakpoint
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ... Get human approval ...
&lt;/span&gt;
&lt;span class="c1"&gt;# If approved, continue the graph execution from the last saved checkpoint
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;node_2 전에 그래프 수행이 끝나서 첫번째 for 구문을 탈출하게 되고, 그 후에 다시 graph.stream을 호출해서 이어서 수행한다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsszwgp2u849i4a6s8h8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsszwgp2u849i4a6s8h8m.png" alt="Image description" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Compile our graph with a checkpoitner and a breakpoint before the step to review
&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;checkpoitner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interrupt_before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Run the graph up to the breakpoint
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Review the state, decide to edit it, and create a forked checkpoint with the new state
&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Continue the graph execution from the forked checkpoint
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;역시 node_2 앞에서 멈추고, 이번에는 아무것도 안하고 다시 graph.stream을 호출하는 대신, 현재 그래프의 state를 변경한다. 그 후에 변경된 state에서 다시 그래프를 수행한다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo9v4sf5e9h56xeyt1sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo9v4sf5e9h56xeyt1sm.png" alt="Image description" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  이외
&lt;/h3&gt;

&lt;p&gt;이외의 동작들은 아래 랭그래프 공식 도큐먼트에서 확인하자&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/" rel="noopener noreferrer"&gt;https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agentic</category>
      <category>langgraph</category>
      <category>javascript</category>
      <category>korean</category>
    </item>
    <item>
      <title>Zed에서 VSCode로 돌아옴</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Thu, 29 Aug 2024 09:10:58 +0000</pubDate>
      <link>https://dev.to/siisee11/zedeseo-vscodero-dolaom-10gj</link>
      <guid>https://dev.to/siisee11/zedeseo-vscodero-dolaom-10gj</guid>
      <description>&lt;p&gt;Rust로 만들어져서 버그 프리할 것 같고, 홈페이지 들어가면 빠르다고 크게 적혀있어서 Zed를 사용해보기로함.&lt;/p&gt;

&lt;h3&gt;
  
  
  1차 시도
&lt;/h3&gt;

&lt;p&gt;사실 1차 시도는 6달전쯤인 2024년 2월 정도에 시도해봄.&lt;/p&gt;

&lt;p&gt;이 때 시도하다 안쓰게된 이유 중 가장 크리티컬했던건 ctrl + tab 이 이전 탭으로 이동이 아닌 것 이었다.&lt;/p&gt;

&lt;p&gt;평생 ctrl + tab으로 이전 탭 이동하던 사람(아마 다 그럴듯)으로써 절대 적응할 수 없는 것이었기에 포기했다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2차 시도
&lt;/h3&gt;

&lt;p&gt;2024년 8월 중순 쯤 다시 깔아서 체험해봄.&lt;/p&gt;

&lt;p&gt;위의 이슈는 &lt;a href="https://github.com/zed-industries/zed/issues/7321" rel="noopener noreferrer"&gt;ctrl + tab 이슈&lt;/a&gt; 다행히 해결되어 있었다.&lt;/p&gt;

&lt;p&gt;불편한 점은 있었지만, 단축키 설정 + 손에 익히기 전략으로 약 2주간 사용했지만 다시 포기했다.&lt;/p&gt;

&lt;h4&gt;
  
  
  아직 불편한 점들  (2024/08/29)
&lt;/h4&gt;

&lt;h5&gt;
  
  
  심각하게 불편한 점
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;Find Reference 하면 reference 찾는 창이 파일 마냥 생기는데, 이 파일이 열려있는 상태에서 그 안쪽의 코드가 변경되면 따로 세이브를 해주거나 변경 안하고 저장을 해줘야함.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;만약 Find Reference에 있는 코드를 삭제하고 돌아와서 이 reference를 저장해버리면, 지웠던 코드가 다시 살아남. (개불편)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Javascript 관련된 것들&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;import를 상대주소로 가져옴 &lt;a href="https://github.com/zed-industries/zed/issues/7626" rel="noopener noreferrer"&gt;해결법이 있는듯 하나 잘 안됨&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auto save 시 린팅이 잘 안됨&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  적당히 불편함 점
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Vim 에서 gd 로 reference 이동이 안됨&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Github extension이 없는 점 &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>NestJS + Opentelemetry (Sampling)</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Sun, 18 Aug 2024 14:26:11 +0000</pubDate>
      <link>https://dev.to/siisee11/nestjs-opentelemetry-sampling-2ome</link>
      <guid>https://dev.to/siisee11/nestjs-opentelemetry-sampling-2ome</guid>
      <description>&lt;h1&gt;
  
  
  Grafana Cloud
&lt;/h1&gt;

&lt;p&gt;이전 포스트에서 Grafana Cloud에 Opentelemetry data를 쏴서 저장하고 보는 것을 했다.&lt;/p&gt;

&lt;p&gt;그라파나 클라우드 무료 버전을 사용하면 한달에 로그와 트레이스에 50GB정도를 준다. 유저가 얼마 없어서 Trace가 별로 안쌓이는(혹은 로그를 안찍는)서비스라면 그냥 사용해도 되겠지만, 조금 규모가 있는 상태에서 도입한다면 로그가 너무 많이 쌓여 터질까봐 두렵다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sampling
&lt;/h2&gt;

&lt;p&gt;Sampling이란 전체에서 일부를 뽑아 쓰는 것이다. 결과적으로 저장되는 Telemetry 데이터의 수를 감소시키는 작업이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why need Sampling
&lt;/h2&gt;

&lt;p&gt;샘플링은 왜 필요할까?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwn5phugbzytu3wqvcjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwn5phugbzytu3wqvcjd.png" alt="Image description" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;위의 그림에서 모든 동그라미(트레이스)를 저장할 필요는 없다. 중요한 트레이스(에러, 혹은 너무 수행시간이 긴)와 전체를 대표하는 일부 표본(OK trace중 일부)만 저장하면 충분하다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sampling의 종류
&lt;/h2&gt;

&lt;p&gt;샘플링은 크게 Head Sampling, Tail Sampling으로 나눌 수 있다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Head Sampling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;맨 앞에서 샘플링하는 것을 말한다. 대표적으로 그냥 확률적으로 샘플링 하는 것이 있다. 전체 트레이스에서 10퍼센트만 남기고 나머지는 트레이스 하지 않는 것이다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Javascript
&lt;/h4&gt;

&lt;p&gt;TraceIdRatioBasedSampler를 기본적으로 제공한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-node';

const samplePercentage = 0.1;

const sdk = new NodeSDK({
  // Other SDK configuration parameters go here
  sampler: new TraceIdRatioBasedSampler(samplePercentage),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  단점
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;묻고 따지지도 않고 Drop해버리는 거기 때문에 중요한 trace들이 드롭되는 경우가 있다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tail Sampling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;뒤쪽에서 샘플링 하는 것이다. 이 때는 사용할 수 있는 정보가 많기 때문에 원하는 로직에 따라서 필터링 할 수 있다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예를 들어, 에러 트레이스는 무조건 샘플링 하는 식이다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;보통, 콜렉터에서 일단 모든 트레이스를 받은 이후에 샘플링을 한다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://opentelemetry.io/docs/concepts/sampling/" rel="noopener noreferrer"&gt;단점&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;구현이 어려울 수 있다. 시스템이 바뀌고 조건이 바뀌면 항상 바껴야하는 존재다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;샘플링하기 위해 Stateful인 상태를 유지하고 있어야해서 수행이 어렵다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tail Sampler가 vendor-specific 인 경우가 있다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  구현
&lt;/h2&gt;

&lt;p&gt;Tail Sampling을 Custom Span Processor를 구현하여 구현해보자.&lt;/p&gt;

&lt;h3&gt;
  
  
  SamplingSpanProcessor 구현
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sampling-span-processor.ts&lt;/code&gt; 파일 생성&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Context } from "@opentelemetry/api";
import {
  SpanProcessor,
  ReadableSpan,
  Span,
} from "@opentelemetry/sdk-trace-node";

/**
 * Sampling span processor (including all error span and ratio of other spans)
 */
export class SamplingSpanProcessor implements SpanProcessor {
  constructor(
    private _spanProcessor: SpanProcessor,
    private _ratio: number
  ) {}

  /**
   * Forces to export all finished spans
   */
  forceFlush(): Promise&amp;lt;void&amp;gt; {
    return this._spanProcessor.forceFlush();
  }

  onStart(span: Span, parentContext: Context): void {
    this._spanProcessor.onStart(span, parentContext);
  }

  shouldSample(traceId: string): boolean {
    let accumulation = 0;
    for (let idx = 0; idx &amp;lt; traceId.length; idx++) {
      accumulation += traceId.charCodeAt(idx);
    }
    const cmp = (accumulation % 100) / 100;
    return cmp &amp;lt; this._ratio;
  }

  /**
   * Called when a {@link ReadableSpan} is ended, if the `span.isRecording()`
   * returns true.
   * @param span the Span that just ended.
   */
  onEnd(span: ReadableSpan): void {
    // Only process spans that have an error status
    if (span.status.code === 2) {
      // Status code 0 means "UNSET", 1 means "OK", and 2 means "ERROR"
      this._spanProcessor.onEnd(span);
    } else {
      if (this.shouldSample(span.spanContext().traceId)) {
        this._spanProcessor.onEnd(span);
      }
    }
  }

  /**
   * Shuts down the processor. Called when SDK is shut down. This is an
   * opportunity for processor to do any cleanup required.
   */
  async shutdown(): Promise&amp;lt;void&amp;gt; {
    return this._spanProcessor.shutdown();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;status.code가 2 (Error)거나 ratio 확률에 당첨되었을 때만 &lt;code&gt;this._spanProcessor.onEnd(span);&lt;/code&gt; 를 호출해서 export한다.&lt;/p&gt;

&lt;h4&gt;
  
  
  OtelSDK 업데이트
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;main.ts&lt;/code&gt;에서 spanProcessors를 업데이트 해준다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  spanProcessors: [
    new SamplingSpanProcessor(
      new BatchSpanProcessor(traceExporter),
      samplePercentage
    ),
  ],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nestjs</category>
    </item>
    <item>
      <title>NestJS + Opentelemetry (Grafana Cloud)</title>
      <dc:creator>Jaeyoun Nam</dc:creator>
      <pubDate>Sun, 18 Aug 2024 12:48:54 +0000</pubDate>
      <link>https://dev.to/siisee11/nestjs-opentelemetry-grafana-cloud-328f</link>
      <guid>https://dev.to/siisee11/nestjs-opentelemetry-grafana-cloud-328f</guid>
      <description>&lt;h1&gt;
  
  
  Production환경에서 Opentelemetry 사용
&lt;/h1&gt;

&lt;p&gt;Opentelemetry를 어플리케이션에 설정하고 로컬에 Otel Collector와 Loki, Tempo, Grafana를 띄워서 트레이스를 볼 수 있는 건 이 전 포스팅까지 마무리가 되었다.&lt;/p&gt;

&lt;p&gt;이제 남은건 로컬 뿐 아니라 실제 Production 환경에서의 tracing을 보는 것이다.&lt;/p&gt;

&lt;p&gt;그러기 위해 필요한 것은 '클라우드 상에 Log, Trace 를 저장하는 일'이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  방법들
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Opentelemetry Collector 를 배포하기
&lt;/h3&gt;

&lt;p&gt;Opentelemetry Collector (+ Loki, Tempo 등)를 어디엔가 띄워 놓고 어플리케이션에서 쏘는 OLTP의 주소를 이 Collector로 설정하면 된다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;혹은 더 좋은 Scalability를 위해서 Load balancing을 위한 Gateway를 두고, 게이트웨이에서 OLTP를 받아서 내부의 Collectors에게 넘겨 주는 방법도 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Grafana Alloy를 설치하여 배포하기
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://grafana.com/docs/alloy/latest/" rel="noopener noreferrer"&gt;Grafana Alloy&lt;/a&gt;는 그라파나에서 제공하는 configurable한 opentelemetry collector 이다. &lt;/p&gt;

&lt;p&gt;Docker로 배포하거나 기존에 Kubernates를 사용했다면 새로운 노드로 추가할 수 있다.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Collector 없이 바로 쏘기
&lt;/h3&gt;

&lt;p&gt;Collector 없이 Backend(Loki, Tempo, Jaeger, 등...)로 OLTP를 바로 보내버리는 방법이다.&lt;/p&gt;

&lt;p&gt;Backend로 Grafana Cloud의 Loki, Tempo 를 사용할 수 있어서 배포 없이 빠르게 도입 가능하다는 장점이 있다.&lt;/p&gt;

&lt;p&gt;대신 Collector를 사용해서 얻을 수 있는 Scalability나 Processing등의 장점은 사라진다.&lt;/p&gt;

&lt;h2&gt;
  
  
  채택: Collector 없이 쏘기
&lt;/h2&gt;

&lt;p&gt;멋지게 Collector를 배포해서 쓰고 싶으나, 기존에 Kubernetes도 쓰지 않는 환경에서 Collector따로 배포하고 설정하는데 시간이 너무 많이 소요될것 같아서 그냥 Grafana Cloud로 바로 쏘는 방법을 선택했다.&lt;/p&gt;

&lt;p&gt;사실 실험용으로 도입해보는 거고 스타트업이어서 scalability가 크게 중요하게 작용하지 않고 (로깅이니까) 무엇보다 빠르게 해볼 수 있기 때문에, fancy하진 않지만 좋은 의사결정이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  코드
&lt;/h2&gt;

&lt;p&gt;코드 변경은 매우 심플하다. OLTP의 endpoint와 Protocol만 잘 설정해주면 된다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracer
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { OTLPTraceExporter as PROTOOTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";

const oltpTraceExporter = new PROTOOTLPTraceExporter({
  url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + "/v1/traces",
  headers: {
    Authorization: process.env.OTEL_EXPORTER_OTLP_HEADERS_AUTHORIZATION,
  },
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;우리가 쏠 trace를 받을 endpoint(grafana cloud)는 http/protobuf 프로토콜을 받으므로  &lt;code&gt;exporter-trace-otlp-proto&lt;/code&gt;에서 임포트해서 써야한다.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logger
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const logExporter = new OTLPLogExporter({
  url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + "/v1/logs",
  headers: {
    Authorization: process.env.OTEL_EXPORTER_OTLP_HEADERS_AUTHORIZATION,
  },
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Logger는 기존에도 Http protocol을 쓰고 있었기 때문에 OTLPLogExporter는 그대로 사용하면 된다.&lt;/p&gt;

&lt;h3&gt;
  
  
  환경변수
&lt;/h3&gt;

&lt;p&gt;참고로 NestJS에서 환경 변수는 AppModule을 Initialization할 때 설정되니, AppModule 만들기 전에 완료해야하는 tracer와 logger 세팅에서 사용할 수 없다.&lt;/p&gt;

&lt;p&gt;만약 dotenv를 사용하고 있다면 먼저 불러주어야한다.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;// eslint-disable-next-line import/order&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// eslint-disable-next-line import/order&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getEnvFilePath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/utils/env-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// load env before loading tracer and logger&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="c1"&gt;// eslint-disable-next-line import/order&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;otelSDK&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tracer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// otelSDK should be imported before any other imports&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// eslint-disable-next-line import/order&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createLogger&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  환경변수 값&lt;br&gt;
&lt;/h4&gt;

&lt;p&gt;은근 찾기 어려우니 잘 따라오자.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://grafana.com" rel="noopener noreferrer"&gt;그라파나&lt;/a&gt; 접속&lt;/li&gt;
&lt;li&gt;오른쪽 위 My Account 클릭&lt;/li&gt;
&lt;li&gt;왼쪽 Navbar의 GRAFANA CLOUD 밑의 Stack 이름을 클릭 (없으면 new stack으로 만듬)&lt;/li&gt;
&lt;li&gt;'Manage your stack' 화면에서 Opentelmetry 카드의 configure 누름&lt;/li&gt;
&lt;li&gt;여기서 OTEL_EXPORTER_OTLP_ENDPOINT 를 얻을 수 있고,&lt;/li&gt;
&lt;li&gt;아래 Password / API Token 에서 Generate Key를 눌러서 키를 만들면&lt;/li&gt;
&lt;li&gt;Environment Variables 칸에 OTEL_EXPORTER_OTLP_HEADERS에서 Basic으로 시작하는 부분이 OTEL_EXPORTER_OTLP_HEADERS_AUTHORIZATION 변수의 값이다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;환경 변수를 등록해주고 실행한다.&lt;/p&gt;

&lt;h2&gt;
  
  
  그라파나 클라우드 실행
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://grafana.com" rel="noopener noreferrer"&gt;그라파나&lt;/a&gt;에서 Grafana Launch 를 클릭하고 Explore에서 데이터를 살펴보자&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyuadjzk3c0ketsuiuyg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyuadjzk3c0ketsuiuyg1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>nestjs</category>
    </item>
  </channel>
</rss>
