<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>MAKE IT SIMPLE</title>
    <link>https://vincode.tistory.com/</link>
    <description>들여다 볼수록 간단해지기를</description>
    <language>ko</language>
    <pubDate>Thu, 11 Jun 2026 23:18:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>빈빠끄</managingEditor>
    <item>
      <title>React로 동적 피벗 테이블 만들기: 트리 모델링부터 rowSpan까지</title>
      <link>https://vincode.tistory.com/27</link>
      <description>&lt;h1&gt;동적 데이터셋 테이블, 어디까지 만들어봤니&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;행/열/값을 사용자가 마음대로 끌어다 놓으면 알아서 피벗되고, 소계·합계·총계까지 끼워 넣는 테이블을 React로 만들면서 정리한 기록.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;1. 들어가며 — 단순해 보였던 요구사항&lt;/h2&gt;
&lt;p&gt;&amp;quot;엑셀 피벗 같은 거 화면에서 보여주면 돼요.&amp;quot;&lt;/p&gt;
&lt;p&gt;처음 받은 한 줄. 가볍게 시작했는데, 막상 펴 보니 정적 테이블과는 완전히 다른 영역이었다. 사용자는 같은 데이터셋을 가지고 어떨 땐 단순 목록으로 보고 싶고, 어떨 땐 부서×월로 묶어서 보고, 또 어떨 땐 거기에 소계와 총계까지 끼워서 보고 싶어 한다. 그리고 모든 모드가 같은 컴포넌트에서 매끄럽게 전환돼야 한다.&lt;/p&gt;
&lt;p&gt;이 글은 그 요구사항을 풀면서 만든 &lt;code&gt;&amp;lt;Grid /&amp;gt;&lt;/code&gt; 컴포넌트와 &lt;code&gt;useCreateTableV2&lt;/code&gt; 훅의 설계·구현을 정리한 회고다. 일부 헬퍼는 재귀가 두 번 꺾이는 구간이 있어서 코드를 그대로 옮기되, 핵심 아이디어를 먼저 설명하는 식으로 풀어 본다.&lt;/p&gt;
&lt;h2&gt;2. 요구사항 — 정확히 무엇을 만들어야 했나&lt;/h2&gt;
&lt;p&gt;만들어야 했던 동작을 한 줄로 줄이면 이렇다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;라인 아이템(&lt;code&gt;ILineItem[]&lt;/code&gt;) 하나를 입력으로&lt;/li&gt;
&lt;li&gt;사용자가 선택한 &lt;strong&gt;행 그룹 / 열 그룹 / 값 그룹&lt;/strong&gt;에 따라&lt;/li&gt;
&lt;li&gt;다음 세 가지 렌더링 모드 중 하나로 출력한다(나머지는 fallback).&lt;ol&gt;
&lt;li&gt;그룹 없음 → 기본 표&lt;/li&gt;
&lt;li&gt;행 그룹만 → 계층형 행 테이블&lt;/li&gt;
&lt;li&gt;행+열+값 → 피벗 테이블&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기에 옵션으로 세 가지 집계를 켜고 끌 수 있어야 했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;소계(subtotal):&lt;/strong&gt; 특정 그룹 단위 안에서의 합. 예) &lt;code&gt;부서&lt;/code&gt; 안 직급별 행이 끝나는 자리에 들어가는 &amp;quot;영업팀 소계&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;합계(semi total):&lt;/strong&gt; 행/열 그룹 한 축 전체에 대한 합. 예) 모든 부서 행 그룹을 묶은 &amp;quot;전체 행 합계&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;총계(total):&lt;/strong&gt; 행×열 매트릭스 전체에 대한 합. 표 우하단 한 셀.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;셀 병합(rowSpan/colSpan)도 정확히 맞아야 했다. 행이 2단 그룹이면 1단 셀은 자식 셀 수만큼 세로 병합, 열도 마찬가지.&lt;/p&gt;
&lt;p&gt;작은 예시 하나로 이후 설명의 기준을 잡자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;rowGroup    = [&amp;#39;부서&amp;#39;, &amp;#39;직급&amp;#39;]
colGroup    = [&amp;#39;월&amp;#39;]
valueGroup  = [&amp;#39;매출&amp;#39;]
showRowsTotal = true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 입력은 대략 이런 표가 된다.&lt;/p&gt;
&lt;table style=&quot;width:100%; table-layout:fixed;&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align:left;&quot;&gt;부서&lt;/th&gt;
      &lt;th style=&quot;text-align:left;&quot;&gt;직급&lt;/th&gt;
      &lt;th style=&quot;text-align:right;&quot;&gt;1월&lt;/th&gt;
      &lt;th style=&quot;text-align:right;&quot;&gt;2월&lt;/th&gt;
      &lt;th style=&quot;text-align:right;&quot;&gt;합계&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;영업팀&lt;/td&gt;&lt;td&gt;사원&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;100&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;120&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;220&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;영업팀&lt;/td&gt;&lt;td&gt;대리&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;80&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;90&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;170&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background:#f2f2f2;&quot;&gt;
      &lt;td&gt;영업팀&lt;/td&gt;&lt;td&gt;&lt;b&gt;소계&lt;/b&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;&lt;b&gt;180&lt;/b&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;&lt;b&gt;210&lt;/b&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;&lt;b&gt;390&lt;/b&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;개발팀&lt;/td&gt;&lt;td&gt;사원&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;200&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;150&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;350&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;...&lt;/td&gt;&lt;td&gt;...&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;...&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;...&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;...&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style=&quot;background:#e0e0e0;&quot;&gt;
      &lt;td&gt;&lt;b&gt;총계&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;&lt;b&gt;...&lt;/b&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;&lt;b&gt;...&lt;/b&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align:right;&quot;&gt;&lt;b&gt;...&lt;/b&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이후 &lt;code&gt;rowSpan&lt;/code&gt;, &lt;code&gt;colSpan&lt;/code&gt;, &lt;code&gt;getGroupedData&lt;/code&gt; 이야기를 할 때 머릿속에 이 표를 띄워두면 한결 덜 추상적이다.&lt;/p&gt;
&lt;h2&gt;3. 데이터 모델 — 트리가 답이다&lt;/h2&gt;
&lt;p&gt;요구사항을 며칠 보다가 결론이 났다. &lt;strong&gt;계층은 트리로 모델링하고, 렌더링은 트리를 평면화하는 과정이다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export type GridGroup = {
  title: string;
  key: string;
  index?: number;
  children?: GridGroup[];
  items?: ILineItem[];
  order?: number;
  colSpan?: number;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GridGroup&lt;/code&gt;은 행/열 모두에 쓰는 공통 노드. 리프 노드는 &lt;code&gt;items&lt;/code&gt;를 들고 있고, 내부 노드는 &lt;code&gt;children&lt;/code&gt;을 들고 있다. 여기까지는 흔한 트리. 다만 &lt;code&gt;key&lt;/code&gt;에 부모-자식 경로를 &lt;code&gt;_&lt;/code&gt; 로 이어 붙여 평면화된 고유 키를 만들어 둔 게 포인트다. 이 키가 뒤에서 데이터 매핑의 기준이 된다.&lt;/p&gt;
&lt;p&gt;데이터는 &lt;code&gt;GridData&lt;/code&gt;로 별도 분리한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export type GridData = {
  division?: string;
  [key: string]: ItemValueType;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;division&lt;/code&gt;은 어떤 &lt;strong&gt;행 노드&lt;/strong&gt;에 매핑되는 데이터인지 식별하는 키, 나머지 키는 어떤 &lt;strong&gt;열 노드&lt;/strong&gt;에 들어갈 값인지 식별하는 키. 즉 트리 두 개(행/열)와 셀 데이터 한 개(행 키 → 열 키 → 값)로 모델을 완전히 분리했다.&lt;/p&gt;
&lt;h2&gt;4. 입력에서 트리까지 — &lt;code&gt;groupByHierarchical&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;사용자가 &lt;code&gt;rowGroup: [부서, 직급]&lt;/code&gt; 을 골랐다고 하자. 라인 아이템 배열을 이 키 순서대로 중첩 그룹화해야 한다. &lt;code&gt;Array.prototype.reduce&lt;/code&gt;를 두 번 재귀로 돌리는 게 핵심.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const groupByHierarchical = (data, keys) =&amp;gt; {
  const groupByRecursively = (items, remainingKeys) =&amp;gt; {
    if (remainingKeys.length === 0) return items;
    const [currentKey] = remainingKeys;
    return items.reduce((result, item) =&amp;gt; {
      const groupKey = item[currentKey];
      (result[groupKey] ??= []).push(item);
      return result;
    }, {});
  };

  const nestedGroupBy = (groupedData, keys) =&amp;gt; {
    if (keys.length === 0) return groupedData;
    const [currentKey, ...nextKeys] = keys;
    for (const key in groupedData) {
      if (Array.isArray(groupedData[key])) {
        groupedData[key] = groupByRecursively(groupedData[key], [currentKey]);
        nestedGroupBy(groupedData[key], nextKeys);
      }
    }
    return groupedData;
  };

  const initial = groupByRecursively(data, [keys[0]]);
  return nestedGroupBy(initial, keys.slice(1));
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처음엔 한 번에 다 묶으려고 reduce 하나로 짰는데 코드가 너무 꼬여서 &lt;strong&gt;&amp;quot;먼저 한 단계 묶고, 그 다음에 단계별로 더 묶어 내려가는&amp;quot;&lt;/strong&gt; 두 단계 구조로 풀었다. 결과는 &lt;code&gt;{ 영업팀: { 사원: [...], 대리: [...] }, 개발팀: {...} }&lt;/code&gt; 같은 중첩 객체. 이게 곧 트리 변환의 입력이 된다.&lt;/p&gt;
&lt;h2&gt;5. 핵심 변환 — &lt;code&gt;transformToGridGroup&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;groupByHierarchical&lt;/code&gt;의 결과는 객체 트리지만 렌더링이 알 수 있는 모양은 아니다. 이걸 &lt;code&gt;GridGroup&lt;/code&gt; 트리로 옮기면서 동시에 &lt;strong&gt;소계/합계 노드를 끼워 넣는&lt;/strong&gt; 일을 한 함수에서 처리하는 게 &lt;code&gt;transformToGridGroup&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;요약하면 세 단계로 일한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;traverse&lt;/strong&gt; — 객체 트리를 깊이 우선으로 돌면서 &lt;code&gt;GridGroup&lt;/code&gt; 노드를 만든다. 키 경로는 &lt;code&gt;parent_child&lt;/code&gt; 식으로 누적해서 leaf까지 고유한 key를 가지도록.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;소계 삽입&lt;/strong&gt; — &lt;code&gt;showTotal: true&lt;/code&gt;로 표시된 그룹의 &lt;code&gt;index&lt;/code&gt;와 현재 traverse depth가 일치하는 시점에 &lt;code&gt;&amp;#39;소계&amp;#39;&lt;/code&gt; 노드를 children 마지막에 끼워 넣는다. axis가 &lt;code&gt;&amp;#39;col&amp;#39;&lt;/code&gt;이면 그냥 push, &lt;code&gt;&amp;#39;row&amp;#39;&lt;/code&gt;면 추가로 &lt;code&gt;colSpan&lt;/code&gt;을 계산해서 단일 셀로 펼친다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;총계 삽입&lt;/strong&gt; — &lt;code&gt;showTotal: true&lt;/code&gt;로 들어오면 트리 루트 옆에 &lt;code&gt;&amp;#39;합계&amp;#39;&lt;/code&gt;(중간 집계) 또는 &lt;code&gt;&amp;#39;총계&amp;#39;&lt;/code&gt;(최종 집계)를 형제 노드로 추가한다. &lt;code&gt;axis_total&lt;/code&gt;, &lt;code&gt;axis_semi_total&lt;/code&gt; 같은 prefix로 키를 만든 게 렌더링 시점에 &lt;code&gt;&amp;#39;!bg-[#C1C4CF]&amp;#39;&lt;/code&gt; 회색 처리를 분기하는 신호가 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기서 한 번 헤맸던 부분이 children 정렬. 진짜 문제는 객체 key 순서의 불안정성이 아니라 &lt;strong&gt;자식 노드 순서가 도메인 원본 순서(라인 아이템에 들어온 순서)를 따라야 했다&lt;/strong&gt;는 것. 그룹화 과정에서는 그 순서 정보가 자연스럽게 소실되니까 별도로 들고 다녀야 했다. 그래서 &lt;code&gt;groupsOrderMap&lt;/code&gt;에 leaf에 도달했을 때의 &lt;code&gt;RN&lt;/code&gt;(원본 row number) 최대값을 기록해 두고, 부모로 올라오면서 그 값으로 정렬하는 우회로를 만들었다. 우아하진 않지만 동작은 안정적이다.&lt;/p&gt;
&lt;h2&gt;6. rowSpan/colSpan — 트리를 펴면 자연스럽게 나온다&lt;/h2&gt;
&lt;p&gt;렌더링 시점의 셀 병합은 트리 구조에서 직접 계산된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const getRowSpan = (row: GridGroup): number =&amp;gt; {
  if (!row.children || !row.children.length) return 1;
  return row.children.reduce((depth, child) =&amp;gt; depth + getRowSpan(child), 0);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;리프면 1, 아니면 자식들의 rowSpan 합. 한 줄짜리 재귀인데 그림으로 그려보면 &lt;strong&gt;부모 셀이 자식 leaf 개수만큼 세로로 합쳐진다&lt;/strong&gt;는 정의를 그대로 옮긴 것. 같은 방식으로 &lt;code&gt;getMaxDepth&lt;/code&gt;가 열 헤더의 rowSpan 기준을 잡아주고, &lt;code&gt;getColSpan&lt;/code&gt;이 첫 번째 행 헤더 자리(&lt;code&gt;idx === 0&lt;/code&gt;)의 가로 합치기를 처리한다.&lt;/p&gt;
&lt;p&gt;이 부분이 트리 모델로 풀길 잘했다고 느낀 지점이다. DOM 기반으로 셀 병합을 손으로 계산하기 시작하면 행/열 교차점 케이스에서 반드시 무너진다.&lt;/p&gt;
&lt;h2&gt;7. 렌더링 — &lt;code&gt;renderFirstChild&lt;/code&gt;의 작은 트릭&lt;/h2&gt;
&lt;p&gt;행 그룹이 2단 이상이면 한 &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 안에 부모 셀과 자식 셀이 같이 그려져야 한다. 첫 번째 자식만 부모와 같은 행에 그리고, 두 번째 이후 자식은 다음 행으로 내려가는 패턴.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const renderFirstChild = (row) =&amp;gt; {
  if (!row) return [];
  const children = row.children ?? [];
  const firstChild = children[0] ?? null;

  return [
    &amp;lt;td rowSpan={getRowSpan(row)} ...&amp;gt;{row.title}&amp;lt;/td&amp;gt;,
    ...renderFirstChild(firstChild),
    ...renderData(row),
  ];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;renderFirstChild&lt;/code&gt;는 자기 자신을 첫 번째 자식으로 재귀한다. 이러면 어느 깊이든 &lt;strong&gt;&amp;quot;맨 왼쪽 첫 row&amp;quot;&lt;/strong&gt; 가 자연스럽게 한 줄에 펼쳐진다. 그리고 나머지 자식들은 &lt;code&gt;renderRow&lt;/code&gt;가 &lt;code&gt;slice(1)&lt;/code&gt;로 떼서 다음 줄로 보낸다.&lt;/p&gt;
&lt;p&gt;처음엔 nested &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 만들어서 처리하려다가 HTML 스펙상 그게 안 돼서 &lt;strong&gt;&amp;quot;한 행에 들어갈 td들을 하나의 평면 배열로 만든 뒤 그 다음 행을 별도로 push&amp;quot;&lt;/strong&gt; 하는 패턴으로 바꿨다. 코드는 평범한 재귀로 보이지만, 결정적으로 &lt;code&gt;renderFirstChild&lt;/code&gt;와 &lt;code&gt;renderRow&lt;/code&gt;가 책임을 나눠 가지는 게 핵심 구조다.&lt;/p&gt;
&lt;h2&gt;8. 데이터 매핑 — &lt;code&gt;getGroupedData&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;트리는 완성됐고, 이제 셀 값을 채울 차례. &lt;code&gt;getGroupedData&lt;/code&gt;는 행 트리(&lt;code&gt;rows&lt;/code&gt;)를 leaf까지 내려가면서 각 leaf의 &lt;code&gt;items&lt;/code&gt;에서 값을 합산하고, 열 키(&lt;code&gt;columns&lt;/code&gt; 맵의 key)에 매칭되는 라인 아이템만 골라 셀 값을 만든다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;for (const colKey of columnKeys) {
  const colItems = columns[colKey];
  for (const valueKey of values) {
    const valueItems = colItems.filter(({ id }) =&amp;gt;
      (items ?? []).find((item) =&amp;gt; id === item.id),
    );
    const value = valueItems.reduce(/* sum */);
    row[colKey] = value;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;행 트리의 leaf &lt;code&gt;items&lt;/code&gt;와 열 트리의 leaf &lt;code&gt;colItems&lt;/code&gt;를 &lt;strong&gt;id 교집합&lt;/strong&gt;으로 묶고, 그 교집합 안에서 &lt;code&gt;values&lt;/code&gt;를 합산. 단순한데, 행/열 둘 다 트리이기 때문에 leaf만 보고 sum해도 부모 셀의 값은 비어 있는 게 아니라 — 그 자리에 자식 셀이 직접 들어가니까 — 자연스럽게 채워진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;col_total&lt;/code&gt; 같은 합계 컬럼은 루프 도중 누적해서 따로 채워 넣는다. 합계 컬럼 키는 transformToGridGroup이 미리 prefix로 표시해 둔 약속이 있어서 식별이 쉬웠다.&lt;/p&gt;
&lt;h2&gt;9. 모드 분기 — &lt;code&gt;useCreateTableV2&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;훅의 &lt;code&gt;getTableData&lt;/code&gt;는 결국 위의 조각들을 &lt;strong&gt;모드별로 갈아 끼우는 디스패처&lt;/strong&gt;다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (!colGroup.length &amp;amp;&amp;amp; !rowGroup.length &amp;amp;&amp;amp; !valueGroup.length) {
  return getBasicGridData(...);
}
if (!colGroup.length &amp;amp;&amp;amp; rowGroup.length &amp;amp;&amp;amp; valueGroup.length) {
  return getOnlyRowGroupGridData(...);
}
if (colGroup.length &amp;amp;&amp;amp; rowGroup.length &amp;amp;&amp;amp; valueGroup.length) {
  return getPivotGridData(...);
}
return { columns: [], rows: [], data: [], amountUnit };&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;분기 자체는 단순하지만, 각 함수가 &lt;strong&gt;같은 형태(columns/rows/data)&lt;/strong&gt; 를 반환하기 때문에 &lt;code&gt;&amp;lt;Grid /&amp;gt;&lt;/code&gt;는 어느 모드에서 왔는지 알 필요가 없다. 이게 컴포넌트를 단순하게 유지해 준 핵심 결정.&lt;/p&gt;
&lt;p&gt;처음엔 한 함수에서 분기 다 처리하려다가 if-else가 5단 깊이로 들어가는 걸 보고 모드별로 함수를 쪼갰다. 모드를 새로 추가할 일이 (당시엔) 명확히 보이지 않아서 인터페이스를 너무 일찍 추상화하지 않은 것도 의도된 선택.&lt;/p&gt;
&lt;h2&gt;10. 회고 — 잘한 것, 다음에 다르게 할 것&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;잘한 선택&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;트리 모델로 통일.&lt;/strong&gt; 행/열을 같은 &lt;code&gt;GridGroup&lt;/code&gt; 타입으로 묶은 게 rowSpan/colSpan 계산, 소계 삽입, 데이터 매핑 모두에서 일관성을 줬다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;헬퍼와 훅, 컴포넌트의 책임 분리.&lt;/strong&gt; 헬퍼는 순수 함수, 훅은 모드 디스패치, 컴포넌트는 트리→DOM 변환. 이 경계가 흐려졌으면 디버깅이 훨씬 어려웠을 것.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모드별 반환 형태 통일.&lt;/strong&gt; 어느 모드에서 왔든 &lt;code&gt;&amp;lt;Grid /&amp;gt;&lt;/code&gt;가 받는 입력 모양이 같아서 컴포넌트 자체는 모드를 알 필요가 없었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;다음에 다르게 할 것&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;키 prefix에 의미를 숨긴 것&lt;/strong&gt;&lt;br&gt;&lt;code&gt;_subtotal1&lt;/code&gt;, &lt;code&gt;col_total&lt;/code&gt;, &lt;code&gt;axis_semi_total&lt;/code&gt; 같은 prefix로 셀의 성격을 식별하다 보니, 렌더링 쪽에서 &lt;code&gt;key.includes(&amp;#39;subtotal&amp;#39;)&lt;/code&gt;, &lt;code&gt;key.includes(&amp;#39;total&amp;#39;)&lt;/code&gt; 같은 문자열 판별이 자라났다. 처음엔 빠르고 깔끔해 보였지만, &amp;quot;총계 행만 색을 다르게&amp;quot;처럼 정책이 늘어날 때마다 판별 위치를 또 추가하게 된다. &lt;code&gt;GridGroup&lt;/code&gt;에 &lt;code&gt;kind: &amp;#39;data&amp;#39; | &amp;#39;subtotal&amp;#39; | &amp;#39;semiTotal&amp;#39; | &amp;#39;total&amp;#39;&lt;/code&gt; 같은 명시적 필드를 둔 뒤 스타일·집계 분기를 그 위에서 하는 게 옳은 방향이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;getGroupedData의 비용&lt;/strong&gt;&lt;br&gt;지금 구조는 사실상 &lt;code&gt;rowLeaf × colLeaf × value × itemScan&lt;/code&gt;이 곱해진다. 라인 아이템이 수만 건 넘어가면 체감된다. 라인 아이템을 &lt;code&gt;id → item&lt;/code&gt; Map으로, 또는 그룹 leaf의 id 집합을 &lt;code&gt;Set&lt;/code&gt;으로 미리 만들어 두면 교집합 비용이 크게 줄어든다. O(n²) → O(n) 같은 단순 표현보다는 &amp;quot;id 인덱싱으로 교집합 비용을 줄인다&amp;quot;가 더 정확한 설명이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암묵 의존성&lt;/strong&gt;&lt;br&gt;&lt;code&gt;transformToGridGroup&lt;/code&gt;의 정렬은 입력 라인 아이템에 &lt;code&gt;RN&lt;/code&gt;(row number)이 박혀 있어야 동작하는데, 함수 시그니처에는 그게 드러나지 않는다. 다음에 손대는 사람이 분명히 헤맨다. 정렬 키는 명시적 인자로 빼야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;불안정한 React key&lt;/strong&gt;&lt;br&gt;셀 렌더링에서 &lt;code&gt;uuidv4()&lt;/code&gt;를 key로 쓰는 곳이 두 군데 있다. 매 렌더마다 새 key가 생기면 React 입장에선 매번 다른 노드다. division/colKey 조합 같은 안정 키로 바꾸는 게 맞다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 매트릭스 부족&lt;/strong&gt;&lt;br&gt;단위 테스트(&lt;code&gt;src/__tests__/unit.test.ts&lt;/code&gt;) 자체는 있지만, V2 헬퍼와 렌더링 매트릭스 — 모드 3종 × 소계/합계/총계 on/off — 를 가로지르는 골든 테스트가 부족하다. 헬퍼 단위로 입력→출력 골든을 짜 두면 다음 손댈 때 안심이 다르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;11. 마치며&lt;/h2&gt;
&lt;p&gt;&amp;quot;피벗 테이블 비슷한 거&amp;quot;의 가벼움과 실제 구현의 무게 차이는 꽤 컸다. 다만 한 번 트리 모델로 정리해 두니, 새 옵션(예: 합계 행 색상 분리, 컬럼 정렬, 셀 포맷터 주입)을 붙일 때는 의외로 손댈 부분이 적었다.&lt;/p&gt;
&lt;p&gt;쓰면서 가장 또렷이 남은 교훈은 진부하지만 이거다. &lt;strong&gt;&amp;quot;렌더링을 잘 하려면 먼저 데이터를 잘 그려라.&amp;quot;&lt;/strong&gt; 트리가 정확하면 DOM은 따라온다. 트리가 어설프면 DOM에서 if문이 자란다.&lt;/p&gt;
&lt;p&gt;다음 글에선 이 컴포넌트 위에서 가상화/내보내기까지 붙이며 만난 문제를 정리해 볼 생각이다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;이 글의 코드는 단순화를 위해 일부 타입 캐스팅과 가드를 생략했다. 실제 구현은 &lt;a href=&quot;https://github.com/ekdldksp123/dynamic-table&quot;&gt;Github&lt;/a&gt; 참고.&lt;/em&gt;&lt;/p&gt;</description>
      <category>React</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/27</guid>
      <comments>https://vincode.tistory.com/27#entry27comment</comments>
      <pubDate>Sat, 30 May 2026 18:50:16 +0900</pubDate>
    </item>
    <item>
      <title>SSE 기반 LLM 응답 스트리밍 구현기</title>
      <link>https://vincode.tistory.com/26</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;LLM 이 뭘하고 있는지 사용자는 궁금해한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챗봇 기반 문서편집 AI 서비스를 개발하다 보면 LLM이 문서 및 자료를 분석하고, 사고 과정(thinking)을 거치고, 편집 명령을 생성하고, 최종 메시지를 작성하기까지 평균 10~30초가 걸린다. 그때 사용자가 원하는 건 단순한 로딩바가 아닌 &quot;지금 AI가 뭘 하고 있는지&quot; 아는 것이였다. 문서를 검색하는 중인지, 생각하는 중인지, 편집을 시작했는지. 그 과정이 실시간으로 보여야 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;폴링 vs WebSocket vs SSE 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폴링(Polling)&lt;/b&gt;: 가장 단순하지만, LLM 응답의 특성과 맞지 않았다. 토큰 단위로 생성되는 텍스트를 0.5초마다 긁어오면 불필요한 요청이 대량 발생하고, 실시간 느낌도 살릴 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt;: 양방향 통신이 가능하다는 장점이 있지만, 우리 유스케이스는 &quot;서버 &amp;rarr; 클라이언트&quot; 단방향이 대부분이었다. 사용자가 질문을 보내는 건 HTTP POST 한 번이면 충분하고, 이후엔 서버가 스트리밍으로 쏴주기만 하면 된다. WebSocket은 연결 유지 비용, 프록시 호환성, 재연결 로직까지 직접 관리해야 해서 오버엔지니어링이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSE(Server-Sent Events)&lt;/b&gt;: HTTP 기반 단방향 스트리밍. 브라우저&amp;nbsp;&lt;span&gt;EventSource&lt;/span&gt; API를 그대로 쓸 수 있고, 자동 재연결이 내장되어 있다. 요구사항에 맞아 보였다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 106px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;기준&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;Polling&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;WebSocket&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;단방향(요청-응답)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;양방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;단방향(서버&amp;rarr;클라이언트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;실시간성&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;높음&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;연결 비용&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;요청마다 새 연결&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;상시 연결 유지&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;단일 HTTP 스트림 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;재연결&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;직접 구현&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;직접 구현&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;브라우저 내장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;LLM 스트리밍 적합도&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;낮음&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;높음(과잉)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;b&gt;최적&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSE를 골랐지만, 하나로는 안 됐다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 SSE 하나로 끝날 줄 알았다. 그런데 LLM 요청은 POST body와 인증 헤더가 필요했고,&amp;nbsp;&lt;span&gt;EventSource&lt;/span&gt;는 GET만 지원했다. 결국 LLM 응답 스트림은&amp;nbsp;&lt;span&gt;fetch&lt;/span&gt;로 직접 읽고, 장시간 서버 푸시 이벤트만&amp;nbsp;&lt;span&gt;EventSource&lt;/span&gt;로 유지했다. 결과적으로 두 종류의 스트리밍 코드가 생겼지만, 각각의 제약을 생각하면 이 구성이 가장 단순했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;LLM 응답 스트리밍 - fetch + ReadableStream&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는&amp;nbsp;text/event-stream&amp;nbsp;형식으로 응답하고, 클라이언트는 그 스트림을&amp;nbsp;&lt;span&gt;fetch&lt;/span&gt;&amp;nbsp;+&amp;nbsp;&lt;span&gt;ReadableStream&lt;/span&gt;으로 직접 읽어 파싱한다. 전송 포맷은 SSE를 따르되, 소비 방식만&amp;nbsp;&lt;span&gt;fetch&lt;/span&gt;&amp;nbsp;기반으로 가져간 셈이다.&lt;/p&gt;
&lt;pre id=&quot;code_1779188836512&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Main Process에서 LLM 서버와 SSE 스트리밍

const res = await fetch(`${this.baseUrl}/api/v2/answers`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${params.accessToken}`,
    Accept: 'text/event-stream',  // SSE 형식 요청
  },
  body: JSON.stringify(params),
  signal: params.abortSignal,
});


const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';

while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // 남은 버퍼 처리 후 종료
    if (buffer.trim()) yield* this.processBufferData(buffer.trim());
    yield { done: true };
    break;
  }

  const chunk = decoder.decode(value, { stream: true });
  buffer += chunk;

  // 줄 단위 분할 &amp;mdash; 마지막 줄은 불완전할 수 있으므로 버퍼에 보관
  const lines = buffer.split(/\r?\n/);
  buffer = lines.pop() || '';

  for (const line of lines) {
    yield* this.processLine(line);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은&amp;nbsp;&lt;b&gt;AsyncGenerator 패턴&lt;/b&gt;이다.&amp;nbsp;&lt;span&gt;yield*&lt;/span&gt;로 파싱된 결과를 하나씩 내보내면, 호출 측에서&amp;nbsp;&lt;span&gt;for await...of&lt;/span&gt;로 자연스럽게 소비할 수 있다. 버퍼링 로직도 간결해진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버 푸시 이벤트 - EventSource&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 수집 명령 같은 서버 푸시 이벤트는&amp;nbsp;&lt;span&gt;EventSource&lt;/span&gt;를 그대로 사용했다. GET 기반이고 장시간 연결을 유지해야 하는 용도에는 여전히 적합하다.&lt;/p&gt;
&lt;pre id=&quot;code_1779189136772&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Renderer Process에서 서버 이벤트 수신

const es = new EventSource(`${baseUrl}/api/v2/events/stream?user_id=${userId}`);

es.addEventListener('open', () =&amp;gt; {
  reconnectAttempts = 0;
  lastMessageTime = Date.now();
  startHeartbeatCheck();

});

es.addEventListener('ping', () =&amp;gt; {
  lastMessageTime = Date.now();  // keep-alive 수신 시간 업데이트
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만&amp;nbsp;&lt;span&gt;EventSource&lt;/span&gt;의 내장 재연결만으로는 운영 환경에서 부족했다. 네트워크가 바뀌거나 백그라운드 전환 이후, 연결은 살아 있는 것처럼 보이지만 메시지가 오지 않는 half-open 상태가 생겼다. 프록시나 서버의 idle timeout으로 끊긴 연결을 브라우저가 즉시 감지하지 못하는 경우도 있었다. 결국&amp;nbsp;&lt;b&gt;지수 백오프 + 지터 기반 재연결&lt;/b&gt;,&amp;nbsp;&lt;b&gt;하트비트 타임아웃 감지&lt;/b&gt;,&amp;nbsp;&lt;b&gt;30분 주기 자가 복구&lt;/b&gt;&amp;nbsp;로직을 직접 얹었다.&lt;/p&gt;
&lt;pre id=&quot;code_1779189211979&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 지수 백오프: 1s &amp;rarr; 2s &amp;rarr; 4s &amp;rarr; 8s &amp;rarr; 16s + 랜덤 지터
function computeBackoffDelayMs(): number {
  const backoff = BASE_RECONNECT_DELAY_MS * 2 ** (reconnectAttempts - 1);
  const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
  return backoff + jitter;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;자동 재연결이 내장되어 있다&quot;고 써놓고 결국 직접 구현한 셈이니, 비교표의 SSE 장점이 실제로는 반쯤만 맞았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 디테일&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;1. 스트리밍 파서: 불완전한 청크 대응&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 응답은 네트워크 상황에 따라 청크가 잘리는 위치가 매번 달라진다. &lt;span&gt;`&lt;/span&gt;&lt;span&gt;\n\n---\n\n&lt;/span&gt;&lt;span&gt;`&lt;/span&gt; 구분자 중간에서 잘릴 수도 있고, JSON의 중괄호가 반만 올 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 파싱하는 대상은 표준 SSE의&amp;nbsp;&lt;span&gt;event: &lt;/span&gt;/ &lt;span&gt;data:&lt;/span&gt;&amp;nbsp;필드 자체라기보다, SSE transport 위에 얹은 애플리케이션 레벨의 델타 포맷이다. 서버는 thinking, retrieve, editDocument, message 같은 작업 단계를 텍스트 블록으로 내려주고, 클라이언트는 이를&amp;nbsp;&lt;span&gt;AgentDelta&lt;/span&gt;로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해&amp;nbsp;&lt;b&gt;상태형 파서(stateful parser)&lt;/b&gt;를 만들었다.&amp;nbsp;&lt;span&gt;createAgentResponseParser()&lt;/span&gt;는 내부에&amp;nbsp;&lt;span&gt;carry&lt;/span&gt;&amp;nbsp;버퍼를 유지하면서,&amp;nbsp;&lt;span&gt;push()&lt;/span&gt;로 새 텍스트가 들어올 때마다 완성된 블록만 파싱해서 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779189861405&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function createAgentResponseParser() {
  let carry = '';         // 미완성 블록 누적 버퍼
  let currentStep = '';   // thinking | message | response 상태 추적

  function push(textChunk: string): AgentDelta[] {
    // thinking 시작/종료 패턴 매칭
    if (textChunk.includes('op: thinking\ncontent: ')) {
      currentStep = 'thinking';
      return [{ op: 'thinking', status: 'start' }];
    }

    // thinking 중이면 time 패턴으로 종료 감지
    if (currentStep === 'thinking') {
      const timeMatch = textChunk.match(/\ntime: (\d+)s\n\n---\n\n/);
      
      if (timeMatch) {
        currentStep = 'response';
        return [{ op: 'thinking', status: 'end', time: timeMatch[1] }];
      }
      return [{ op: 'thinking', content: textChunk }];
    }

    // 블록 구분자로 분리, 마지막은 미완성이므로 보류
    carry += textChunk;
    const parts = carry.split(/\n\n+---\n\n+/);
    carry = parts.pop() ?? '';

    // 완성된 블록만 파싱
    return parts.map(parseBlockToDelta).filter(Boolean);
  }

  function flush(): AgentDelta[] { /* 잔여 버퍼 최종 처리 */ }

  return { push, flush };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파서는 op 종류가 늘어날 때마다&amp;nbsp;&lt;span&gt;if&lt;/span&gt;&amp;nbsp;분기가 하나씩 추가되는 구조라, 지금도 꽤 복잡하다. thinking, webSearch, retrieve, editDocument, message 각각 시작/종료 패턴이 다르고, 멀티라인 처리 규칙도 다르다. 파서와 UI 핸들러 양쪽을 동시에 수정해야 하는 점이 현재 가장 큰 유지보수 부담이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;2. 자동 스크롤: 단순하지만 까다로운 UX&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 중 자동 스크롤은 &lt;b&gt;&quot;바닥 근처에 있으면 따라가고, 위로 올라갔으면 멈추는&quot;&lt;/b&gt; 단순한 규칙이지만, 구현은 까다로웠다.&lt;/p&gt;
&lt;pre id=&quot;code_1779190012839&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const DYNAMIC_SCROLL_THRESHOLD = Math.round(dynamicMinHeight * 0.5);

useLayoutEffect(() =&amp;gt; {
  if (!isLastMessage || !messageRef.current) return;

  // 유저가 질문한 직후 &amp;rarr; 무조건 바닥으로
  if (isLoadingMessage) {
    scrollToBottom();
    return;
  }

  // 스트리밍 중 &amp;rarr; 바닥에서 threshold 이내일 때만 따라감
  const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
  if (distanceFromBottom &amp;lt;= DYNAMIC_SCROLL_THRESHOLD) {
    scrollToBottom();
  }
}, [isLastMessage, message.content, isLoadingMessage]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 응답의 경우 &lt;b&gt;메시지 중앙 고정&lt;/b&gt;도 필요했다. 사용자가 스트리밍을 따라가는 상태일 때만 적용하고, 위로 스크롤해 이전 내용을 읽기 시작하면 자동 이동을 멈춘다. &lt;span&gt;throttle(200ms)&lt;/span&gt;&amp;nbsp;+&amp;nbsp;&lt;span&gt;requestAnimationFrame&lt;/span&gt;으로 스트리밍 중인 메시지를 화면 중앙에 유지한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779190102813&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 스트리밍 메시지 화면 중앙 고정
const centerMessageById = useCallback((messageId: string) =&amp;gt; {
  const el = container.querySelector(`[data-message-id=&quot;${messageId}&quot;]`);
  const containerCenter = containerRect.top + containerRect.height / 2;
  const elCenter = elRect.top + elRect.height / 2;
  const delta = elCenter - containerCenter;

  container.scrollTop = Math.max(0, Math.min(
    container.scrollTop + delta,
    container.scrollHeight - container.clientHeight
  ));
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;threshold 값(&lt;span&gt;dynamicMinHeight * 0.5&lt;/span&gt;)은 휴리스틱으로 정했다. 여러 화면 크기에서 &quot;자연스럽다&quot;고 느끼는 값을 감으로 잡은 것이지, 사용자 행동 데이터로 검증한 건 아니다. 마지막 메시지의&amp;nbsp;&lt;span&gt;minHeight&lt;/span&gt;도 컨테이너 높이의 80%로 설정했는데, 짧은 응답에서 과도한 여백이 생기는 부작용이 있어서 작업 완료 후&amp;nbsp;&lt;span&gt;offsetHeight&lt;/span&gt;를 비교해 인라인 스타일을 제거하는 보정 로직까지 들어갔다. 깔끔한 해결이라고 하기는 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 응답 중간 끊김 대응: AbortController 관통&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 &quot;중지&quot; 버튼을 누르거나, 네트워크가 끊기거나, 페어링된 문서 창이 닫힐 때 스트리밍을 중단해야 한다. 이를 위해&amp;nbsp;&lt;span&gt;AbortController&lt;/span&gt;를 파이프라인 전체에 관통시켰다.&lt;/p&gt;
&lt;pre id=&quot;code_1779190219581&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[사용자 중지] &amp;rarr; eventBus.emit('STOP_AGENT')

  &amp;rarr; useChatMessages.stopAgent()

    &amp;rarr; abortRunningMessages()    // 모든 진행 중 메시지를 aborted 상태로

    &amp;rarr; stopAndResetAgent()       // AgentContext 상태 초기화

      &amp;rarr; stopAgent(runId)        // IPC &amp;rarr; Main Process

        &amp;rarr; orchestrator.abort()  // AbortController.abort()

          &amp;rarr; LlmClient stream 중단&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 메시지는&amp;nbsp;&lt;span&gt;start &amp;rarr; end | aborted | error&lt;/span&gt;&amp;nbsp;상태를 가지며, 중단 시 해당 시점의 모든 진행 중 메시지가&amp;nbsp;&lt;span&gt;aborted&lt;/span&gt;로 전환된다. 이렇게 하면 UI에서 &quot;AI가 여기까지 작업하다가 중단됨&quot;을 보여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 흐름이 한 번에 나온 건 아니다. 처음에는&amp;nbsp;&lt;span&gt;stopAndResetAgent&lt;/span&gt;를 직접 호출했는데, 그러면 메시지 상태가&amp;nbsp;&lt;span&gt;start&lt;/span&gt;인 채로 남아서 UI가 어중간한 상태에 빠졌다. 그래서&amp;nbsp;&lt;span&gt;abortRunningMessages&lt;/span&gt;를 먼저 호출하고, 그다음 Agent를 중단하는 순서로 바꿨다.&amp;nbsp;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 아키텍처: Electron IPC를 관통하는 이벤트 파이프라인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Electron 앱은 Main Process &amp;harr; Renderer Process 간 경계가 있다. Renderer에서 직접 LLM 스트림을 읽으면 파싱 로직이 UI 코드와 섞이니까, Main Process의&amp;nbsp;&lt;span&gt;AgentRuntimeOrchestrator&lt;/span&gt;가 스트리밍과 파싱을 담당하고 Renderer는 파싱된&amp;nbsp;&lt;span&gt;AgentDelta&lt;/span&gt;만 받아서 UI를 업데이트하도록 나눴다.&lt;/p&gt;
&lt;div id=&quot;code_1779190611279&quot; data-ke-type=&quot;html&quot; data-source=&quot;  &amp;lt;table style=&amp;quot;width:100%; border-collapse:collapse; font-family:monospace; font-size:14px; line-height:1.6;&amp;quot;&amp;gt;
    &amp;lt;thead&amp;gt;
      &amp;lt;tr style=&amp;quot;border-bottom:2px solid #333;&amp;quot;&amp;gt;
        &amp;lt;th style=&amp;quot;padding:8px 12px; text-align:left; width:33%;&amp;quot;&amp;gt;Renderer&amp;lt;/th&amp;gt;
        &amp;lt;th style=&amp;quot;padding:8px 12px; text-align:left; width:34%;&amp;quot;&amp;gt;Main Process&amp;lt;/th&amp;gt;
        &amp;lt;th style=&amp;quot;padding:8px 12px; text-align:left; width:33%;&amp;quot;&amp;gt;LLM Server&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;AichatForm&amp;lt;br/&amp;gt;&amp;rarr; &amp;lt;code&amp;gt;sendMessage()&amp;lt;/code&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; color:#888; text-align:center;&amp;quot;&amp;gt;&amp;darr; IPC&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;AgentRuntimeOrchestrator&amp;lt;br/&amp;gt;&amp;rarr; &amp;lt;code&amp;gt;LlmClient.stream()&amp;lt;/code&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;&amp;lt;code&amp;gt;/api/v2/answers&amp;lt;/code&amp;gt;&amp;lt;br/&amp;gt;(SSE)&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;AgentContextProvider&amp;lt;br/&amp;gt;&amp;larr; &amp;lt;code&amp;gt;subscribe(event)&amp;lt;/code&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; color:#888; text-align:center;&amp;quot;&amp;gt;&amp;darr; fetch + ReadableStream&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; color:#888; text-align:center;&amp;quot;&amp;gt;&amp;darr;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;SseParser&amp;lt;br/&amp;gt;&amp;rarr; &amp;lt;code&amp;gt;createAgentResponseParser()&amp;lt;/code&amp;gt;&amp;lt;br/&amp;gt;&amp;rarr; &amp;lt;code&amp;gt;push(chunk)&amp;lt;/code&amp;gt; &amp;rarr;
  &amp;lt;code&amp;gt;AgentDelta[]&amp;lt;/code&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;useChatMessages&amp;lt;br/&amp;gt;&amp;larr; &amp;lt;code&amp;gt;handleAgentEvent()&amp;lt;/code&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; color:#888; text-align:center;&amp;quot;&amp;gt;&amp;darr; emitToParent&amp;lt;br/&amp;gt;&amp;larr; IPC event&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; color:#888; text-align:center;&amp;quot;&amp;gt;&amp;darr;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px; vertical-align:top;&amp;quot;&amp;gt;ChatMessages&amp;lt;br/&amp;gt;(UI 렌더링)&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td style=&amp;quot;padding:6px 12px;&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
  &amp;lt;/table&amp;gt;&quot;&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; font-family: monospace; font-size: 14px; line-height: 1.6;&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;border-bottom: 2px solid #333;&quot;&gt;
&lt;th style=&quot;padding: 8px 12px; text-align: left; width: 33%;&quot;&gt;Renderer&lt;/th&gt;
&lt;th style=&quot;padding: 8px 12px; text-align: left; width: 34%;&quot;&gt;Main Process&lt;/th&gt;
&lt;th style=&quot;padding: 8px 12px; text-align: left; width: 33%;&quot;&gt;LLM Server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;AichatForm&lt;br /&gt;&amp;rarr; &lt;code&gt;sendMessage()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; color: #888; text-align: center;&quot;&gt;&amp;darr; IPC&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;AgentRuntimeOrchestrator&lt;br /&gt;&amp;rarr; &lt;code&gt;LlmClient.stream()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;&lt;code&gt;/api/v2/answers&lt;/code&gt;&lt;br /&gt;(SSE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;AgentContextProvider&lt;br /&gt;&amp;larr; &lt;code&gt;subscribe(event)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; color: #888; text-align: center;&quot;&gt;&amp;darr; fetch + ReadableStream&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; color: #888; text-align: center;&quot;&gt;&amp;darr;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;SseParser&lt;br /&gt;&amp;rarr; &lt;code&gt;createAgentResponseParser()&lt;/code&gt;&lt;br /&gt;&amp;rarr; &lt;code&gt;push(chunk)&lt;/code&gt; &amp;rarr; &lt;code&gt;AgentDelta[]&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;useChatMessages&lt;br /&gt;&amp;larr; &lt;code&gt;handleAgentEvent()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px; color: #888; text-align: center;&quot;&gt;&amp;darr; emitToParent&lt;br /&gt;&amp;larr; IPC event&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; color: #888; text-align: center;&quot;&gt;&amp;darr;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px 12px; vertical-align: top;&quot;&gt;ChatMessages&lt;br /&gt;(UI 렌더링)&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;padding: 6px 12px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도는 &quot;Renderer가 원시 스트림을 직접 만지지 않게 하자&quot;였다. 실제로 파싱 로직은 Main Process에 격리되어 있고, Renderer는&amp;nbsp;&lt;span&gt;AgentDelta&lt;/span&gt; 타입만 알면 된다. 하지만 완전한 분리는 아니었다. 렌더러에서 op 종류마다 분기하면서 메시지 상태를 직접 조작하고, &lt;span&gt;isWorking&lt;/span&gt;,&amp;nbsp;&lt;span&gt;isDocumentEditing&lt;/span&gt; 같은 상태를 별도로 관리한다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과: 첫 피드백 시간 1~2초&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 LLM 처리 시간은 동일하다. 줄어든 건 없다. 달라진 건 사용자가 메시지 전송 후 실시간으로 상태 변화를 볼수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;b&gt;Thinking 단계&lt;/b&gt;: &quot;AI가 생각 중입니다&quot;와 함께 사고 과정이 실시간으로 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;b&gt;검색/편집 단계&lt;/b&gt;: &quot;문서에서 검색 중...&quot;, &quot;문서 편집을 시작합니다...&quot; 등 단계별 피드백&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;b&gt;메시지 단계&lt;/b&gt;: 토큰 단위 스트리밍으로 타이핑 효과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;b&gt;중단 피드백&lt;/b&gt;: 즉각적인 상태 전환으로 &quot;내 요청이 반영됐다&quot;는 확신 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트리밍 적용 후에는 첫 피드백이 1~2초 안에 도착한다. LLM이 더 빨라진 게 아니라, 중간 과정을 보여줌으로써 기다리는 시간이 지켜보는 시간으로 바뀐 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;현재 &lt;/span&gt;한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;EventSource&lt;/span&gt;의 GET 제한으로 LLM 스트리밍에는 fetch 기반 SSE를 별도 구현해야 했다. SSE를 골랐지만 표준 API 하나로 커버되지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;상태형 파서의 복잡도가 높다. op 종류가 늘어날 때마다 파서와 UI 핸들러 양쪽을 수정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;자동 스크롤의 threshold 값이 휴리스틱 기반이다. 다양한 화면 크기와 콘텐츠 길이에서의 최적값을 아직 데이터로 검증하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/26</guid>
      <comments>https://vincode.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 19 May 2026 20:58:51 +0900</pubDate>
    </item>
    <item>
      <title>AI가 코드를 짜주는 시대, 왜 TDD가 더 중요해졌나</title>
      <link>https://vincode.tistory.com/25</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 코딩의 역설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 개발 현장에서 AI 코딩 도구를 안 쓰는 곳을 찾기 어렵다. GitHub Copilot, Claude, ChatGPT 등 AI에게 &quot;이런 기능 만들어줘&quot;라고 말하면 순식간에 코드가 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 예전에는 개발 공수 때문에 신중하게 결정했던 것들이 이제는 &quot;일단 만들어보고 바꾸죠&quot;가 되어버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는? 기획 변경이 잦아지고, UI/UX 수정이 늘어나고, 스펙이 수시로 바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 변경이 가능해진 건 좋다. 하지만&amp;nbsp;&lt;b&gt;빠른 변경 &amp;ne; 안전한 변경&lt;/b&gt;이다. AI가 아무리 빨리 코드를 짜줘도, 그 코드가 기존 기능을 망가뜨리지 않았는지는 인간이 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 TDD의 새로운 가치가 드러난다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SDD(Spec-Driven Development)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDD는 Spec-Driven Development, 즉&amp;nbsp;&lt;b&gt;스펙을 먼저 정의하고 그에 맞춰 개발&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;요구사항 분석 &amp;rarr; 스펙 문서화 &amp;rarr; 구현 &amp;rarr; 검증&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 협업할 때 SDD는 특히 효과적이다. AI는 명확한 지시를 받을수록 좋은 결과물을 낸다. &quot;적당히 예쁘게&quot;보다 &quot;너비 320px, 둥근 모서리 8px, 배경색 #F5F5F5&quot;라고 말해야 원하는 결과가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 많은 팀이 SDD를 도입하고 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. PRD(Product Requirements Document) 작성&lt;br /&gt;2. 기술 스펙 정의&lt;br /&gt;3. Acceptance Criteria(AC) 도출&lt;br /&gt;4. AI에게 스펙 기반으로 구현 요청&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 좋다. 문제는 그 다음이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;여기서 &lt;/span&gt;문제: 스펙이 바뀌면?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실에서 스펙은 바뀐다. 아니, AI 시대에는&amp;nbsp;&lt;b&gt;더 자주&lt;/b&gt;&amp;nbsp;바뀐다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전형적인 시나리오&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. PM이 기능 A의 스펙을 정의한다&lt;br /&gt;2. 개발자가 AI에게 스펙을 주고 구현을 요청한다&lt;br /&gt;3. AI가 뚝딱 코드를 만들어낸다&lt;br /&gt;4. 리뷰 중 PM이 말한다: &quot;여기 조금만 바꾸면 안 될까요?&quot;&lt;br /&gt;5. &quot;금방 될 것 같은데요?&quot; (AI가 빠르니까)&lt;br /&gt;6. 스펙 수정 &amp;rarr; AI에게 다시 요청 &amp;rarr; 새 코드 생성&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사이클이 반복되면서 문제가 커진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;AI는 전체 맥락을 기억하지 못한다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 &quot;기능 A를 이렇게 바꿔줘&quot;라고 요청하면, AI는 기능 A만 수정한다. 하지만 기능 A의 변경이 기능 B, C에 영향을 줄 수 있다는 건 AI가 알려주지 않는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;기능 A에서 사용하던 상태 구조가 바뀌면?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;기능 A를 호출하던 다른 컴포넌트는?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;기능 A의 API 응답 형태가 달라지면?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 영향 범위를 &lt;b&gt;인간이 직접 파악하고 검증&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;휴먼 에러의 함정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스펙 변경이 한두 번이면 괜찮다. 하지만 5번, 10번 반복되면 어떨까?&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- &quot;이 부분은 지난번에 확인했으니까 괜찮겠지&quot; &amp;rarr; 버그&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- &quot;여기는 안 건드렸으니까 문제없겠지&quot; &amp;rarr; 버그&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- &quot;시간 없으니까 핵심만 테스트하자&quot; &amp;rarr; 버그&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만든 코드는 빠르게 쌓이지만, 인간의 검증 능력은 그대로다. 이 간극에서 버그가 발생한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SDD 와 TDD의 결합: AC 기반 TDD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 TDD(Test-Driven Development)가 등장한다. TDD의 핵심은 간단하다:&amp;nbsp;&lt;b&gt;테스트를 먼저 작성하고, 테스트를 통과하는 코드를 작성한다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;테스트 작성(Red) &amp;rarr; 구현(Green) &amp;rarr; 리팩토링(Refactor) &amp;rarr; 반복&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 시대에 TDD가 중요해진 이유는, 테스트 코드가&amp;nbsp;&lt;b&gt;스펙의 실행 가능한 버전&lt;/b&gt;이 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Acceptance Criteria를 테스트로 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스펙 문서의 AC를 그대로 테스트 코드로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AC 예시:&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 사용자가 &quot;저장&quot; 버튼을 누르면 데이터가 저장된다&lt;br /&gt;- 저장 성공 시 &quot;저장되었습니다&quot; 토스트가 표시된다&lt;br /&gt;- 저장 실패 시 에러 메시지가 표시된다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770287063333&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe('저장 기능', () =&amp;gt; {

  it('저장 버튼 클릭 시 데이터가 저장된다', async () =&amp;gt; {

    const { getByText } = render(&amp;lt;SaveForm /&amp;gt;)
    fireEvent.press(getByText('저장'))

    await waitFor(() =&amp;gt; {
      expect(mockSaveAPI).toHaveBeenCalled()
    })
  })


  it('저장 성공 시 토스트가 표시된다', async () =&amp;gt; {

    mockSaveAPI.mockResolvedValue({ success: true })
    const { getByText } = render(&amp;lt;SaveForm /&amp;gt;)

    fireEvent.press(getByText('저장'))

    await waitFor(() =&amp;gt; {
      expect(getByText('저장되었습니다')).toBeTruthy()
    })
  })


  it('저장 실패 시 에러 메시지가 표시된다', async () =&amp;gt; {

    mockSaveAPI.mockRejectedValue(new Error('Network Error'))
    const { getByText } = render(&amp;lt;SaveForm /&amp;gt;)

    fireEvent.press(getByText('저장'))

    await waitFor(() =&amp;gt; {
      expect(getByText('저장에 실패했습니다')).toBeTruthy()
    })
  })

})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &quot;저장 기능의 스펙&quot;은 문서가 아니라&amp;nbsp;&lt;b&gt;실행 가능한 테스트&lt;/b&gt;로 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스펙 변경 시 일어나는 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획이 바뀌어서 AC가 수정되었다고 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;- 저장 성공 시 &quot;저장되었습니다&quot; 토스트가 표시된다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;+ 저장 성공 시 &quot;저장되었습니다&quot; 토스트가 표시되고, 목록 화면으로 이동한다&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 테스트 수정: 새로운 AC에 맞게 테스트 업데이트&lt;br /&gt;2. 테스트 실행: 당연히 실패 (Red)&lt;br /&gt;3. 구현 수정: AI에게 &quot;저장 후 목록으로 이동하도록 수정해줘&quot; 요청&lt;br /&gt;4. 테스트 통과 확인: 모든 테스트가 통과하면 (Green) 스펙을 만족&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 이거다:&amp;nbsp;&lt;b&gt;테스트가 실패하면, 뭔가 빠뜨린 거다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 수정했는데 테스트가 깨졌다면, 그건 AI가 기존 스펙을 위반했다는 신호다. 인간이 일일이 체크하지 않아도 테스트가 알려준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SDD + TDD 워크플로우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방법론을 결합하면 이런 흐름이 된다:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;스펙 정의 &amp;rarr; AC 도출 &amp;rarr; 테스트 작성(Red) &amp;rarr; 구현(Green) &amp;rarr; 리팩토링(Refactor)&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;uarr;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;│&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;└──────────────────────────────┘&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;(다음 AC로 반복)&lt;br /&gt;&lt;br /&gt;[스펙 변경 시]&lt;br /&gt;AC 수정 &amp;rarr; 테스트 수정 &amp;rarr; Red &amp;rarr; Green &amp;rarr; ...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 스펙 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 분석하고 기능 명세를 작성한다. PRD, 기술 스펙 문서 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. AC(Acceptance Criteria) 도출&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 기능이 완성되었다고 말할 수 있는 조건&quot;을 나열한다. JIRA 를 사용한다면 개발 티켓에 AC에 대한 내용이 들어간다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기능: 로그인&lt;br /&gt;AC:&lt;br /&gt;- 이메일과 비밀번호를 입력하고 로그인 버튼을 누르면 로그인된다&lt;br /&gt;- 이메일 형식이 올바르지 않으면 &quot;올바른 이메일을 입력하세요&quot; 메시지가 표시된다&lt;br /&gt;- 비밀번호가 틀리면 &quot;비밀번호가 일치하지 않습니다&quot; 메시지가 표시된다&lt;br /&gt;- 로그인 성공 시 홈 화면으로 이동한다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 테스트 작성 (Red)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC를 테스트 코드로 변환한다. 이 시점에서 구현은 없으니 테스트는 실패한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 구현 (Green)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 스펙과 실패하는 테스트를 주고 구현을 요청한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;gt; &quot;다음 테스트를 통과하는 로그인 컴포넌트를 만들어줘&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;AI가 만든 코드로 테스트가 통과하면 Green.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 리팩토링 (Refactor)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 통과하는 상태에서 코드 품질을 개선한다. 테스트가 보호막이 되어 리팩토링 중 실수해도 바로 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 스펙 변경 시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC가 바뀌면 테스트부터 수정한다. 테스트가 실패하는 걸 확인하고(Red), 구현을 수정해서 통과시킨다(Green).&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 TDD가 AI 시대의 가드레일인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. AI 출력물의 품질 검증 도구&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만든 코드가 &quot;돌아가는지&quot;는 AI도 확인할 수 있다. 하지만 &quot;요구사항을 만족하는지&quot;는 테스트가 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 스펙 변경의 영향 범위 즉시 파악&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스펙이 바뀌면 관련 테스트를 수정하고 실행한다. 실패하는 테스트가 곧 &quot;영향받는 기능 목록&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 회귀 버그 방지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 &quot;A 기능 수정해줘&quot;라고 했는데, AI가 실수로 B 기능을 건드렸다면? B 기능의 테스트가 실패하면서 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 인간이 검토할 범위 축소&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 통과하면 인간은 &quot;테스트가 커버하지 못하는 부분&quot;만 집중해서 검토하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. AI에게 명확한 성공 기준 제공&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 기능 만들어줘&quot;보다 &quot;이 테스트를 통과하는 코드 만들어줘&quot;가 더 명확한 지시다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론: AI 시대의 개발자는 무엇을 잘해야 될까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 AI가 코드를 개발자보다 전문적이고 빠르게 작성해주다 보니 나의 역할이 바뀌고 있는게 체감이 된다. 확실히 AI는 훌륭한&amp;nbsp;&lt;b&gt;실행자&lt;/b&gt;다. 하지만 &lt;b&gt;이게 맞는지&lt;/b&gt; 판단하고 그 가이드 라인을 상세하게 정해주고 매니징 하는건 여전히 인간의 몫이자 개발자들의 핵심역량이 될거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD는 이 판단을 도와주는 도구다. AC를 테스트로 작성해두면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 스펙 변경 시 영향 범위를 자동으로 파악할 수 있다&lt;br /&gt;- AI 출력물이 요구사항을 만족하는지 즉시 확인할 수 있다&lt;br /&gt;- 회귀 버그 없이 안전하게 변경을 적용할 수 있다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 빠르게 코드를 만들어줄수록,&amp;nbsp;&lt;b&gt;테스트라는 가드레일&lt;/b&gt;이 더 중요해진다. 속도와 안전을 동시에 잡으려면 SDD와 TDD를 함께 사용해야 한다. 빠르게 달리려면 브레이크가 좋아야 한다.&amp;nbsp;&lt;b&gt;TDD는 AI 시대의 브레이크다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>AI</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/25</guid>
      <comments>https://vincode.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 5 Feb 2026 19:36:53 +0900</pubDate>
    </item>
    <item>
      <title>EventBus에 대해서 알아야 할것들 (feat. 잘쓰면 넘나 좋다)</title>
      <link>https://vincode.tistory.com/24</link>
      <description>&lt;pre id=&quot;code_1768807312053&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ProductDetail.jsx
const handleAddToCart = async (product) =&amp;gt; {
  await cartAPI.add(product);
  eventBus.emit('cart:item-added', { product, timestamp: Date.now() });
};

// CartIcon.jsx
useEffect(() =&amp;gt; {
  const handleItemAdded = ({ product }) =&amp;gt; {
    showToast(`${product.name} 담김!`);
    // 또는 refetch cart count
  };
  
  eventBus.on('cart:item-added', handleItemAdded);
  return () =&amp;gt; eventBus.off('cart:item-added', handleItemAdded);
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하다 보면 &quot;컴포넌트 A에서 발생한 일을 컴포넌트 B가 알아야 하는데, 둘 사이에 직접적인 연결고리가 없다&quot;는 상황을 자주 마주친다. props를 타고 타고 올라갔다가 다시 내려오는 건 너무 번거롭고, 전역 상태관리를 도입하자니 배보다 배꼽이 큰 것 같고. 이럴 때 Event Bus가 매력적인 선택지로 떠오른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 솔직히 말하면, Event Bus는 양날의 검이다. 잘 쓰면 코드가 깔끔해지고 관심사가 분리되지만, 잘못 쓰면 디버깅 지옥에 빠지고 메모리 누수로 프로덕션에서 장애를 맞는다. 이 글에서는 Event Bus를 실무에서 어떻게 판단하고 사용해야 하는지, 그리고 흔히 저지르는 실수들을 어떻게 피할 수 있는지 이야기해보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Event Bus란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Bus는 Publish-Subscribe 패턴의 구현체다. 핵심 개념은 단순하다.&lt;/p&gt;
&lt;pre id=&quot;code_1768806938739&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 가장 기본적인 Event Bus 구조
class EventBus {
  constructor() {
    this.listeners = {};
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  off(event, callback) {
    if (!this.listeners[event]) return;
    this.listeners[event] = this.listeners[event].filter(cb =&amp;gt; cb !== callback);
  }

  emit(event, data) {
    if (!this.listeners[event]) return;
    this.listeners[event].forEach(callback =&amp;gt; callback(data));
  }
}

export const eventBus = new EventBus();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행자(Publisher)는 이벤트를 emit하고, 구독자(Subscriber)는 해당 이벤트를 on으로 구독한다. 발행자는 누가 듣고 있는지 모르고, 구독자는 누가 보냈는지 신경 쓰지 않는다. 이 &quot;서로 모른다&quot;는 특성이 Event Bus의 핵심이자, 동시에 가장 위험한 부분이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 Event Bus를 선택해야 하는가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선택지들을 먼저 정리하자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 간 통신 문제를 해결하는 방법은 여러 가지가 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;방법&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%;&quot;&gt;적합한 상황&lt;/td&gt;
&lt;td style=&quot;width: 28.7984%;&quot;&gt;주의점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Props Drilling&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%;&quot;&gt;depth가 2-3 이내&lt;/td&gt;
&lt;td style=&quot;width: 28.7984%;&quot;&gt;깊어지면 유지보수 헬파티&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Context API&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%;&quot;&gt;같은 트리 내에서 공유되는 상태&lt;/td&gt;
&lt;td style=&quot;width: 28.7984%;&quot;&gt;리렌더링 범위&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;상태관리 Library&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%;&quot;&gt;복잡한 전역 상태, 여러 곳에서 읽고 쓰는 데이터&lt;/td&gt;
&lt;td style=&quot;width: 28.7984%;&quot;&gt;보일러플레이트, &lt;br /&gt;학습곡선에 따른 진입장벽&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Event Bus&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%;&quot;&gt;서로 독립적인 모듈 간 일회성 통신&lt;/td&gt;
&lt;td style=&quot;width: 28.7984%;&quot;&gt;암묵적 의존성, 메모리 누수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;&quot;상태&quot;와 &quot;이벤트&quot;를 구분&lt;/b&gt;하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상태 vs 이벤트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;사용자가 로그인되어 있는가?&quot;는 &lt;b&gt;상태&lt;/b&gt;다. 여러 컴포넌트가 이 값을 계속 참조하고, 값이 바뀌면 UI가 반응해야 한다. 이건 Context나 상태관리 라이브러리가 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;사용자가 방금 로그인했다&quot;는 &lt;b&gt;이벤트&lt;/b&gt;다. 특정 시점에 발생한 일이고, 이 알림을 받아서 일회성 작업(토스트 표시, 애널리틱스 전송 등)을 수행하면 끝이다. 이런 경우 Event Bus가 깔끔하다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807259467&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이벤트성 통신 - Event Bus가 적합
eventBus.emit('user:login-success', { userId: '123' });

// 상태성 데이터 - 상태관리가 적합
// userStore.setUser({ id: '123', name: 'Kim' });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Event Bus가 유용한 순간들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 서로 모르는 컴포넌트 간 통신&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장바구니 아이콘은 헤더에 있고, &quot;장바구니에 담기&quot; 버튼은 상품 상세 페이지 깊숙한 곳에 있다. 이 둘은 컴포넌트 트리상 완전히 다른 위치에 있다. 버튼을 누르면 헤더의 장바구니 아이콘에 숫자가 바뀌어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807321008&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ProductDetail.jsx 
const handleAddToCart = async (product) =&amp;gt; {
  await cartAPI.add(product);
  eventBus.emit('cart:item-added', { product, timestamp: Date.now() });
};

// CartIcon.jsx
useEffect(() =&amp;gt; {
  const handleItemAdded = ({ product }) =&amp;gt; {
    showToast(`${product.name} 담김!`);
    // 또는 refetch cart count
  };
  
  eventBus.on('cart:item-added', handleItemAdded);
  return () =&amp;gt; eventBus.off('cart:item-added', handleItemAdded);
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이건 장바구니 상태를 전역으로 관리해도 되는 케이스다. 하지만 &quot;장바구니에 담겼다&quot;는 이벤트에 반응해서 토스트를 띄우거나, 애니메이션을 트리거하거나, 애널리틱스를 보내는 등의 &quot;일회성 사이드 이펙트&quot;가 여러 군데 있다면 Event Bus가 더 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 레거시 코드와의 브릿지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jQuery로 작성된 레거시 위젯과 새로 작성한 React 컴포넌트가 공존하는 상황. 이런 과도기에 Event Bus는 훌륭한 접착제 역할을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807346547&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 레거시 jQuery 위젯
$('#legacy-form').on('submit', function() {
  const formData = $(this).serialize();
  window.eventBus.emit('legacy:form-submitted', formData);
});

// 새로운 React 컴포넌트
useEffect(() =&amp;gt; {
  const handler = (formData) =&amp;gt; {
    // React 쪽에서 후처리
    trackAnalytics('form_submit', formData);
  };
  
  window.eventBus.on('legacy:form-submitted', handler);
  return () =&amp;gt; window.eventBus.off('legacy:form-submitted', handler);
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 마이크로 프론트엔드 환경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 팀이 개발한 독립적인 애플리케이션들이 한 페이지에서 동작할 때, Event Bus는 느슨한 결합을 유지하면서 통신할 수 있는 좋은 방법이다. 각 앱은 서로의 내부 구현을 전혀 모른 채 이벤트만 주고받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807365976&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Team A의 앱
eventBus.emit('teamA:user-selected', { userId: '123' });

// Team B의 앱 - Team A의 코드를 전혀 모름
eventBus.on('teamA:user-selected', ({ userId }) =&amp;gt; {
  loadUserDetails(userId);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 크로스 커팅 관심사 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 로깅, 애널리틱스, 토스트 알림 같은 횡단 관심사를 처리할 때 유용하다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807386263&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// API 호출
try {
  await api.call();
} catch (error) {
  eventBus.emit('error:api', { error, context: 'user-profile' });
}

// 에러 핸들러 
eventBus.on('error:api', ({ error, context }) =&amp;gt; {
  logToSentry(error, { context });
  showErrorToast(getErrorMessage(error));
});&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Event Bus가 독이 되는 순간&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 암묵적 의존성의 늪&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Bus의 가장 큰 문제는 &lt;b&gt;코드만 봐서는 의존 관계를 알 수 없다&lt;/b&gt;는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807445731&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleUpdate = () =&amp;gt; {
  eventBus.emit('user:updated', userData);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드만 보면 user:updated 이벤트를 누가 듣고 있는지 전혀 알 수 없다. IDE에서 &quot;Find References&quot;를 해도 문자열이라 잡히지 않는다. 시간이 지나면 이 이벤트를 구독하는 곳이 5군데, 10군데로 늘어나고, 어느 날 이벤트 이름을 바꾸거나 페이로드 구조를 변경하면 런타임에서야 버그가 터진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 디버깅 지옥&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그가 발생했을 때 콜스택을 따라가면 Event Bus의 emit에서 끊긴다. 누가 이 이벤트를 발생시켰는지, 어떤 핸들러가 문제인지 추적하려면 프로젝트 전체를 grep 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807468097&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error: Cannot read property 'name' of undefined
    at CartHandler.handleItemAdded (cart-handler.js:15)
    at EventBus.emit (event-bus.js:23)
    // 여기서 끊김. 누가 emit했는지 모름&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실행 순서 불확실성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 핸들러가 같은 이벤트를 구독할 때, 실행 순서를 보장하기 어렵다. 순서에 의존하는 로직이 있다면 잠재적 버그다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807557646&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 핸들러 A - 먼저 등록됨
eventBus.on('data:loaded', () =&amp;gt; {
  processData(); // 데이터 가공
});

// 핸들러 B - 나중에 등록됨
eventBus.on('data:loaded', () =&amp;gt; {
  renderChart(); // 가공된 데이터로 차트 그림
});

// B가 A보다 먼저 실행되면? 가공 안 된 데이터로 차트를 그림&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 상태와 이벤트의 혼동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트로 상태를 관리하려 하면 금방 카오스가 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807581676&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이러면 안 됨
eventBus.on('user:updated', (user) =&amp;gt; {
  this.currentUser = user; // 컴포넌트마다 각자 상태를 들고 있음
});

// 어느 컴포넌트의 currentUser가 최신인지 알 수 없음
// 동기화 문제 발생&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 누수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Bus를 처음 알게되었을때 레거시 프로젝트 환경에서 일하던 터라 구현에 급급해서 남발했던 때 있었다. 이때 메모리 누수 문제로 인한 서비스 장애를 겪었던 적이 있는데, 처음부터 이벤트 버스 때문일거라고는 생각치 못했다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807886065&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이렇게 쓰면 컴포넌트가 언마운트돼도 핸들러는 Event Bus에 계속 남아있음
useEffect(() =&amp;gt; {
  eventBus.on('notification:received', handleNotification);
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 마운트될 때 이벤트를 구독하고, 언마운트될 때 구독을 해제하지 않으면 핸들러가 계속 쌓인다. 10번 페이지 이동하면 같은 핸들러가 10개 등록되고, 이벤트가 발생하면 10번 실행된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;React 18 Strict Mode의 함정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18에서 Strict Mode를 켜면 개발 환경에서 컴포넌트가 두 번 마운트된다. 이게 cleanup 누락을 찾는 데 도움을 주려는 의도인데, Event Bus와 만나면 이상한 버그처럼 보인다.&lt;/p&gt;
&lt;pre id=&quot;code_1768807942296&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  console.log('구독!');
  eventBus.on('test', handler);
}, []);

// Strict Mode에서 콘솔:
// &quot;구독!&quot;
// &quot;구독!&quot;
// 핸들러가 2개 등록됨

// 정답: 항상 cleanup 해주기
useEffect(() =&amp;gt; {
  const handler = (data) =&amp;gt; {
    // ...
  };
  
  eventBus.on('notification:received', handler);
  
  return () =&amp;gt; {
    eventBus.off('notification:received', handler);
  };
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cleanup 함수에서 반드시 구독을 해제해야 한다. 여기서 주의할 점은 &lt;b&gt;같은 함수 참조&lt;/b&gt;를 off에 전달해야 한다는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 훅으로 실수 방지하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 cleanup을 신경 쓰는 건 귀찮고 실수하기 쉽다. 커스텀 훅으로 추상화하면 안전하다.&lt;/p&gt;
&lt;pre id=&quot;code_1768808062882&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useEventBus.js
import { useEffect, useRef } from 'react';
import { eventBus } from './eventBus';

export function useEventBus(event, handler) {
  // 핸들러의 최신 참조 유지
  const handlerRef = useRef(handler);
  handlerRef.current = handler;

  useEffect(() =&amp;gt; {
    const listener = (data) =&amp;gt; handlerRef.current(data);
    
    eventBus.on(event, listener);
    return () =&amp;gt; eventBus.off(event, listener);
  }, [event]);
}

// 사용하는 곳
function NotificationBadge() {
  const [count, setCount] = useState(0);
  
  useEventBus('notification:received', (data) =&amp;gt; {
    setCount(prev =&amp;gt; prev + 1);
  });
  
  return &amp;lt;Badge count={count} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handlerRef를 쓰는 이유는 핸들러 내부에서 최신 state나 props를 참조할 수 있게 하면서도, 이벤트 리스너 자체는 다시 등록하지 않기 위해서다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DevTools로 누수 확인하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 누수가 의심될 때 Chrome DevTools로 확인하는 방법:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Memory 탭 열기&lt;/li&gt;
&lt;li&gt;Take heap snapshot 클릭&lt;/li&gt;
&lt;li&gt;페이지 이동 몇 번 반복&lt;/li&gt;
&lt;li&gt;다시 스냅샷 찍기&lt;/li&gt;
&lt;li&gt;Comparison 뷰에서 늘어난 객체 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Bus 관련 누수라면 핸들러 함수들이 계속 늘어나는 게 보인다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입 안전한 Event Bus 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript를 쓴다면 Event Bus의 가장 큰 약점인 &quot;이벤트 이름 오타&quot;와 &quot;페이로드 타입 불일치&quot;를 컴파일 타임에 잡을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적인 타입 정의&lt;/h3&gt;
&lt;pre id=&quot;code_1768808118123&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// events.ts
export interface EventMap {
  'user:login': { userId: string; timestamp: number };
  'user:logout': void;
  'cart:item-added': { productId: string; quantity: number };
  'cart:cleared': void;
  'notification:received': { id: string; message: string; type: 'info' | 'error' };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 안전한 Event Bus 구현&lt;/p&gt;
&lt;pre id=&quot;code_1768808141231&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// typedEventBus.ts
import type { EventMap } from './events';

type EventKey = keyof EventMap;
type EventCallback&amp;lt;K extends EventKey&amp;gt; = EventMap[K] extends void
  ? () =&amp;gt; void
  : (data: EventMap[K]) =&amp;gt; void;

class TypedEventBus {
  private listeners: {
    [K in EventKey]?: Array&amp;lt;EventCallback&amp;lt;K&amp;gt;&amp;gt;;
  } = {};

  on&amp;lt;K extends EventKey&amp;gt;(event: K, callback: EventCallback&amp;lt;K&amp;gt;): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    (this.listeners[event] as Array&amp;lt;EventCallback&amp;lt;K&amp;gt;&amp;gt;).push(callback);
  }

  off&amp;lt;K extends EventKey&amp;gt;(event: K, callback: EventCallback&amp;lt;K&amp;gt;): void {
    const callbacks = this.listeners[event];
    if (!callbacks) return;
    
    this.listeners[event] = callbacks.filter(
      cb =&amp;gt; cb !== callback
    ) as typeof callbacks;
  }

  emit&amp;lt;K extends EventKey&amp;gt;(
    event: K,
    ...args: EventMap[K] extends void ? [] : [EventMap[K]]
  ): void {
    const callbacks = this.listeners[event];
    if (!callbacks) return;
    
    callbacks.forEach(callback =&amp;gt; {
      (callback as Function)(...args);
    });
  }
}

export const eventBus = new TypedEventBus();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅도 타입 안전하게&lt;/p&gt;
&lt;pre id=&quot;code_1768808171228&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useEventBus.ts
import { useEffect, useRef } from 'react';
import { eventBus } from './typedEventBus';
import type { EventMap } from './events';

type EventKey = keyof EventMap;
type EventCallback&amp;lt;K extends EventKey&amp;gt; = EventMap[K] extends void
  ? () =&amp;gt; void
  : (data: EventMap[K]) =&amp;gt; void;

export function useEventBus&amp;lt;K extends EventKey&amp;gt;(
  event: K,
  handler: EventCallback&amp;lt;K&amp;gt;
): void {
  const handlerRef = useRef(handler);
  handlerRef.current = handler;

  useEffect(() =&amp;gt; {
    const listener = ((data: EventMap[K]) =&amp;gt; {
      (handlerRef.current as Function)(data);
    }) as EventCallback&amp;lt;K&amp;gt;;
    
    eventBus.on(event, listener);
    return () =&amp;gt; eventBus.off(event, listener);
  }, [event]);
}

// 사용
useEventBus('cart:item-added', (data) =&amp;gt; {
  // data는 { productId: string; quantity: number } 타입으로 추론됨
  console.log(`상품 ${data.productId} ${data.quantity}개 추가됨`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디버깅을 위한 미들웨어 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Bus의 디버깅 문제를 완화하려면 모든 이벤트를 로깅하는 미들웨어를 추가하는 게 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1768808205933&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class EventBusWithMiddleware {
  private listeners = new Map&amp;lt;string, Set&amp;lt;Function&amp;gt;&amp;gt;();
  private middlewares: Array&amp;lt;(event: string, data: any) =&amp;gt; void&amp;gt; = [];

  use(middleware: (event: string, data: any) =&amp;gt; void) {
    this.middlewares.push(middleware);
  }

  emit(event: string, data?: any) {
    // 미들웨어 실행
    this.middlewares.forEach(mw =&amp;gt; mw(event, data));
    
    // 핸들러 실행
    const handlers = this.listeners.get(event);
    if (handlers) {
      handlers.forEach(handler =&amp;gt; handler(data));
    }
  }
  
  // ...
}

// 로깅 미들웨어
eventBus.use((event, data) =&amp;gt; {
  console.group(`  Event: ${event}`);
  console.log('Data:', data);
  console.log('Time:', new Date().toISOString());
  console.trace('Emitted from:');
  console.groupEnd();
});

// 개발 환경에서만 이벤트 히스토리 저장
if (process.env.NODE_ENV === 'development') {
  const eventHistory: Array&amp;lt;{ event: string; data: any; time: number }&amp;gt; = [];
  
  eventBus.use((event, data) =&amp;gt; {
    eventHistory.push({ event, data, time: Date.now() });
    
    // 전역에 노출해서 콘솔에서 확인 가능하게
    (window as any).__EVENT_HISTORY__ = eventHistory;
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 브라우저 콘솔에서 __EVENT_HISTORY__를 입력해 지금까지 발생한 모든 이벤트를 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 가이드라인 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Event Bus 도입 전 체크리스트&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이게 정말 &quot;이벤트&quot;인가, &quot;상태&quot;인가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일회성 알림이면 Event Bus&lt;/li&gt;
&lt;li&gt;지속적으로 참조되는 데이터면 상태관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;직접적인 연결이 정말 불가능한가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;props 2-3단계면 그냥 drilling&lt;/li&gt;
&lt;li&gt;같은 트리 안이면 Context 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구독자가 몇 명이나 될 것 같은가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1-2개면 그냥 직접 호출이 나을 수 있음&lt;/li&gt;
&lt;li&gt;여러 군데서 반응해야 하면 Event Bus 가치 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;나중에 유지보수할 사람이 이해할 수 있는가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 흐름이 복잡해지면 문서화 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Bus는 분명 유용한 패턴이지만, &quot;쓰기 쉬운 만큼 오용하기도 쉬운&quot; 양날의 검이다. 핵심은 적절한 상황 판단과 안전한 사용 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기억할 것들:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태와 이벤트를 구분하자&lt;/li&gt;
&lt;li&gt;cleanup은 선택이 아닌 필수다&lt;/li&gt;
&lt;li&gt;타입으로 안전성을 확보하자&lt;/li&gt;
&lt;li&gt;디버깅을 위한 로깅을 고려하자&lt;/li&gt;
&lt;li&gt;남용하지 말자. Event Bus가 5개 이상 쓰이고 있다면 아키텍처를 다시 생각해볼 때다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발에서 &quot;이 기술을 쓸 줄 안다&quot;보다 중요한 건 &quot;언제 쓰고 언제 안 쓰는지 판단할 수 있다&quot;는 것이다. Event Bus도 마찬가지다. 망치를 들면 모든 게 못으로 보인다고 하던가. Event Bus라는 망치를 들었을 때, 진짜 못인지 한 번 더 생각해보자.&lt;/p&gt;</description>
      <category>React</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/24</guid>
      <comments>https://vincode.tistory.com/24#entry24comment</comments>
      <pubDate>Mon, 19 Jan 2026 16:39:40 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드가 복잡해질수록 먼저 무너지는 것: 상태 관리</title>
      <link>https://vincode.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 프로젝트가 일정 규모를 넘기 시작하면, 기능보다 먼저 문제가 되는 것은 &lt;b&gt;상태(state)&lt;/b&gt;다. UI 버그, 불필요한 리렌더링, 어디서 바뀌는지 알 수 없는 값들. 대부분 &amp;ldquo;상태를 어디서, 어떻게 관리할지&amp;rdquo;에 대한 기준 부재에서 시작된다 &lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;534&quot; data-start=&quot;428&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 실제로 자주 마주치는 문제 상황을 기준으로&lt;br /&gt;&lt;b&gt;어떤 상태를 로컬로 두고, 언제 전역으로 끌어올리며, Context와 Recoil은 어디서 갈라지는지, URLSearchParams 은 언제 사용하면 좋은지&lt;/b&gt;를 단계적으로 정리한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;576&quot; data-start=&quot;541&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황 1: &amp;ldquo;분명 값은 바뀌었는데 UI가 안 바뀐다&amp;rdquo;&lt;/h2&gt;
&lt;h3 data-end=&quot;584&quot; data-start=&quot;578&quot; data-ke-size=&quot;size23&quot;&gt;증상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;632&quot; data-start=&quot;585&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;597&quot; data-start=&quot;585&quot;&gt;API 응답은 정상&lt;/li&gt;
&lt;li data-end=&quot;612&quot; data-start=&quot;598&quot;&gt;콘솔 로그에는 최신 값&lt;/li&gt;
&lt;li data-end=&quot;632&quot; data-start=&quot;613&quot;&gt;그런데 화면은 이전 상태 그대로&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;643&quot; data-start=&quot;634&quot; data-ke-size=&quot;size23&quot;&gt;원인 분석&lt;/h3&gt;
&lt;p data-end=&quot;703&quot; data-start=&quot;644&quot; data-ke-size=&quot;size16&quot;&gt;React는 &lt;b&gt;상태 변화 &amp;rarr; 리렌더링&lt;/b&gt;이라는 단순한 규칙 위에서 동작한다. 문제는 다음 중 하나다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;800&quot; data-start=&quot;705&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;730&quot; data-start=&quot;705&quot;&gt;상태가 &lt;b&gt;불변성을 깨고 직접 수정됨&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;731&quot;&gt;상태가 **잘못된 위치(너무 상위 or 너무 전역)**에 있음&lt;/li&gt;
&lt;li data-end=&quot;800&quot; data-start=&quot;769&quot;&gt;파생 가능한 값을 &lt;b&gt;중복 상태로 관리&lt;/b&gt;하고 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;825&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;특히 주니어 단계에서 가장 흔한 패턴은&lt;/p&gt;
&lt;blockquote data-end=&quot;844&quot; data-start=&quot;826&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;844&quot; data-start=&quot;828&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;일단 전역 상태로 올려두자&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;939&quot; data-start=&quot;846&quot; data-ke-size=&quot;size16&quot;&gt;하지만 모든 상태를 전역으로 두는 순간, 데이터 흐름은 흐려지고 디버깅 비용은 급격히 증가한다&lt;/p&gt;
&lt;h2 data-end=&quot;982&quot; data-start=&quot;946&quot; data-ke-size=&quot;size26&quot;&gt;해결 1: 상태를 먼저 분류한다 (관리 도구를 고르기 전에)&lt;/h2&gt;
&lt;p data-end=&quot;1020&quot; data-start=&quot;984&quot; data-ke-size=&quot;size16&quot;&gt;상태 관리 도구 선택보다 중요한 것은 &lt;b&gt;상태의 성격 분리&lt;/b&gt;다.&lt;/p&gt;
&lt;h3 data-end=&quot;1037&quot; data-start=&quot;1022&quot; data-ke-size=&quot;size23&quot;&gt;실무 기준 상태 분류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1171&quot; data-start=&quot;1038&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1064&quot; data-start=&quot;1038&quot;&gt;&lt;b&gt;로컬 상태&lt;/b&gt;: 단일 컴포넌트 UI 제어&lt;/li&gt;
&lt;li data-end=&quot;1094&quot; data-start=&quot;1065&quot;&gt;&lt;b&gt;전역 상태&lt;/b&gt;: 여러 컴포넌트에서 동시에 필요&lt;/li&gt;
&lt;li data-end=&quot;1125&quot; data-start=&quot;1095&quot;&gt;&lt;b&gt;서버 상태&lt;/b&gt;: API 기반, 캐싱/동기화 대상&lt;/li&gt;
&lt;li data-end=&quot;1150&quot; data-start=&quot;1126&quot;&gt;&lt;b&gt;URL 상태&lt;/b&gt;: 라우팅과 직접 연결&lt;/li&gt;
&lt;li data-end=&quot;1171&quot; data-start=&quot;1151&quot;&gt;&lt;b&gt;폼 상태&lt;/b&gt;: 입력&amp;middot;검증 중심&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1251&quot; data-start=&quot;1173&quot; data-ke-size=&quot;size16&quot;&gt;이 기준만 명확해져도 &amp;ldquo;왜 여기서 이 상태를 쓰는가&amp;rdquo;가 설명 가능해진다&lt;/p&gt;
&lt;p data-end=&quot;1251&quot; data-start=&quot;1173&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1297&quot; data-start=&quot;1258&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1297&quot; data-start=&quot;1258&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황 2: props drilling이 지옥이 되기 시작한다&lt;/h2&gt;
&lt;h3 data-end=&quot;1305&quot; data-start=&quot;1299&quot; data-ke-size=&quot;size23&quot;&gt;증상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1381&quot; data-start=&quot;1306&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1336&quot; data-start=&quot;1306&quot;&gt;상위 &amp;rarr; 하위 &amp;rarr; 손자 컴포넌트까지 props 전달&lt;/li&gt;
&lt;li data-end=&quot;1362&quot; data-start=&quot;1337&quot;&gt;중간 컴포넌트는 값도 안 쓰는데 전달만 함&lt;/li&gt;
&lt;li data-end=&quot;1381&quot; data-start=&quot;1363&quot;&gt;구조 변경 시 연쇄 수정 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1405&quot; data-start=&quot;1383&quot; data-ke-size=&quot;size23&quot;&gt;1차 해결: Context API&lt;/h3&gt;
&lt;p data-end=&quot;1498&quot; data-start=&quot;1407&quot; data-ke-size=&quot;size16&quot;&gt;Context는 &lt;b&gt;설정 비용이 거의 없고&lt;/b&gt;, 작은 범위의 상태 공유에는 여전히 유효하다 &lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1510&quot; data-start=&quot;1500&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Context API가 적합한 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1552&quot; data-start=&quot;1511&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1522&quot; data-start=&quot;1511&quot;&gt;테마, 인증 정보&lt;/li&gt;
&lt;li data-end=&quot;1533&quot; data-start=&quot;1523&quot;&gt;변경 빈도 낮음&lt;/li&gt;
&lt;li data-end=&quot;1552&quot; data-start=&quot;1534&quot;&gt;Provider 범위가 명확함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1560&quot; data-start=&quot;1554&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한계&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1626&quot; data-start=&quot;1561&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1601&quot; data-start=&quot;1561&quot;&gt;Context value 변경 시 &lt;b&gt;구독 컴포넌트 전부 리렌더링&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1626&quot; data-start=&quot;1602&quot;&gt;상태 의존성이 커질수록 추적 난이도 상승&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1656&quot; data-start=&quot;1628&quot; data-ke-size=&quot;size16&quot;&gt;이 지점에서 프로젝트는 보통 다음 단계로 넘어간다.&lt;/p&gt;
&lt;p data-end=&quot;1656&quot; data-start=&quot;1628&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1700&quot; data-start=&quot;1663&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1700&quot; data-start=&quot;1663&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황 3: 전역 상태는 필요한데 Context로는 버겁다&lt;/h2&gt;
&lt;h3 data-end=&quot;1708&quot; data-start=&quot;1702&quot; data-ke-size=&quot;size23&quot;&gt;증상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1769&quot; data-start=&quot;1709&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1727&quot; data-start=&quot;1709&quot;&gt;Context가 점점 비대해짐&lt;/li&gt;
&lt;li data-end=&quot;1753&quot; data-start=&quot;1728&quot;&gt;selector 같은 파생 계산이 필요해짐&lt;/li&gt;
&lt;li data-end=&quot;1769&quot; data-start=&quot;1754&quot;&gt;렌더링 최적화가 어려워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1792&quot; data-start=&quot;1771&quot; data-ke-size=&quot;size23&quot;&gt;해결: Recoil 도입 포인트&lt;/h3&gt;
&lt;p data-end=&quot;1890&quot; data-start=&quot;1794&quot; data-ke-size=&quot;size16&quot;&gt;Recoil은 전역 상태를 &lt;b&gt;atom 단위로 쪼개고&lt;/b&gt;, 필요한 컴포넌트만 정확히 구독하게 만든다 &lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1910&quot; data-start=&quot;1892&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Recoil이 적합한 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1981&quot; data-start=&quot;1911&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1925&quot; data-start=&quot;1911&quot;&gt;상태 변경 빈도가 높다&lt;/li&gt;
&lt;li data-end=&quot;1937&quot; data-start=&quot;1926&quot;&gt;파생 상태가 많다&lt;/li&gt;
&lt;li data-end=&quot;1952&quot; data-start=&quot;1938&quot;&gt;성능 최적화가 중요하다&lt;/li&gt;
&lt;li data-end=&quot;1981&quot; data-start=&quot;1953&quot;&gt;props / Context 계층을 줄이고 싶다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1997&quot; data-start=&quot;1983&quot; data-ke-size=&quot;size16&quot;&gt;특히 selector는&lt;/p&gt;
&lt;blockquote data-end=&quot;2092&quot; data-start=&quot;1998&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;2092&quot; data-start=&quot;2000&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 값은 저 값으로부터 계산된다&amp;rdquo;&lt;br /&gt;라는 의존성을 코드 레벨에서 고정시킨다는 점에서 강력하다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;247&quot; data-start=&quot;220&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;247&quot; data-start=&quot;220&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황 4: 새로고침하면 상태가 날아간다&lt;/h2&gt;
&lt;h3 data-end=&quot;255&quot; data-start=&quot;249&quot; data-ke-size=&quot;size23&quot;&gt;증상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;360&quot; data-start=&quot;256&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;290&quot; data-start=&quot;256&quot;&gt;검색어를 입력하고 페이지를 이동했는데 새로고침 시 초기화됨&lt;/li&gt;
&lt;li data-end=&quot;328&quot; data-start=&quot;291&quot;&gt;페이지네이션 후 뒤로 가기를 누르면 페이지 정보가 유지되지 않음&lt;/li&gt;
&lt;li data-end=&quot;360&quot; data-start=&quot;329&quot;&gt;URL을 공유했지만 동일한 검색 결과가 재현되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;387&quot; data-start=&quot;362&quot; data-ke-size=&quot;size16&quot;&gt;이 상황에서 흔히 보이는 구현은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1767084867712&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [searchValue, setSearchValue] = useState(&quot;&quot;);
const [currentPage, setCurrentPage] = useState(1);
const [sort, setSort] = useState&amp;lt;&quot;recent&quot; | &quot;favorite&quot;&amp;gt;(&quot;recent&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 상태들이 모두 &amp;ldquo;쿼리로 표현되는 상태&amp;rdquo;라는 점이다.&lt;br /&gt;즉, 컴포넌트 내부 상태(useState)로 관리하는 순간부터 구조적으로 한계를 갖게 된다&lt;/p&gt;
&lt;h3 data-end=&quot;739&quot; data-start=&quot;709&quot; data-ke-size=&quot;size23&quot;&gt;원인: 쿼리 상태를 컴포넌트 메모리에 두고 있다&lt;/h3&gt;
&lt;p data-end=&quot;770&quot; data-start=&quot;741&quot; data-ke-size=&quot;size16&quot;&gt;useState로 관리된 상태는 다음 특성을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;843&quot; data-start=&quot;772&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;789&quot; data-start=&quot;772&quot;&gt;&lt;b&gt;페이지 내에서만 유효&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;806&quot; data-start=&quot;790&quot;&gt;&lt;b&gt;새로고침 시 초기화&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;826&quot; data-start=&quot;807&quot;&gt;&lt;b&gt;브라우저 히스토리와 무관&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;843&quot; data-start=&quot;827&quot;&gt;&lt;b&gt;URL 공유 불가능&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;869&quot; data-start=&quot;845&quot; data-ke-size=&quot;size16&quot;&gt;검색어, 페이지, 정렬 조건은 본질적으로&lt;/p&gt;
&lt;blockquote data-end=&quot;973&quot; data-start=&quot;870&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;973&quot; data-start=&quot;872&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;현재 화면을 설명하는 상태&amp;rdquo;&lt;br /&gt;이며, 이 상태는 브라우저 주소창(URL)에 존재해야 일관성이 유지된다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;1020&quot; data-start=&quot;980&quot; data-ke-size=&quot;size26&quot;&gt;해결: 검색 상태를 URL Query Parameter로 끌어올린다&lt;/h2&gt;
&lt;h3 data-end=&quot;1031&quot; data-start=&quot;1022&quot; data-ke-size=&quot;size23&quot;&gt;핵심 전환&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1125&quot; data-start=&quot;1032&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1055&quot; data-start=&quot;1032&quot;&gt;❌ 검색 상태를 useState로 관리&lt;/li&gt;
&lt;li data-end=&quot;1125&quot; data-start=&quot;1056&quot;&gt;✅ 검색 상태를 **URL query string을 단일 진실 소스(Single Source of Truth)**로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1130&quot; data-start=&quot;1127&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767084954380&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/items?page=3&amp;amp;category=health&amp;amp;sort=recent&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1202&quot; data-start=&quot;1179&quot; data-ke-size=&quot;size16&quot;&gt;이 URL 하나로 다음이 모두 가능해진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1266&quot; data-start=&quot;1204&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1222&quot; data-start=&quot;1204&quot;&gt;새로고침 후 동일한 상태 복원&lt;/li&gt;
&lt;li data-end=&quot;1245&quot; data-start=&quot;1223&quot;&gt;뒤로 가기 / 앞으로 가기 동작 보장&lt;/li&gt;
&lt;li data-end=&quot;1266&quot; data-start=&quot;1246&quot;&gt;URL 공유 시 동일한 결과 재현&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;1305&quot; data-start=&quot;1273&quot; data-ke-size=&quot;size26&quot;&gt;실무 패턴: 관련된 쿼리는 &amp;ldquo;하나의 훅&amp;rdquo;으로 관리한다&lt;/h2&gt;
&lt;p data-end=&quot;1408&quot; data-start=&quot;1307&quot; data-ke-size=&quot;size16&quot;&gt;중요한 포인트는 &lt;b&gt;쿼리마다 개별 훅을 만들지 않고, 하나의 통합 훅으로 관리&lt;/b&gt;했다는 점이다 &lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;1416&quot; data-start=&quot;1410&quot; data-ke-size=&quot;size23&quot;&gt;이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1497&quot; data-start=&quot;1417&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1443&quot; data-start=&quot;1417&quot;&gt;검색어 변경 &amp;rarr; 페이지는 보통 1로 초기화됨&lt;/li&gt;
&lt;li data-end=&quot;1473&quot; data-start=&quot;1444&quot;&gt;정렬 변경 &amp;rarr; 같은 검색어라도 결과 의미가 달라짐&lt;/li&gt;
&lt;li data-end=&quot;1497&quot; data-start=&quot;1474&quot;&gt;쿼리 간 &lt;b&gt;의존성이 명확하게 존재&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1545&quot; data-start=&quot;1499&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &amp;ldquo;독립 상태&amp;rdquo;가 아니라 &lt;b&gt;하나의 검색 상태 묶음&lt;/b&gt;으로 다루는 것이 맞다.&lt;/p&gt;
&lt;h4 data-end=&quot;1545&quot; data-start=&quot;1499&quot; data-ke-size=&quot;size20&quot;&gt;Next.js App Router 기준 예시 구조&lt;/h4&gt;
&lt;pre id=&quot;code_1767085129346&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function useProductQuery() {
  const router = useRouter();
  const params = useSearchParams();

  const query = useMemo(() =&amp;gt; {
    return {
      page: Number(params.get(&quot;page&quot;)) || 1,
      search: params.get(&quot;search&quot;) || &quot;&quot;,
      sort: (params.get(&quot;sort&quot;) as &quot;recent&quot; | &quot;favorite&quot;) || &quot;recent&quot;,
    };
  }, [params]);

  const updateQuery = useCallback(
    (updates) =&amp;gt; {
      const newParams = new URLSearchParams(params.toString());

      Object.entries(updates).forEach(([key, value]) =&amp;gt; {
        if (!value) newParams.delete(key);
        else newParams.set(key, String(value));
      });

      router.push(`?${newParams.toString()}`);
    },
    [params, router]
  );

  return {
    ...query,
    setPage: (page: number) =&amp;gt; updateQuery({ page }),
    setSearch: (search: string) =&amp;gt; updateQuery({ search, page: 1 }),
    setSort: (sort) =&amp;gt; updateQuery({ sort }),
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조의 핵심은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2571&quot; data-start=&quot;2499&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2525&quot; data-start=&quot;2499&quot;&gt;컴포넌트는 &lt;b&gt;상태를 &amp;ldquo;소유&amp;rdquo;하지 않는다&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2546&quot; data-start=&quot;2526&quot;&gt;URL을 읽어 UI를 그릴 뿐이다&lt;/li&gt;
&lt;li data-end=&quot;2571&quot; data-start=&quot;2547&quot;&gt;상태 변경 = URL 변경 = 네비게이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기준으로 두고 판단해보면 좋다.&lt;/p&gt;
&lt;h3 data-end=&quot;2699&quot; data-start=&quot;2688&quot; data-ke-size=&quot;size23&quot;&gt;전역 상태를 써야 할때&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2762&quot; data-start=&quot;2700&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2712&quot; data-start=&quot;2700&quot;&gt;새로고침 시 초기화&lt;/li&gt;
&lt;li data-end=&quot;2747&quot; data-start=&quot;2713&quot;&gt;검색처럼 &lt;b&gt;페이지 종속적인 상태&lt;/b&gt;에 비해 범위가 넓음&lt;/li&gt;
&lt;li data-end=&quot;2762&quot; data-start=&quot;2748&quot;&gt;라우팅과 상태가 분리됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2776&quot; data-start=&quot;2764&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;URL 상태를 써야 할때&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2846&quot; data-start=&quot;2777&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2786&quot; data-start=&quot;2777&quot;&gt;새로고침 안전&lt;/li&gt;
&lt;li data-end=&quot;2809&quot; data-start=&quot;2787&quot;&gt;뒤로 가기 / 앞으로 가기 자연스러움&lt;/li&gt;
&lt;li data-end=&quot;2821&quot; data-start=&quot;2810&quot;&gt;공유&amp;middot;북마크 가능&lt;/li&gt;
&lt;li data-end=&quot;2846&quot; data-start=&quot;2822&quot;&gt;서버 컴포넌트 / SSR과도 궁합이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2907&quot; data-start=&quot;2873&quot; data-ke-size=&quot;size16&quot;&gt;다음 질문에 &lt;b&gt;2개 이상 YES&lt;/b&gt;라면 URL 상태가 맞다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3006&quot; data-start=&quot;2909&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2931&quot; data-start=&quot;2909&quot;&gt;이 상태는 특정 페이지를 설명하는가?&lt;/li&gt;
&lt;li data-end=&quot;2952&quot; data-start=&quot;2932&quot;&gt;사용자가 뒤로 가기를 기대하는가?&lt;/li&gt;
&lt;li data-end=&quot;2980&quot; data-start=&quot;2953&quot;&gt;URL 공유 시 동일한 화면이 나와야 하는가?&lt;/li&gt;
&lt;li data-end=&quot;3006&quot; data-start=&quot;2981&quot;&gt;새로고침 이후에도 의미가 유지돼야 하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3043&quot; data-start=&quot;3008&quot; data-ke-size=&quot;size16&quot;&gt;검색, 필터, 정렬, 페이지네이션은 거의 항상 여기에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/23</guid>
      <comments>https://vincode.tistory.com/23#entry23comment</comments>
      <pubDate>Tue, 30 Dec 2025 18:01:58 +0900</pubDate>
    </item>
    <item>
      <title>Electron + React + TS + Python - Desktop application 윈도우 배포하기</title>
      <link>https://vincode.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;1. electron-builder 설치&lt;/p&gt;
&lt;pre id=&quot;code_1673856268525&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i -D electron-builder
yarn add -D electron-builder&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. package.json 에 build config 추가&lt;/p&gt;
&lt;pre id=&quot;code_1673856962621&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	.....,
    
    &quot;main&quot;: &quot;dist/main.js&quot;, // webpack 후 main 의 경로를 적어준다
    &quot;homepage&quot;: &quot;./&quot;,
    &quot;build&quot;: {
    &quot;appId&quot;: &quot;{your-app-id}&quot;, // 앱 아이디를 적어준다 exe 파일의 이름이 됨
    &quot;productName&quot;: &quot;{your-app-name}&quot;, //설치 되고 나서 표시되는 앱의 이름
    // 아웃풋 디렉토리를 설정해주지 않으면 dist 폴더로 지정된다. 
     //나같은 경우 webpack을 돌리면 dist 폴더가 생기기 때문에 따로 분리해줌
    &quot;directories&quot;: {
      &quot;output&quot;: &quot;./out/&quot;, 
    },
    //electron-builder 는 맥과 윈도우 모두 패키지 할수 있다
    &quot;mac&quot;: { 
      &quot;target&quot;: {
        &quot;target&quot;: &quot;dmg&quot;,
        &quot;arch&quot;: &quot;universal&quot;
      }
    },
    &quot;win&quot;: {
      &quot;target&quot;: {
        &quot;target&quot;: &quot;nsis&quot;,
        &quot;arch&quot;: [
          &quot;x64&quot;,
          &quot;ia32&quot;
        ]
      }
    },
    // 윈도우 용 nsis script 추가
    &quot;nsis&quot;: {
      &quot;oneClick&quot;: false,
      &quot;allowToChangeInstallationDirectory&quot;: true // 설치 경로 변경 가능
    },
    &quot;buildVersion&quot;: &quot;1&quot;,
    // 빌드하면 electron-react 프로젝트가 asar 파일로 패키징 되는데 이때 추가로 함께 빌드되어야 하는 리소스 경로를 여기 적어준다. 그럼 {appPath}/resources 에 고대로 담겨진다
    &quot;extraResources&quot;: [ 
      {
        &quot;from&quot;: &quot;scripts&quot;, // 리소스 경로
        &quot;to&quot;: &quot;scripts&quot; // 빌드 경로
      },
      {
        &quot;from&quot;: &quot;pyenv&quot;,
        &quot;to&quot;: &quot;pyenv&quot;
      }
    ]},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. package.json 에 build script 추가&lt;/p&gt;
&lt;pre id=&quot;code_1673857181899&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
    ...,
    &quot;build:win&quot;: &quot;cross-env NODE_ENV=production electron-builder build --win&quot;,
    &quot;build:mac&quot;: &quot;cross-env NODE_ENV=production electron-builder build --mac&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. 윈도우 패키징 결과물!!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ab3GC/btrWnnZWcFd/kTbZlDWlACad0sJKaQcR5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ab3GC/btrWnnZWcFd/kTbZlDWlACad0sJKaQcR5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ab3GC/btrWnnZWcFd/kTbZlDWlACad0sJKaQcR5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAb3GC%2FbtrWnnZWcFd%2FkTbZlDWlACad0sJKaQcR5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;253&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;눙물...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;진짜 삽질만 한 일주일 넘게 한거 같다ㅠㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아나콘다에서 띄워서 사용하던 파이썬 가상환경 같은 경우는 패키징 하기 정말 막막했는데 이 블로그 덕분에 광광 울면서 같이 성공적으로 패키징 할 수 있게 되었다. 궁금하신 분들은 참고해보시길!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a title=&quot;[Node, electron] 파이썬이 설치되지 않은 PC에서 실행가능하도록 배포하기&quot; href=&quot;https://velog.io/@ktaewon98/Node-electron-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%B4-%EC%84%A4%EC%B9%98%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%80-PC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B0%80%EB%8A%A5%ED%95%98%EB%8F%84%EB%A1%9D-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0feat.-python-shell-anaconda&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ktaewon98/Node-electron-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%B4-%EC%84%A4%EC%B9%98%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%80-PC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B0%80%EB%8A%A5%ED%95%98%EB%8F%84%EB%A1%9D-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0feat.-python-shell-anaconda&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Electron</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/22</guid>
      <comments>https://vincode.tistory.com/22#entry22comment</comments>
      <pubDate>Mon, 16 Jan 2023 17:25:59 +0900</pubDate>
    </item>
    <item>
      <title>DeadLock - (5)</title>
      <link>https://vincode.tistory.com/21</link>
      <description>&lt;h1 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1. DeadLock 이란&lt;/span&gt;&lt;/h1&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1) DeadLock 은 각자의 프로세스가 자원을 점유하고 있으면서 다른 프로세스가 점유한 자원을 기다리며 block 된 교착 상태를 말한다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2) Resource(자원)&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;하드웨어, 소프트웨어 등을 포함하는 개념 &lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;ex) I/O device, CPU cycle, memory space, semaphore 등&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; 프로세스가 자원을 사용하는 절차&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Request(요청) &amp;rarr; Allocate(할당) &amp;rarr; Use(사용) &amp;rarr; Release(방출)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;※ Resource-Allocation Gragh&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; (자원 할당 그래프)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 프로세스와 자원의 요청 및 할당 관계를 표시한 그래프다&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt; &lt;u&gt;순환 대기 조건을 발견하기 위한 목적으로 사용한다&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DdSqA/btrBx0KQpJX/xJF4PkbiFjaXCIjPFgbW4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DdSqA/btrBx0KQpJX/xJF4PkbiFjaXCIjPFgbW4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DdSqA/btrBx0KQpJX/xJF4PkbiFjaXCIjPFgbW4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDdSqA%2FbtrBx0KQpJX%2FxJF4PkbiFjaXCIjPFgbW4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;460&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2. DeadLock 의 발생 조건(=이유)&lt;/span&gt;&lt;/h1&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;1. Mutual Exclustion (상호 배제)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;항상 한 프로세스만이 하나의 자원을 사용할 수 있다 &amp;rarr; 자원 독점 현상 발생&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;2. Non-preemptive (비선점)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;프로세스는 자원을 스스로 반납할 분 강제로 빼앗기지 않는다&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;3. Hold and wait (보유 대기 or 점유 대기)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원을 가진 프로세스가 다른 자원을 기다릴 때 보유 자원을 놓지 않고 계속 가지고 있는다&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;4. Circular wait (순환 대기)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원을 기다리는 프로세스 간에 사이클이 형성되어야 한다&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;ex) 프로세스 번호 0 부터 n 까지 있을때&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5UF8K/btrBu5TCO2K/xCPeK5QsKt7IH82purnLIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5UF8K/btrBu5TCO2K/xCPeK5QsKt7IH82purnLIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5UF8K/btrBu5TCO2K/xCPeK5QsKt7IH82purnLIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5UF8K%2FbtrBu5TCO2K%2FxCPeK5QsKt7IH82purnLIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;597&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3. DeadLock 처리 방법&lt;/span&gt;&lt;/h1&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;대부분의 운영체제(Unix 계열)는 교착 상태 발생을 무시한다. 사용차가 처리하게 냅둠.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;-&amp;gt; 무시는 해결 방법이 아니라고 생각해서 목차에 넣지 않았다&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;실제로 처리하는 프로그램의 작성은 SW 개발자의 책임&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;이라고 한다...ㄷㄷ&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1. DeadLock Prevention (교착 상태 예방)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 교착 상태 발생 조건 중 1가지가 만족되지 않도록 하는 방법&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1) 상호 배제 예방 : 동시 접근하는 자원에 대한 상호배제를 무시. - &lt;/span&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;일반적으로 허용되지 않는 방법이다&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2) 비선점 예방&amp;nbsp; &lt;/span&gt;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;프로세스가 어떤 자원이 필요해서 요청 대기 상태에 빠지게 될 경우 이미 보유한 자원들을 해제해버리고 모든 필요한 자원을 얻을 수 있을 때 그 프로세스가 다시 시작되게 한다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;프로세스 대기줄에 끼어들어서 선점해버리기~&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&amp;rarr; &lt;u&gt;State를 쉽게 save하고 restore 할 수 있는 자원에서 주로 사용한다(CPU, memory)&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3) 보유 대기 예방 &lt;/span&gt;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;프로세스 시작 시 모든 필요한 자원을 할당받게 하는 방법 &lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원이 필요할 경우 보유 자원을 모두 놓고 다시 요청하는 방법 &lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;ex) 두 개의 critical section 을 하나의 critical section으로 묶어서 처리하는 방식을 이용하면 점유하여 대기하는 것을 방지할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&amp;rarr; 효율이 많이 떨어지기 때문에 좋은 해결 방법은 아니라고 한다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;4) 순환 대기 예방 : 프로그래밍에서 적용할 수 있는 현실적인 방법. 핵심은 lock을 걸어주는 타이밍을 잘 조정하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;ex) 모든 자원 유형에 할당 순서를 정하여 정해진 순서대로만 자원 할당&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt; 교착 상태 예방은 Starvation, 자원의 이용률 저하(Utilization 저하), 시스템의 성능 저하(throughput 감소) 등이 발생할 수 있다 = &lt;/span&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;그냥 비효율적이다&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2. DeadLock Avoidance (교착 상태 회피)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 자원 요청에 대한 부가적인 정보를 이용해서 deadlock 가능성이 없는지를 동적으로 조사해서 안전한 경우에만 자원을 할당. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 시스템 state 가 원래 state 로 돌아올 수 있는 경우에만 자원 할당 (unsafe state X)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt; 가장 단순하고 일반적인 모델은&lt;/span&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; 프로세스들이 필요로 하는 각 자원별 최대 사용량을 미리 선언하도록 하는 방법이다&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;▶ 안전 상태 (safe state) &lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;시스템 내의&lt;u&gt; 프로세스들에 대한 safe sequence 가 존재하는 상태&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;▶ 안전 순서열 (safe sequence)&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Pi (sequnce&amp;lt;P1 ... Pn&amp;gt;) 의 자원 요청이 &lt;/span&gt;&lt;span data-highlight=&quot;yellow&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;가용 자원 + 모든 Pj(j &amp;lt; i)의 점유중인 자원&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;에 의해 충족되는 순서열&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTZJoU/btrBxatmiyO/KU8sKE7DT24BFD4IvkZj00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTZJoU/btrBxatmiyO/KU8sKE7DT24BFD4IvkZj00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTZJoU/btrBxatmiyO/KU8sKE7DT24BFD4IvkZj00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTZJoU%2FbtrBxatmiyO%2FKU8sKE7DT24BFD4IvkZj00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;267&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;▷ 교착상태를 피하는 2가지 알고리즘&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원 할당 그래프 알고리즘 &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- &lt;/span&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원 타입 당 사용할 수 있는 &lt;u&gt;인스턴스가 하나&lt;/u&gt;일 경우 사용&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원 할당 그래프 알고리즘 에는 요청 화살표(request edge), 할당 화살표(assignment edge)에 더하여 클레임 화살표(claim edge)의 개념이 더 추가 된다. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;클레임 화살표는 프로세스가 미래 언젠가 자원을 요청 할 수도 있다는 것을 나타낸다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;이것을 이용해서 자원의 요청이 들어와도 프로세스들이 서로 보유한 자원을 고려해서 점유되어 있지 않은 상태더라도 할당을 바로 해주지 않고 대기시킨다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;**안전 상태에서 프로세스가 작업을 처리하기 위해 필요한 전체 자원의 수가 요청 시 추가 요구 되는 정보였다면, 이번에는 클레임 화살표가 추가 요구 되는 정보다.&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;i&gt;&lt;/i&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pkUkh/btrBrOYp3fb/6W2EaeesTuFGIQoAEEaO90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pkUkh/btrBrOYp3fb/6W2EaeesTuFGIQoAEEaO90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pkUkh/btrBrOYp3fb/6W2EaeesTuFGIQoAEEaO90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpkUkh%2FbtrBrOYp3fb%2F6W2EaeesTuFGIQoAEEaO90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;372&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;은행원 알고리즘&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&amp;nbsp; &amp;nbsp; ※ 은행원 알고리즘 구현을 위해 필요한 자료구조&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Available(가용 자원) : Available[자원 타입] 로 정의되는 1차원 배열.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;각 자원 타입마다 현재 사용 가능한 개수를 저장한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Max(최대 요구가능 자원) : Max[프로세스 , 자원 타입] 로 정의되는 2차원 배열.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;각 프로세스가 요구할 수 있는 최대 자원 개수를 나타낸다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Allocation (현재 할당 자원) : Allocation[프로세스, 자원 타입] 로 정의되는 2차원 배열.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;각 프로세스 별 현재 할당되어 있는 자원, 현재 할당되어 있는 자원의 개수를 나타낸다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Need (필요 자원) : Need[프로세스, 자원 타입] 로 정의되는 2차원 배열.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;각 프로세스 별 작업을 완료하기 위해 추가로 필요한 자원의 개수를 나타낸다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;**Available 이 Maximum 을 충족할 때 프로세스에 우선 순위를 주게 된다고 한다&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hz0sW/btrBvZTa9mM/zessOWCd45VJCaV5XwHIOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hz0sW/btrBvZTa9mM/zessOWCd45VJCaV5XwHIOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hz0sW/btrBvZTa9mM/zessOWCd45VJCaV5XwHIOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHz0sW%2FbtrBvZTa9mM%2FzessOWCd45VJCaV5XwHIOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;812&quot; height=&quot;330&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;i&gt;&lt;/i&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3. Deadllock Detection (교착 상태 검출)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- OS 가 프로세스의 작업을 관찰하면서 교착 상태 발생 여부를 계속 주시하는 방법이다. 가장 현실적인 교착상태 해결방법! &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;1) &lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원 타입 당 single instance 인 경우 =&amp;gt; 자원할당 그래프 알고리즘 &amp;amp; Wait-for 그래프(= 자원할당 그래프의 변형) 사용&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;i&gt;&lt;/i&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sIFFJ/btrBxaNGKnd/goU8BBk6qV5PLogl8XJtM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sIFFJ/btrBxaNGKnd/goU8BBk6qV5PLogl8XJtM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sIFFJ/btrBxaNGKnd/goU8BBk6qV5PLogl8XJtM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsIFFJ%2FbtrBxaNGKnd%2FgoU8BBk6qV5PLogl8XJtM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;480&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2) 자원 타입 당 multiple instance 인 경우 =&amp;gt; Banker's algorithm 과 유사한(= 더 낙관적인) 방법 활용&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3) 타임 아웃: 일정 시간 동안 작업이 진행되지 않는 프로세스를 교착상태가 발생한 것으로 간주하여 처리하는 방법&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;가벼운 교착 상태를 검출할 수 있다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;엉뚱한 프로세스가 강제 종료될 수 있다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;모든 시스템이 적용할 수 없다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;-&amp;gt; 하지만 데이터베이스와 운영체제에서 많이 선호한다 ex) Window, Oracle ..&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt; 데이터의 일관성이 깨지는 문제를 해결하기 위해서 체크 포인트와 롤백을 사용!&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;4. Deadllock Recovery (교착 상태 회복)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1) Process termination (프로세스 종료)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;교착상태를 일으킨 모든 프로세스를 동시에 종료한다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;교착상태가 해결될때 까지 교착 상태를 일으킨 프로세스 중 하나를 골라 순서대로 종료한다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2) Resource Preemption (자원 선점화)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;비용을 최소화할 victim 선정해서 자원 뺏기&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;safe state 를 rollback 하여 process를 restart&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- Starvation 문제&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;동일한 프로세스가 계속해서 victim으로 선정되는 경우&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;cost factor에 rollback 회수도 같이 고려&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>컴퓨터 공학/운영체제</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/21</guid>
      <comments>https://vincode.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 9 May 2022 08:35:50 +0900</pubDate>
    </item>
    <item>
      <title>프로세스 동기화 - (4)</title>
      <link>https://vincode.tistory.com/20</link>
      <description>&lt;h2 data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size26&quot;&gt;1. Race Condition 과 Critical Section 이란&lt;/h2&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Race Condition&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; = 경쟁 상태&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 두 개 이상의 프로세스가 공용 자원을 병행적으로 읽거나 쓰는 작업을 하는 상태&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;-&amp;gt; &lt;/span&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;공통 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 달라진다&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Critical Section&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; = Race Condition 이 발생할 수 있는 부분&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 프로그래밍 코드 상에서 공유 데이터를 접근하는 부분을 뜻한다. &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;여기에서 문제점들이 발생함.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;-&amp;gt;&amp;nbsp; 이 영역에 &lt;/span&gt;&lt;span&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;한번에 한 프로세스만&lt;/span&gt;&lt;/i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;들어올 수 있도록 해야함 = &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #fb5f2c;&quot;&gt;동기화의 필요성&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt; 모든 프로세스가 읽을 수 있는 &lt;u&gt;공유 변수(Synchronization variable)&lt;/u&gt;를 놓고 알고리즘을 설계하기 시작&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;**프로세스는 크게 4가지 구역으로 나뉜다&lt;/span&gt;&lt;/div&gt;
&lt;div data-codeblock=&quot;true&quot;&gt;
&lt;div data-plaintext=&quot;true&quot;&gt;
&lt;pre id=&quot;code_1652051187798&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;do {

1. entry section /* critical section 에 진입 요청을 하는 부분 */
2. critical section /* 허락받고 들어가는 임계 구역 */
3. exit section /* critical section 에서 퇴출되는 부분 */
4. remainder section /* 나머지 코드 부분 */

} while(true)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 일단 Critical Section 문제를 해결하려면 어떤 조건을 충족해야 되는 거니&lt;/h2&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;상호 배제(Mutual Exclusion)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;: Critical Section 에는 한번에&amp;nbsp; 하나의 프로세스만 들어와서 작업할수 있다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;진행(Progress)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; : Critical Section이 비어있는데 어 나 쓸래! 하는 프로세스가 있으면 바로 들어와서 쓸수 있도록 해줘야 한다&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;Bounded Waiting (한정 대기)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #fb5f2c;&quot;&gt; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;: Critical Section 에 들어가려고 대기표 끊어놨는데 내 앞으로 쓸수 있는 매직패스 티켓 수를 한정해 놓는 것! = 무한정 기다리게 하면 안됨&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Synchronization을 위해 나온 여러 가지 방법 들을 알아보쟈&lt;/h2&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1) 소프트웨어적 방법 - 초기&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;첫째, 둘째 - &lt;/span&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;시도는 좋았으나 fail&lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1379&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdLvfL/btrBzeaWK7g/wAJyX4F9bHXr8TkQafgCj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdLvfL/btrBzeaWK7g/wAJyX4F9bHXr8TkQafgCj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdLvfL/btrBzeaWK7g/wAJyX4F9bHXr8TkQafgCj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdLvfL%2FbtrBzeaWK7g%2FwAJyX4F9bHXr8TkQafgCj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1379&quot; height=&quot;457&quot; data-origin-width=&quot;1379&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 4 []&quot; data-en-clipboard=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;피터슨 알고리즘(Peterson's Algorithm)&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; &lt;/span&gt;= flag, turn 변수를 동시에 사용&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;-&lt;span data-highlight=&quot;yellow&quot;&gt;&lt;i&gt;Mutual Exclusion, Progress, Bounded waiting 모두 만족!!&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;▷ 단점 및 문제점&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어적인 방법으로 속도가 느리다&lt;/li&gt;
&lt;li&gt;프로세스가 두 개인 경우에만 적용이 가능&lt;/li&gt;
&lt;li&gt;자원을 많이 소모한다(Busy Waiting)&lt;/li&gt;
&lt;li&gt;현대&lt;span&gt; &lt;/span&gt;컴퓨터에서는&lt;span&gt; &lt;/span&gt;정상&lt;span&gt; &lt;/span&gt;작동하지&lt;span&gt; &lt;/span&gt;않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcXEe8/btrBrsg3EvM/nj3LH7280SY9l1VuktViuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcXEe8/btrBrsg3EvM/nj3LH7280SY9l1VuktViuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcXEe8/btrBrsg3EvM/nj3LH7280SY9l1VuktViuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcXEe8%2FbtrBrsg3EvM%2Fnj3LH7280SY9l1VuktViuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;355&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2) 하드웨어적 방법&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;엔지니어들은 초기 소프트웨어적인 방법으로 임계 영역 문제를 해결하는 것은 한계가 있다는 것을 깨닫고 이번에는 하드웨어적인 방법을 고안했다.&lt;/div&gt;
&lt;div&gt;&lt;u&gt;하드웨어적 방법은 원자적 실행 단위를 보장하기 때문에 코드의 순서가 바뀌거나 하는 일이 없어서 좀 더 간단하게 이 문제를 해결할 수 있었다.&lt;/u&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;▷TestAndSet()&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q4XHZ/btrBCrOcVS7/m1L22aDxe9dcr6bHYkiZsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q4XHZ/btrBCrOcVS7/m1L22aDxe9dcr6bHYkiZsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q4XHZ/btrBCrOcVS7/m1L22aDxe9dcr6bHYkiZsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq4XHZ%2FbtrBCrOcVS7%2Fm1L22aDxe9dcr6bHYkiZsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;615&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;**하지만 이 방식은 프로세스의 도착 순서와 무관하게 임계 영역에 진입할 프로세스가 정해졌기 때문에 Bounded Waiting 문제를 해결할 수 없었다.&lt;/div&gt;
&lt;div&gt;waiting array 를 추가하여 보완할 수 있었지만 복잡하고 어려워 소프트웨어 툴인 Mutex(&lt;b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;Mut&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;ual &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;Ex&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;clusion)&lt;/span&gt; 가 만들어졌다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3) Mutex Locks&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;한 프로세스에 의해 소유될 수 있는 key(object)를 기반으로 한 상호배제 기법이다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;key에 해당하는 객체, &lt;u&gt;lock을 소유한 프로세스만이 공유자원에 접근할 수 있다.&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;하지만 Mutex Lock 도 마찬가지로 원자적으로 수행되고 Busy Waiting 문제점을 개선하지 못했다.&lt;/div&gt;
&lt;div&gt;(lock을 얻지 못하여 Critical section에 들어가지 못하는 동안 acquire 함수 내에서 while문을 돌고 있어야 함 = &lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;Spin Lock&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm0SQH/btrBtdpF5Wv/FC3rrKXo4iW5UZlmhvxPT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm0SQH/btrBtdpF5Wv/FC3rrKXo4iW5UZlmhvxPT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm0SQH/btrBtdpF5Wv/FC3rrKXo4iW5UZlmhvxPT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm0SQH%2FbtrBtdpF5Wv%2FFC3rrKXo4iW5UZlmhvxPT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;969&quot; height=&quot;270&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3) Semaphore&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba66oR/btrBsGr5jeS/vkoYn3je2czPglrlKmzZO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba66oR/btrBsGr5jeS/vkoYn3je2czPglrlKmzZO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba66oR/btrBsGr5jeS/vkoYn3je2czPglrlKmzZO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba66oR%2FbtrBsGr5jeS%2FvkoYn3je2czPglrlKmzZO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;484&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원을 얻는 P 연산과 자원을 반납하는 V 연산 과정을 통해 이뤄지는 자료형이며 그 값은 공유 자원의 개수를 의미한다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Semaphore와 Mutex Lock 모두 busy waiting 이라는 단점을 발생할 수 있기 때문에 &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;세마포어를 구현할때 프로세스를 연결하기 위한 큐를 추가로 정의하게 된다.&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tkyNQ/btrBC97lNcm/vnNrYtWY3gYpCBiLmlu3f1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tkyNQ/btrBC97lNcm/vnNrYtWY3gYpCBiLmlu3f1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tkyNQ/btrBC97lNcm/vnNrYtWY3gYpCBiLmlu3f1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtkyNQ%2FbtrBC97lNcm%2FvnNrYtWY3gYpCBiLmlu3f1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;995&quot; height=&quot;597&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;**이런 구현 방식을 Block/Wake-up 이라고 하며 대기해야 할 프로세스에서 CPU를 뺏어서 수면 상태에 빠지게 한다고 &lt;/span&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;Sleep Lock&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;이라고도 한다&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;=&amp;gt;&amp;nbsp; &lt;u&gt;Block/Wake-up가 Spin Lock 보다 일반적으로 더 좋지만&lt;/u&gt;&lt;/span&gt;&lt;span data-highlight=&quot;yellow&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt; Critical Section 의 길이가 짧을 경우에는 과한 방식이 될수 있다&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EEK3B/btrBu7jAfRt/SPVTL5Z1BPQRbMKTEiIoz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EEK3B/btrBu7jAfRt/SPVTL5Z1BPQRbMKTEiIoz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EEK3B/btrBu7jAfRt/SPVTL5Z1BPQRbMKTEiIoz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEEK3B%2FbtrBu7jAfRt%2FSPVTL5Z1BPQRbMKTEiIoz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;327&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;▷ DeadLock과 Starvation&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;b&gt;DeadLock&lt;/b&gt; : 두 개 이상의 프로세스가 &lt;/span&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;서로 상대방의 작업이 끝나기만을 기다리며 다음 단계로 진행하지 못하는 상태&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5vQtM/btrBwukaXe9/FfwJPPjmU104dwPIa1Hju0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5vQtM/btrBwukaXe9/FfwJPPjmU104dwPIa1Hju0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5vQtM/btrBwukaXe9/FfwJPPjmU104dwPIa1Hju0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5vQtM%2FbtrBwukaXe9%2FFfwJPPjmU104dwPIa1Hju0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;280&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[해결방법]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예방&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;Mutual exclusion(상호 배제) 조건의 제거: critical section 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;No Preemption(비선점) 조건의 제거: 선점 가능 기법을 만들어줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;Hold and wait(점유 대기) 조건의 제거: 한 번에 모든 필요 자원 점유 및 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;Circular wait(순환 대기) 조건의 제거: 자원 유형에 따라 순서를 매김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회피&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자원 요청에 대한 부가정보를 이용해서 자원 할당이 deadlock으로부터 안전(safe)한지를 동적으로 조사해서 안전한 경우에만 할당하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;=&amp;gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;단순하고&lt;span&gt; &lt;/span&gt;일반적인&lt;span&gt; &lt;/span&gt;모델은&lt;span&gt; &lt;/span&gt;프로세스들이&lt;span&gt; &lt;/span&gt;필요로하는&lt;span&gt; &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;자원별&lt;span&gt; &lt;/span&gt;최대&lt;span&gt; &lt;/span&gt;사용량을&lt;span&gt; &lt;/span&gt;미리&lt;span&gt; &lt;/span&gt;선언하도록&lt;span&gt; &lt;/span&gt;하는&lt;span&gt; &lt;/span&gt;방법이다&lt;span&gt;.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Starvation&lt;/b&gt;: 특정 프로세스의 우선순위가 낮아서 자원을 계속 할당받지 못하는 상태&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjCjrd/btrBxatlPYb/povW6DZISqguhDBHA5OynK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjCjrd/btrBxatlPYb/povW6DZISqguhDBHA5OynK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjCjrd/btrBxatlPYb/povW6DZISqguhDBHA5OynK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjCjrd%2FbtrBxatlPYb%2FpovW6DZISqguhDBHA5OynK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;870&quot; height=&quot;527&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[해결방법]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 우선순위를 수시로 변경해서 각 프로세스가 높은 우선순위를 가질 기회주기&lt;/li&gt;
&lt;li&gt;오래 기다린 프로세스의 우선순위를 높여주기&lt;/li&gt;
&lt;li&gt;우선순위가&lt;span&gt; &lt;/span&gt;아닌&lt;span&gt; &lt;/span&gt;요청&lt;span&gt; &lt;/span&gt;순서대로&lt;span&gt; &lt;/span&gt;처리하는&lt;span&gt; FIFO &lt;/span&gt;기반의&lt;span&gt; &lt;/span&gt;요청&lt;span&gt; &lt;/span&gt;큐&lt;span&gt; &lt;/span&gt;사용&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>컴퓨터 공학/운영체제</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/20</guid>
      <comments>https://vincode.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 9 May 2022 08:20:14 +0900</pubDate>
    </item>
    <item>
      <title>프로세스 매니지먼트 - (3)</title>
      <link>https://vincode.tistory.com/19</link>
      <description>&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;1. 프로세스는 어떻게 생성되고 실행되니&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 프로세스는 &lt;/span&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;프로세스 생성 시스템 콜(&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;fork()&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span data-highlight=&quot;green&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;)을 통해 새로운 프로세스들을 생성&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;할 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;3 3 []&quot; data-en-clipboard=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모 프로세스 :&amp;nbsp; 다른 프로세스를 생성하는 프로세스&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자식 프로세스 : 다른 프로세스에 의해 생성된 프로세스&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;이때 생성된 새로운 프로세스의 주소 공간(Address Space)은 &lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;일반적으로 부모 프로세스를 카피해서 만들어지게 된다&lt;/span&gt;&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-pm-slice=&quot;3 3 [&amp;quot;ul&amp;quot;,{&amp;quot;style&amp;quot;:null,&amp;quot;backgroundColor&amp;quot;:null,&amp;quot;color&amp;quot;:null,&amp;quot;lineHeight&amp;quot;:null,&amp;quot;listStyleType&amp;quot;:null}]&quot; data-en-clipboard=&quot;true&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모의 공간을 그대로 복사 = 완전히 같은 복사본 (&lt;u&gt;효율성을 위해 몇 OS 에서는 자식이 부모의 주소 공간을 공유&lt;/u&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모 프로세스와는 다른 새로운 프로그램 - &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;exec() &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;(&lt;/span&gt;&lt;span data-highlight=&quot;yellow&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;덮어쓰기&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645095916882&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
	int pid;
    pid = fork(); ------------ 복제 일어남 --------------&amp;gt;
    
    if(pid == 0) {
    	
        printf(&quot;\n Hello, I am child!\n&quot;);
        execlp(&amp;ldquo;/bin/date&amp;rdquo;, &amp;ldquo;bin/date&amp;rdquo; , (char*) 0);
        
    } else if(pid &amp;gt; 0) {
    	
        wait();
        printf(&quot;\n Hello, I am parent!\n&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;1) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;fork()&lt;/span&gt;&lt;span&gt;에서 부모 프로세스는 자신을 복제해 자식 프로세스를 생성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;2) &lt;/span&gt;&lt;span&gt;자식 프로세스는 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;memory&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;와 &lt;/span&gt;&lt;span&gt;register, pc&lt;/span&gt;&lt;span&gt;등 전부 복사해 오기 때문에 &lt;/span&gt;&lt;span&gt;main &lt;/span&gt;&lt;span&gt;시작 절이 아닌&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pork(); &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;이후부터 수행 됨&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;3) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;부모 프로세스에서는 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pork()&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;수행 후 자식 프로세스 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pid&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;를 리턴 해 &lt;/span&gt;&lt;span&gt;pid&lt;/span&gt;&lt;span&gt;는 양수가 됨&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;4) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;자식 프로세스는 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pid&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;가 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;5) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;자식 프로세스는 &lt;/span&gt;&lt;span&gt;excelp()&lt;/span&gt;&lt;span&gt;을 만나면 자식 프로세스의 내용이 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;ldquo;bin/date&amp;rdquo;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;의 내용으로 변경 되고&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;다시 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;main &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;시작 절부터 함수가 호출된다&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;6) &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #2e75b5;&quot;&gt;&lt;span&gt;wait() : &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;부모 프로세스가 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;wait() &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;시스템 콜을 호출하면&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;커널은 자식 프로세스가 종료될 때 까지 부모 프로세스를 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sleep &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;시킨다&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;block) -&amp;gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;자식 프로세스가 종료 되면 커널은 부모 프로세스를 깨운다&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;상태&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;ex ) Linux&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;※ 주소 공간이란&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 69.7674%; height: 258px;&quot; border=&quot;1&quot; width=&quot;642px&quot; data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Process Address Space&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td rowspan=&quot;2&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Code Segment&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/td&gt;
&lt;td data-colwidth=&quot;454&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;프로그램의 코드가 저장되어있다.&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-colwidth=&quot;454&quot;&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;읽기만 가능하다.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td rowspan=&quot;2&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;Data Segment &lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td data-colwidth=&quot;454&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;전역 변수(global variables) 같은 데이터가 저장되어 있다.&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-colwidth=&quot;454&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;읽고 쓰기가 가능하다.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td rowspan=&quot;2&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;Stack Segment &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/td&gt;
&lt;td data-colwidth=&quot;454&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;함수(function) 나 지역 변수(local variables)가 저장되어 있다.&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-colwidth=&quot;454&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;읽고 쓰기가 가능하다.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;**프로세스의 부모 - 자식 관계는 트리 형태(계층 구조)를 가지게 된다&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (10).png&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzJ0yk/btrtzbflCcU/zXFqGbTbcjOKotK87QwUM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzJ0yk/btrtzbflCcU/zXFqGbTbcjOKotK87QwUM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzJ0yk/btrtzbflCcU/zXFqGbTbcjOKotK87QwUM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzJ0yk%2FbtrtzbflCcU%2FzXFqGbTbcjOKotK87QwUM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;374&quot; height=&quot;349&quot; data-filename=&quot;image (10).png&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (11).png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ChrOT/btrtDFfcSzV/XzQjkdI6axcA1mB5tArQkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ChrOT/btrtDFfcSzV/XzQjkdI6axcA1mB5tArQkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ChrOT/btrtDFfcSzV/XzQjkdI6axcA1mB5tArQkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FChrOT%2FbtrtDFfcSzV%2FXzQjkdI6axcA1mB5tArQkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;528&quot; data-filename=&quot;image (11).png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 3 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;▷ 프로세스의 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자원(CPU, Memory 등)의 공유&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- 생성된 프로세스는 운영체제로부터 직접 자원을 제공받거나 부모 프로세스의 자원의 일부를 한정적으로 사용할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;전혀 공유하지 않는 모델(*)&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;일부를 공유하는 모델 - &lt;u&gt;Linux&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모와 자식이 모든 자원을 공유하는 모델 (copy X)&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;▷ 프로세스의 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;실행 방식&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모 프로세스가 자식 프로세스들과 &lt;u&gt;병행되어 같이 실행&lt;/u&gt;된다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자식 프로세스들이 &lt;u&gt;모두 종료될 때까지 대기&lt;/u&gt;한다(*) -&lt;/span&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #1aa9b2;&quot;&gt;wait()&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;※ 프로세스 생성 및 실행 관련 System Call(Unix)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;/span&gt;
&lt;table style=&quot;border-collapse: collapse; width: 80.3488%; height: 128px;&quot; border=&quot;1&quot; width=&quot;943px&quot; data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td data-colwidth=&quot;192&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;fork()&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td data-colwidth=&quot;751&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;현재 프로세스에서 새 프로세스 하나를 생성한다&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-colwidth=&quot;192&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;exec()&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td data-colwidth=&quot;751&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;프로세스의 메모리 공간을 새 프로그램으로 교체한다(일반적으로 fork() 이후 호출된다)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;- exec() 시스템 콜을 포함한 기존의 프로그램을 지우고, 새 binary file을 메모리에 탑재한다.&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-colwidth=&quot;192&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;wait()&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td data-colwidth=&quot;751&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자식 프로세스가 종료될때까지 부모 프로세스를 대기시킨다&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;2. 프로세스의 종료&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;1 0 []&quot; data-en-clipboard=&quot;true&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;※&amp;nbsp; 프로세스 종료 관련 System Call(Unix)&lt;/span&gt;&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 77.093%; height: 208px;&quot; border=&quot;1&quot; width=&quot;939px&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.2326%;&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;exit()&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 89.5349%;&quot; data-colwidth=&quot;751&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;마지막 명령 수행 후 프로세스를 정상적으로 종료하면서, 사용한 리소스를 반납한다&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.2326%;&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;abort()&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 89.5349%;&quot; data-colwidth=&quot;751&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모 프로세스가 자식의 수행을 종료시키며,&amp;nbsp; 리소스를 정리하지 않는다.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자식 프로세스가 할당 자원의 한계치를 넘어서는 경우&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;자식 프로세스에게 할당된 task가 없거나 더 이상 필요하지 않은 경우&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;부모 프로세스가 종료되는 경우&lt;br /&gt;- &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;운영체제는 부모 프로세스가 종료하는 경우 자식이 더 이상 수행되도록 두지 않는다&lt;br /&gt;- 단계적인 종료 ( 손자/손녀 -&amp;gt; 자식 -&amp;gt; 부모)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.2326%;&quot; data-colwidth=&quot;188&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;assert()&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 89.5349%;&quot; data-colwidth=&quot;751&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;파라미터로 들어온 표현식이 거짓이면 abort()를 호출한다&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-en-clipboard=&quot;true&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5a5a5a;&quot;&gt;3. 프로세스 간 협력&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1) 독립적 프로세스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;- 프로세스는 각자의 주소 공간을 가지고 수행되므로 원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2) 협력 프로세스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;- 프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있음 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt; 3) 프로세스 간 협력 메커니즘 &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;process.JPG&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;535&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzb2zR/btrtzck7imW/MIVSb5NSkoCkfJdZKgG780/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzb2zR/btrtzck7imW/MIVSb5NSkoCkfJdZKgG780/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzb2zR/btrtzck7imW/MIVSb5NSkoCkfJdZKgG780/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzb2zR%2Fbtrtzck7imW%2FMIVSb5NSkoCkfJdZKgG780%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;535&quot; data-filename=&quot;process.JPG&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;535&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Message passing : Memory protection을 위해 커널을 통해 메시지 전달 &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp; &amp;nbsp; -&amp;gt; direct communication와 indirect communication 방식이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp; &amp;nbsp; =&amp;gt; 안전하고 동기화 걱정이 없다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Shared Memory : &lt;span&gt;&lt;span&gt;서로 다른 프로세스 간에도 일부 주소 공간을 공유하게 하는&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;메커니즘이 있다.&lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;gt; &lt;span&gt;&lt;span&gt;shared memory &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;최초 사용시 &lt;/span&gt;&lt;span&gt;system call&lt;/span&gt;&lt;span&gt;을 통해 커널에게 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;shared memory&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;를 구성하게 해야 함&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;=&amp;gt; 성능이 좋지만 동기화 문제가 발생한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>컴퓨터 공학/운영체제</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/19</guid>
      <comments>https://vincode.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 17 Feb 2022 19:12:05 +0900</pubDate>
    </item>
    <item>
      <title>[Nextjs] Emotion.js - Prop className did not match (부제: Babel 설정)</title>
      <link>https://vincode.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;emotion.js&lt;span style=&quot;background-color: #ffffff;&quot;&gt;는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;styled-components&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;처럼 브라우저에서 열었을 때(처음) className을 임의로 생성(SSR)해주는데 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #666666;&quot;&gt;이후 CSR로 화면을 렌더링하게 되면 이때 서버에서 받은 클래스명과 이후 클라이언트에서 작동하는 클래스 명이 달라지면서 스타일을 불러올수 없는 문제가 발생한다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;난 next도 emotion 도 둘다 초보였기 때무네.. 눈물의 구글링을 반복하다가 바벨 설정을 이렇게 해줬다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1. 바벨 플러그인 설치&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1644896892693&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add babel-plugin-module-resolver @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2. .babelrc 작성&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1644897569663&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;presets&quot;: [&quot;next/babel&quot;],
    &quot;plugins&quot;: [
      [
        &quot;@emotion&quot;,{
          // sourceMap is on by default but source maps are dead code eliminated in production
          &quot;sourceMap&quot;: true,
          &quot;autoLabel&quot;: &quot;dev-only&quot;,
          &quot;labelFormat&quot;: &quot;[local]&quot;,
          &quot;cssPropOptimization&quot;: true
        }
      ],
      [
        &quot;module-resolver&quot;,
        {
          &quot;root&quot;: [&quot;.&quot;],
          &quot;extensions&quot;: [&quot;.js&quot;, &quot;.ts&quot;, &quot;.tsx&quot;]
        }
      ]
    ]
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>React/에러 노트</category>
      <author>빈빠끄</author>
      <guid isPermaLink="true">https://vincode.tistory.com/18</guid>
      <comments>https://vincode.tistory.com/18#entry18comment</comments>
      <pubDate>Tue, 15 Feb 2022 12:59:59 +0900</pubDate>
    </item>
  </channel>
</rss>