<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Psychogrammer</title>
    <link>https://psychogrammer.tistory.com/</link>
    <description>코딩에 가치를 더하다</description>
    <language>ko</language>
    <pubDate>Sat, 10 Oct 2026 22:46:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Psychogrammer</managingEditor>
    <image>
      <title>Psychogrammer</title>
      <url>https://tistory1.daumcdn.net/tistory/4456315/attach/e887fb9b2aa5418e9864d3448ede854f</url>
      <link>https://psychogrammer.tistory.com</link>
    </image>
    <item>
      <title>Next.js 15 instrumentation이 프로덕션에서 안돌아간다면</title>
      <link>https://psychogrammer.tistory.com/294</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20250620_2200_Next.js 15 Insights_simple_compose_01jy6prsxffrnsaqwa5a4snw7v.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brMSzd/btsOLgfP4cY/LGsEPxJHmcPXMOd2xwXT9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brMSzd/btsOLgfP4cY/LGsEPxJHmcPXMOd2xwXT9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brMSzd/btsOLgfP4cY/LGsEPxJHmcPXMOd2xwXT9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrMSzd%2FbtsOLgfP4cY%2FLGsEPxJHmcPXMOd2xwXT9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;483&quot; data-filename=&quot;20250620_2200_Next.js 15 Insights_simple_compose_01jy6prsxffrnsaqwa5a4snw7v.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 앱에서 서버 시작 시점에 딱 한 번만 실행되는 초기화 로직이 필요했다. 데이터베이스 연결, 캐시 설정, 외부 서비스 연결 같은 것들 말이다. 공식 문서를 보니 &lt;code&gt;instrumentation.ts&lt;/code&gt; 파일을 만들면 앱 시작 시점에 한 번 돌아가는 기능이 있었고&amp;nbsp;&lt;code&gt;register&lt;/code&gt; 함수를 export하면 된다고 나와있었다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// instrumentation.ts
export async function register() {
  console.log('서버 초기화 중...');
  // 초기화 로직
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서는 잘 되더라. &lt;code&gt;npm run dev&lt;/code&gt; 하면 콘솔에 로그도 잘 찍히고, 초기화도 정상적으로 실행됐다. 그런데 프로덕션 빌드 후 실행하니까 아무것도 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;npm run build
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔에 아무런 로그도 없고, 초기화 코드도 실행되지 않았다. 뭔가 이상해서 GitHub 이슈들을 찾아봤더니 나만 겪는 문제가 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;발견한 이슈들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 공식 레포지토리에서 관련 이슈들을 여러 개 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Next.js 14.0.5-canary.29에서 발견한 건데, 프로덕션 빌드 후 서버를 시작할 때 register 함수가 자동으로 호출되지 않고, 첫 번째 라우트를 방문해야만 실행되는 문제였다. 하지만 나는 Next.js 15를 사용했고, instrumentation 기능이 정식으로 안정화되어서 experimental.instrumentationHook 설정을 config에 작성하지 않아도 기본적으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능 자체가 성능 추적이나 디버깅, 관찰 가능성 도구 통합 목적으로 만들어진 거라 어느 정도는 예상된 동작이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 서버 시작과 동시에 실행되어야 할 초기화 로직이 사용자의 첫 번째 HTTP 요청에 의존한다는 점이었다. 서버가 시작되자마자 데이터베이스 연결이나 환경 설정 같은 걸 초기화해야 하는데, 누군가 웹사이트에 접속해야만 그 코드가 돌아간다니... 결국 다른 방법을 찾아야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결책 찾기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;instrumentation 기능 자체가 불안정하고, next 팀에서도 동작 방식을 블랙 박스로 숨겨놓으니 다른 방법을 써야겠다고 생각했다. 여러 방법을 시도해봤는데, 각각 장단점이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Next.js 커스텀 서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 확실한 방법은 Next.js 내장 커스텀 서버를 사용하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

async function initialize() {
  console.log('  서버 초기화 중...');

  // 여기에 초기화 로직 작성
  // await connectDatabase();
  // await setupCache();

  console.log('✅ 초기화 완료');
}

app.prepare().then(async () =&amp;gt; {
  await initialize();

  createServer((req, res) =&amp;gt; {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
  }).listen(3000, () =&amp;gt; {
    console.log('&amp;gt; Ready on http://localhost:3000');
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;에서 start 스크립트도 바꿔준다:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node server.js&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Next.js 서버가 준비된 직후에 초기화 함수가 확실히 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 모듈 레벨에서 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 간단한 방법도 있다. Next.js 앱 내에서 모듈이 처음 로드될 때 초기화하는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// lib/server-init.ts
let initialized = false;

async function initializeApp() {
  if (initialized) return;

  console.log('  앱 초기화 중...');

  // 초기화 로직

  initialized = true;
  console.log('✅ 앱 초기화 완료');
}

// 서버에서만 실행
if (typeof window === 'undefined') {
  initializeApp();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;code&gt;app/layout.tsx&lt;/code&gt;에서 이 모듈을 import만 해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/layout.tsx
import '@/lib/server-init'; // 이것만 추가

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &amp;lt;html&amp;gt;
      &amp;lt;body&amp;gt;{children}&amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 좀 더 정교한 조건 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 타임과 런타임을 확실히 구분하고 싶다면 이런 방법도 있다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;// 런타임에서만 실행 (빌드 타임 제외)
if (
  typeof window === &quot;undefined&quot; &amp;amp;&amp;amp; 
  process.env.NEXT_PHASE !== &quot;phase-production-build&quot; &amp;amp;&amp;amp;
  process.env.NEXT_PHASE !== &quot;phase-export&quot; &amp;amp;&amp;amp;
  !process.env.NEXT_BUILD_ID
) {
  process.nextTick(() =&amp;gt; {
    initializeApp().catch((error) =&amp;gt; {
      console.error(&quot;앱 초기화 중 오류 발생:&quot;, error);
    });
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Next.js가 빌드 중일 때는 실행되지 않고, 실제 서버가 돌아갈 때만 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법이 최초 1번만 실행되는 이유는 Node.js의 모듈 캐싱 시스템 때문이다. Node.js는 한 번 로드된 모듈을 메모리에 캐시해두고, 같은 모듈을 다시 import할 때는 새로 실행하지 않고 캐시된 결과를 재사용한다. 따라서 lib/server-init.ts 파일이 처음 import될 때만 최상위 레벨의 코드가 실행되고, 그 이후에는 아무리 많은 컴포넌트에서 이 모듈을 import해도 초기화 코드는 다시 실행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 RSC(React Server Components)와는 직접적인 관련이 없는, Node.js 자체의 기본 동작이다. 다만 Next.js App Router 환경에서는 layout.tsx에서 모듈을 import할 때 서버에서 렌더링되면서 초기화가 실행되는 것뿐이다. 여기에 initialized 플래그를 추가로 사용해서 혹시 모를 중복 실행도 방지하고, typeof window === 'undefined' 체크로 서버 환경에서만 실행되도록 보장하는 이중 안전장치를 마련한 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 15에서 &lt;code&gt;instrumentation.ts&lt;/code&gt;는 개발 환경에서는 잘 되지만 프로덕션에서는 신뢰할 수 없다. 특히 standalone 빌드나 Docker 환경에서는 더더욱 그렇다. 당분간은 커스텀 서버를 사용하거나 모듈 레벨에서 초기화하는 방식을 쓰는 게 좋겠다. 개인적으로는 커스텀 서버 방식을 추천한다. 좀 더 명시적이고 제어 가능하기 때문이다. 어차피 프로덕션에서는 확실한 게 최고다.&lt;/p&gt;</description>
      <category>  Front/⚛️ React JS</category>
      <category>instrumentation</category>
      <category>NextJS</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/294</guid>
      <comments>https://psychogrammer.tistory.com/294#entry294comment</comments>
      <pubDate>Fri, 20 Jun 2025 22:17:19 +0900</pubDate>
    </item>
    <item>
      <title>Elasticsearch 대용량 데이터 파이프라인 구축기 - 수십억 건 데이터를 실시간으로 처리하는 법</title>
      <link>https://psychogrammer.tistory.com/293</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20250619_1734_데이터 파이프라인 구축_simple_compose_01jy3n45nbf478hkqgv1g9yytw.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8gxlT/btsOInF3FEf/Q0lLw7KsaIf4KnDHbzxLh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8gxlT/btsOInF3FEf/Q0lLw7KsaIf4KnDHbzxLh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8gxlT/btsOInF3FEf/Q0lLw7KsaIf4KnDHbzxLh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8gxlT%2FbtsOInF3FEf%2FQ0lLw7KsaIf4KnDHbzxLh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;20250619_1734_데이터 파이프라인 구축_simple_compose_01jy3n45nbf478hkqgv1g9yytw.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느&amp;nbsp;날&amp;nbsp;갑자기&amp;nbsp;팀원이&amp;nbsp;찾아와서&amp;nbsp;말했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;지금 로그에서 사용자 선호 국가 데이터를 시각화하는데, 5개를 고른 유저와 1개만 고른 유저의 데이터가 같이 쌓이고 있어서 왜곡이 생기는 것 같아요. 국가별로 광고 캠페인을 집행해야하는데, 빨리 확인해볼 수 있을까요?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞는 말이었다. '한국'만을 선호하는 사용자와 5개 국가 중 하나로 한국을 포함시킨 사용자의 데이터가 동일한 가중치로 집계된다면, 실제 선호도를 제대로 반영하기 어려울 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그럼, 선택한 개수에 따라 가중치를 계산해서 필드로 제공해드릴까요? 예를 들어 1개 선택하면 1.0, 5개 선택하면 각각 0.2씩 부여하는 식이면 정확하진 않아도, 지금보다는 훨씬 정확할 거에요.&quot;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원도 좋은 아이디어라고 동의하며 바로 현재 상황을 분석하기 시작했다. 기존 Elasticsearch에는 이미 수십억 건의 로그가 쌓여있었고, user.preferences.preferRegion 필드는 존재했지만 가중치 필드는 없는 상황이었다. 더욱이 이 문제는 엄청난 데이터가 실시간으로 계속 유입되고 있다는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 번째 접근: Update By Query의 함정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 생각은 간단했다. &quot;그냥 update_by_query로 기존 데이터를 업데이트하면 되겠네!&quot;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #383a42; text-align: left;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;POST /*-log-[service]-*/_update_by_query
{
  &quot;script&quot;: {
    &quot;source&quot;: &quot;&quot;&quot;
    def preferRegion = ctx._source.user?.preferences?.preferRegion;
    def size = 0;
    
    if (preferRegion instanceof String) {
      size = (preferRegion == &quot;ALL&quot;) ? 0 : 1;
    } else if (preferRegion instanceof List) {
      size = preferRegion.size();
    }
    
    if (size &amp;gt;= 5) ctx._source.preferRegion_weight = 0.2;
    else if (size == 4) ctx._source.preferRegion_weight = 0.25;
    // ... 이하 생략
    &quot;&quot;&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 인덱스로 돌려보니까... 1N억 건 처리해야하는데 대략 80시간이 걸릴 것 같다고 나왔다. 이거 운영에서 돌리면 서비스 터진다. 근데 로그 특성상 최신 로그를 반영하면 된다는 것을 알고, 최근 24시간 데이터부터 처리해볼까?&quot; 싶어서 범위를 좁혀서 시도해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;```json&lt;/p&gt;
&lt;pre id=&quot;code_1750397790824&quot; class=&quot;ceylon&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;POST /*-log-[service]-*/_update_by_query
{
  &quot;query&quot;: {
    &quot;range&quot;: {
      &quot;@timestamp&quot;: {
        &quot;gte&quot;: &quot;now-24h&quot;
      }
    }
  },
  &quot;script&quot;: {
    &quot;source&quot;: &quot;...&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;최근 하루 데이터는 대략 1000만 건 정도였는데, 이것도 30분은 걸렸다. 그리고 무엇보다 치명적인 문제가 있었다 - 새로 들어오는 데이터에는 여전히 가중치가 추가되지 않는다는 것! &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 번째 시도: Ingest Pipeline 발견&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 방법을 찾아보다가 Ingest Pipeline이라는 걸 알게 되었다. 새로 들어오는 데이터에 자동으로 전처리를 적용하는 방식이었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #383a42; text-align: left;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;PUT _ingest/pipeline/add_region_weight
{
  &quot;description&quot;: &quot;Add preferRegion_weight based on preferRegion size&quot;,
  &quot;processors&quot;: [
    {
      &quot;script&quot;: {
        &quot;source&quot;: &quot;&quot;&quot;
        if (ctx.user?.preferences?.preferRegion != null) {
          def preferRegion = ctx.user.preferences.preferRegion;
          def size = 0;
          
          if (preferRegion instanceof String) {
            size = (preferRegion == &quot;ALL&quot;) ? 0 : 1;
          } else if (preferRegion instanceof List) {
            size = preferRegion.size();
          }
          
          if (size &amp;gt;= 5) ctx.preferRegion_weight = 0.2;
          else if (size == 4) ctx.preferRegion_weight = 0.25;
          else if (size == 3) ctx.preferRegion_weight = 0.33;
          else if (size == 2) ctx.preferRegion_weight = 0.5;
          else ctx.preferRegion_weight = 1.0;
        } else {
          ctx.preferRegion_weight = 1.0;
        }
        &quot;&quot;&quot;
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트해보니 잘 작동했다. 하지만 여기서 첫 번째 삽질이 시작되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세 번째 난관: Index Template 우선순위 지옥&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인만 만들어서는 새로 생성되는 인덱스에 자동으로 적용되지 않았다. Index Template이 필요했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;thrift&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;PUT _index_template/log-service-template
{
  &quot;index_patterns&quot;: [&quot;*-log-[service]-*&quot;],
  &quot;priority&quot;: 200,
  &quot;template&quot;: {
    &quot;settings&quot;: {
      &quot;index.default_pipeline&quot;: &quot;add_region_weight&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이게 웬걸? 에러가 터졌다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;index template [log-service-template] has index patterns [*-log-[service]-*] 
matching patterns from existing templates with patterns that have the same priority [200]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고 보니 Elasticsearch에는 수많은 기본 템플릿들이 있었고, 다들 200을 사용하고 있었다. 500으로 올려도 안 되고, 1000으로 올려도 안 되고... 결국 3000으로 올려서야 성공했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #383a42; text-align: left;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
  &quot;index_patterns&quot;: [&quot;*-log-[service]-*&quot;],
  &quot;priority&quot;: 3000,
  &quot;template&quot;: {
    &quot;settings&quot;: {
      &quot;index.default_pipeline&quot;: &quot;add_region_weight&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네 번째 깨달음: 기존 인덱스는 따로 처리해야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Template은 새로 생성되는 인덱스에만 적용된다는 걸 뒤늦게 알았다. 기존 인덱스들은 수동으로 파이프라인을 적용해야 했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;PUT /*-log-[service]-*/_settings
{
  &quot;index.default_pipeline&quot;: &quot;add_region_weight&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 이 작업은 메타데이터만 수정하는 거라 몇 초 만에 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 건 제대로 작동하는지 확인하는 것이었다. _simulate API를 사용해서 파이프라인을 테스트해봤다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;POST _ingest/pipeline/add_region_weight/_simulate
{
  &quot;docs&quot;: [
    {
      &quot;_source&quot;: {
        &quot;user&quot;: {
          &quot;preferences&quot;: {
            &quot;preferRegion&quot;: [&quot;KR&quot;, &quot;US&quot;]
          }
        }
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보니 정확히 0.5가 나왔다. 드디어 제대로 작동한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영 모니터링과 지속적인 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인을 적용한 후에는 지속적인 모니터링이 필요했다. 실제 데이터에 필드가 잘 추가되고 있는지 확인하는 쿼리를 만들었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;GET /*-log-[service]-*/_search
{
  &quot;query&quot;: {
    &quot;range&quot;: {
      &quot;@timestamp&quot;: {
        &quot;gte&quot;: &quot;now-1h&quot;
      }
    }
  },
  &quot;_source&quot;: [&quot;@timestamp&quot;, &quot;preferRegion_weight&quot;, &quot;user.preferences.preferRegion&quot;],
  &quot;size&quot;: 10
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가중치 분포도 확인해서 의도한 대로 처리되고 있는지 체크했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;json&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #383a42; text-align: left;&quot;&gt;&lt;code&gt;GET /*-log-[service]-*/_search
{
  &quot;size&quot;: 0,
  &quot;aggs&quot;: {
    &quot;weight_distribution&quot;: {
      &quot;terms&quot;: {
        &quot;field&quot;: &quot;preferRegion_weight&quot;,
        &quot;size&quot;: 10
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마지막 함정: Kibana에서 필드가 안 보인다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kibana Discover에서 새 필드가 보이지 않았다. 알고 보니 Data View에서 필드 목록을 새로고침해야 했다. Stack Management &amp;rarr; Data Views &amp;rarr; Refresh field list를 누르니까 드디어 필드가 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 배운 것들은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ingest Pipeline 잘쓰자&lt;/b&gt;: Update By Query로 몇 시간 걸릴 작업을 실시간으로 처리할 수 있다. 새로 들어오는 데이터에 자동으로 적용되니까 한 번 설정해놓으면 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Index Template 우선순위&lt;/b&gt;: Elasticsearch 기본 템플릿들이 생각보다 많다. 사용자 정의 템플릿은 1000 이상의 높은 우선순위를 사용하는 게 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단계별 검증의 중요성&lt;/b&gt;: _simulate API로 파이프라인 로직을 미리 검증하고, 실제 데이터로 테스트하고, Kibana UI까지 확인하는 전체 플로우를 검증해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지속적 모니터링&lt;/b&gt;: 파이프라인을 적용한 후에도 실제 데이터 적용률, 성능 영향, 가중치 분포 등을 지속적으로 모니터링해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 처음 예상했던 것보다 훨씬 복잡했지만, 한 번 제대로 구축해놓으니 매일 수백만 건의 데이터가 자동으로 전처리되고 있다.&lt;/p&gt;</description>
      <category>  DevOps</category>
      <category>Elk</category>
      <category>Kibana</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/293</guid>
      <comments>https://psychogrammer.tistory.com/293#entry293comment</comments>
      <pubDate>Thu, 19 Jun 2025 17:41:45 +0900</pubDate>
    </item>
    <item>
      <title>Docker COPY에서 node_modules 충돌 해결하기</title>
      <link>https://psychogrammer.tistory.com/292</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20250602_1404_Docker 이미지 강조_simple_compose_01jwqga75kfxn8bbgx4ebsnavq.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PTXUN/btsOmLGXg4Z/SPE5mEg66XolwAgRp7vO60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PTXUN/btsOmLGXg4Z/SPE5mEg66XolwAgRp7vO60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PTXUN/btsOmLGXg4Z/SPE5mEg66XolwAgRp7vO60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPTXUN%2FbtsOmLGXg4Z%2FSPE5mEg66XolwAgRp7vO60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;522&quot; data-filename=&quot;20250602_1404_Docker 이미지 강조_simple_compose_01jwqga75kfxn8bbgx4ebsnavq.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&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;turborepo + pnpm 환경을 도커 설정을 수정하다가 흥미로운 에러를 만났다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;ERROR: failed to solve: cannot copy to non-directory: 
/var/lib/docker/overlay2/.../app/apps/chat/node_modules/typescript
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node_modules 관련 충돌인 건 분명한데, git에는 node_modules가 없는 상황이라 원인 파악이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 캐시 최적화를 위해 다음과 같은 Dockerfile 구조를 사용하고 있었다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;COPY apps/chat/package.json ./apps/chat/package.json
RUN pnpm install  
COPY apps ./apps  # 여기서 에러 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git 상태를 확인해보니 깨끗했다:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;$ git status  
nothing to commit, working tree clean

$ ls apps/chat/
src/ public/ package.json # node_modules 보이지 않음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 왜 충돌이 발생할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인 발견&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너 내부에서 일어나는 일을 살펴봤다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;pnpm install 실행 &amp;rarr; 컨테이너 내부에 apps/chat/node_modules/ 생성&lt;/li&gt;
&lt;li&gt;COPY apps ./apps 실행 &amp;rarr; 로컬의 apps 폴더를 같은 위치에 복사 시도&lt;/li&gt;
&lt;li&gt;경로 충돌 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 로컬 인스턴스를 다시 확인해보니&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ ls -la apps/chat/
drwxr-xr-x  3 user user 4096 Apr 23 15:05 node_modules
-rw-r--r--  1 user user 1122 May  9 15:40 package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아, 로컬 개발 과정에서 생성된 node_modules가 실제로 존재했다. .gitignore로 git에는 올라가지 않았지만 파일시스템에는 있었던 것이다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;.dockerignore 이슈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 .dockerignore를 설정해뒀다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;node_modules/
**/node_modules/
apps/*/node_modules/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 왜 node_modules가 복사가 된될까? .dockerignore는 COPY . . 같은 명령어에서 해당 파일들을 아예 제외해야 하는데 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 빌드 캐시나 pnpm의 특수한 링크 구조 때문에 제대로 작동하지 않는 것 같았다. --no-cache 옵션으로 빌드해봐도 동일한 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;임시 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 로컬 node_modules를 제거하고 테스트해봤다:&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;mv apps/chat/node_modules apps/chat/node_modules.bak
docker build -t test .  # 성공!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 충돌할 파일이 없으니 정상 빌드됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;포인트 짚고 넘어가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker COPY의 동작&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;같은 경로에 다른 타입(파일 vs 폴더)이 있으면 충돌 발생&lt;/li&gt;
&lt;li&gt;.dockerignore가 항상 예상대로 작동하지 않을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pnpm workspace 특성&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;복잡한 심볼릭 링크 구조로 일반적인 npm과 다른 동작&lt;/li&gt;
&lt;li&gt;Docker 환경에서는 추가 고려사항 필요&lt;/li&gt;
&lt;/ul&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;로컬 개발 환경의 부산물도 Docker 빌드에 영향&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git에는 없지만 로컬 파일시스템에 존재하는 node_modules가 원인이었고, .dockerignore가 예상대로 작동하지 않는 상황이 겹쳤다. 결국 node_modules를 없애서 해결한 간단한 문제였다. Docker 환경에서 당연히 로컬 개발 환경의 상태도 함께 고려해야 한다는 점을 다시 한번 확인했다.&lt;/p&gt;</description>
      <category>  DevOps/  Docker</category>
      <category>docker</category>
      <category>pnpm</category>
      <category>Turbo</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/292</guid>
      <comments>https://psychogrammer.tistory.com/292#entry292comment</comments>
      <pubDate>Mon, 2 Jun 2025 19:05:46 +0900</pubDate>
    </item>
    <item>
      <title>Tailwind v4 도입하다가 monorepo 빌드 시스템을 뜯어고친 썰</title>
      <link>https://psychogrammer.tistory.com/291</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Currency 앱에 새로 나온 Tailwind v4를 적용해보고 싶었다. 기존 앱들은 v3를 쓰고 있었는데, &quot;각각 따로 빌드하니까 상관없겠지&quot;라고 생각했던 게 착각이었다. PostCSS 설정이 충돌하면서 빌드가 망가지기 시작했고, 이를 해결하려다 보니 결국 전체 monorepo 구조를 뜯어고치게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20250601_1859_Currency 앱 구조 개선_simple_compose_01jwnewbtwf3e953b0hh4h3rbc.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nZCFK/btsOmwbtBSb/6Kp41PCOuedBhQeMbrXTI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZCFK/btsOmwbtBSb/6Kp41PCOuedBhQeMbrXTI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZCFK/btsOmwbtBSb/6Kp41PCOuedBhQeMbrXTI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZCFK%2FbtsOmwbtBSb%2F6Kp41PCOuedBhQeMbrXTI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-filename=&quot;20250601_1859_Currency 앱 구조 개선_simple_compose_01jwnewbtwf3e953b0hh4h3rbc.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일단 Tailwind v4 적용했던 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind v4의 새로운 CSS 엔진이 궁금해서 Currency 앱에 적용한 코드다. 공식 문서를 보니 설정 방법이 꽤 달라져 있었다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// apps/currency/vite.config.ts
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    tailwindcss(), // v4는 Vite 플러그인으로 모든 걸 처리
  ]
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 v3처럼 별도의 PostCSS 플러그인이 필요 없다는 게 신기했다. 일단 적용해보니 잘 동작했다. &quot;이거 쉽네?&quot;라고 생각했는데 터보 빌드를 돌려보니 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 번째 문제: PostCSS 설정 충돌&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터보가 전체 앱들을 빌드할 때 PostCSS 설정이 뒤섞이기 시작했다. Currency 앱의 CSS는 제대로 생성되는데, 다른 앱들의 CSS가 이상해졌다. 로그를 보니 Tailwind v3와 v4의 PostCSS 처리 방식이 서로 간섭하고 있었다. Currency 앱에 별도의 PostCSS 설정을 만들어서 간단히 해결할 수 있을 줄 알았다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// apps/currency/postcss.config.js
export default {
  plugins: [] // Tailwind v4는 Vite에서 처리하니까 비워두자
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 문제가 있었다. 터보가 빌드할 때 각 앱의 node_modules가 서로 영향을 주고 있는 것 같았다. LLM에 물어보니 &quot;phantom dependencies&quot; 문제일 가능성이 있다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 번째 문제: 패키지 격리 부족&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 더 파보니 npm의 hoisting 때문이었다. 각 앱이 독립적으로 관리되어야 하는데, 의존성들이 섞이면서 PostCSS 플러그인들이 예상치 못한 방식으로 로드되고 있었다. 결국 패키지 격리가 더 엄격한 pnpm으로 갈아타기로 결정했다. 일단 설정부터 만들어봤다.&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# pnpm-workspace.yaml
packages:
  - 'apps/*'
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# .pnpmrc
strict-peer-dependencies=false
auto-install-peers=true
shamefully-hoist=false  # 이게 핵심이었다
store-dir=.pnpm-store
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shamefully-hoist=false가 중요했다. 이 설정으로 각 앱의 의존성이 완전히 격리됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세 번째 문제: Turborepo 설정의 미스터리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnpm으로 바꾸고 나니 새로운 의문이 생겼다. 공식 문서에 나온 best pracice대로 turbo.json 파일을 작성하다 보니 이상했다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;tasks&quot;: {
    &quot;build&quot;: {
      &quot;inputs&quot;: [
        &quot;src/**&quot;,
        &quot;public/**&quot;, 
        &quot;*.json&quot;,
        &quot;postcss.config.*&quot;,
        &quot;vite.config.*&quot;
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 내 프로젝트에는 apps/currency, apps/chat, apps/mbti 같은 폴더들이 있는데, 왜 구체적인 경로가 없을까? 처음에는 이렇게 써야 하는 줄 알았다:&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;&quot;inputs&quot;: [&quot;apps/currency/**&quot;, &quot;apps/chat/**&quot;, &quot;apps/mbti/**&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터보가 어떻게 내 앱들을 찾는지 궁금해서 여러 실험을 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실험 1: 터보가 뭘 보고 있나?&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;npx turbo ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어로 터보가 인식하는 패키지들을 확인해봤다. 신기하게도 apps 폴더 안의 것들만 나타났다. 그럼 터보가 어떻게 이걸 알지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에 물어보니 &quot;워크스페이스 설정을 자동으로 감지한다&quot;고 했다. 그래서 더 실험해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실험 2: 워크스페이스 패턴 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;packages 폴더를 만들고 그 안에 테스트 패키지를 추가해봤다. npx turbo ls를 다시 실행해도 새 패키지는 목록에 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 pnpm-workspace.yaml에 packages/*를 추가하고 다시 실행하니 새 패키지가 나타났다! 이제 이해됐다. 터보는 워크스페이스 설정을 읽고 그에 따라 패키지들을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, turbo.json의 postcss.config.* 패턴은 각 앱 디렉토리를 기준으로 상대적으로 적용된다는 뜻이었다. apps/currency/postcss.config.js, apps/chat/postcss.config.js 이런 식으로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 해결의 전체적인 그림&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 퍼즐이 맞춰지니 Currency 앱의 Tailwind v4 적용이 완벽하게 작동했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;pnpm의 엄격한 격리&lt;/b&gt;: shamefully-hoist=false로 각 앱의 PostCSS 관련 패키지들이 완전히 분리됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;독립적인 PostCSS 설정&lt;/b&gt;: Currency 앱은 빈 PostCSS 설정, 다른 앱들은 기존 v3 설정 유지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;터보의 개별 빌드&lt;/b&gt;: 각 앱이 자신의 설정에 따라 독립적으로 빌드됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 빌드 플로우는 이렇게 됐다:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;build-apps&quot;: &quot;turbo run build&quot;,
    &quot;build&quot;: &quot;pnpm run build-apps &amp;amp;&amp;amp; next build&quot;,
    &quot;clean&quot;: &quot;rm -rf public/static &amp;amp;&amp;amp; rm -rf apps/*/node_modules &amp;amp;&amp;amp; rm -rf apps/*/.next&quot;,
    &quot;dev&quot;: &quot;pnpm run build-apps &amp;amp;&amp;amp; next dev --turbopack&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 터보가 각 앱을 개별적으로 빌드하고, 그 결과물을 메인 Next.js 프로젝트가 사용하는 하이브리드 구조가 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예상치 못한 부가 효과들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Currency 앱의 Tailwind v4 적용을 위해 시작한 작업이었는데, 결과적으로 전체 monorepo가 훨씬 견고해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빌드 캐시 개선&lt;/b&gt;: 각 앱의 설정 파일 변경이 해당 앱에만 영향을 주게 됐다. Currency 앱의 Vite 설정을 바꿔도 다른 앱들은 캐시를 그대로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성 충돌 해결&lt;/b&gt;: phantom dependencies 문제가 완전히 사라졌다. 각 앱이 정말로 독립적인 환경에서 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발 경험 향상&lt;/b&gt;: 새로운 앱을 추가할 때 별도 설정이 필요 없어졌다. apps 폴더에 디렉토리 만들고 package.json 추가하면 터보가 자동으로 인식한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삽질하면서 배운 것들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 경험에서 가장 큰 교훈은 &quot;자동화된 도구의 동작 원리를 이해하는 것&quot;의 중요성이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터보가 &quot;워크스페이스를 자동으로 감지한다&quot;는 문서만 봤을 때는 그냥 '오 편하네' 라고만 생각했다. 하지만 실제로 pnpm-workspace.yaml 파일을 읽고, 글로브 패턴을 확장하고, 각 디렉토리에서 package.json을 찾는 구체적인 과정이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 과정을 이해하고 나니 Currency 앱의 특별한 PostCSS 설정이 어떻게 다른 앱들과 격리되는지도 명확해졌다. 각 앱의 빌드 환경이 진짜로 독립적이라는 확신이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind v4 하나 적용하려다가 monorepo 전체를 뜯어고치게 됐지만, 결과적으로는 훨씬 좋은 구조를 얻었다. Currency 앱은 v4의 새로운 기능들을 자유롭게 사용할 수 있게 됐고, 다른 앱들도 안정적으로 v3를 계속 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 이제는 새로운 기술을 실험할 때 다른 앱들에게 영향을 줄 걱정이 없어졌다. 각 앱이 완전히 독립적인 환경을 가지고 있으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순한 CSS 충돌 문제였는데, 해결 과정에서 패키지 관리와 빌드 시스템까지 제대로 이해하게 됐다. 이런 식으로 하나의 문제를 깊이 파다 보면 관련된 시스템 전체를 배우게 되는 것 같다. 시간은 좀 걸렸지만 값진 경험이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;currency&quot; href=&quot;https://junha.kr/currency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://junha.kr/currency&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748772248285&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;currency&quot; data-og-description=&quot;&quot; data-og-host=&quot;junha.kr&quot; data-og-source-url=&quot;https://junha.kr/currency&quot; data-og-url=&quot;https://junha.kr/currency&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://junha.kr/currency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junha.kr/currency&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;currency&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junha.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Front/⚛️ React JS</category>
      <category>monorepo</category>
      <category>pnpm</category>
      <category>TAILWIND</category>
      <category>Turbo</category>
      <category>V4</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/291</guid>
      <comments>https://psychogrammer.tistory.com/291#entry291comment</comments>
      <pubDate>Sun, 1 Jun 2025 19:01:31 +0900</pubDate>
    </item>
    <item>
      <title>모듈 시스템을 알아보자. 모듈은 동기적 호출일까? Require vs Import</title>
      <link>https://psychogrammer.tistory.com/284</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1_n-UZ42vYMZn-nw62ATR7lg.jpeg&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AzWSy/btsdZZ839Ya/Z9Yr4jKqlQzEWzBwYZnpOk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AzWSy/btsdZZ839Ya/Z9Yr4jKqlQzEWzBwYZnpOk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AzWSy/btsdZZ839Ya/Z9Yr4jKqlQzEWzBwYZnpOk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAzWSy%2FbtsdZZ839Ya%2FZ9Yr4jKqlQzEWzBwYZnpOk%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;575&quot; height=&quot;345&quot; data-filename=&quot;1_n-UZ42vYMZn-nw62ATR7lg.jpeg&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;모듈?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련된 코드들을 하나의 코드 단위로 캡슐화한 &lt;b&gt;하나의 파일&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CommonJS (CJS)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Require 키워드를 사용한 모듈 시스템을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js와 구버전의 브라우저에서 주로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;module.exports&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;와&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;exports&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;두 가지 방법으로 모듈을 내보낼 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시1. module.exports&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1683348549159&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// arithmetic.js

class Arithmetic {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
    add() {return this.a + this.b;}
    subtract() {return this.a - this.b;}
};
 
module.exports = Arithmetic;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683348615681&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.js

const Arithmetic = require('./calculator.js');

const op = new Arithmetic(100,40)
op.add();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;module.exports에  값을 담아서 내보내는 방식&lt;/li&gt;
&lt;li&gt;값은 클래스, 변수, 함수 등 하나의 레퍼런스(메모리 주소)등이 들어갈 수 있다.&lt;/li&gt;
&lt;li&gt;require 키워드는 module.exports에 할당한 값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시2.&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;module.exports&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1683348920485&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// module.js

module.exports = {
	name: 'junha',
	age: 27
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683348934478&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.js

const obj = require('./module.js')

obj.name // 'junha'
obj.age // 27&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시3. &lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;exports&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1683348997569&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// arithmetic.js

exports.add = (a, b) =&amp;gt; a + b;
exports.subtract = (a, b) =&amp;gt; a - b;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683349027213&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.js

const Arithmetic = require('./calculator.js');
 
Arithmetic.add(100,40)
Arithmetic.subtract(100,40)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;exports 객체는 module.exports 객체를 참조하는 변수다.&lt;/li&gt;
&lt;li&gt;exports 객체에 할당된 프로퍼티들은 모두 module.exports 객체에서 접근이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ECMAScript Modules (&lt;b&gt;ESM)&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;es6에서 공식적으로 채택된 모듈 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Default exports, &lt;b&gt;Named exports&lt;/b&gt;&lt;/b&gt;&amp;nbsp;두 가지 방법으로 모듈을 내보낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시1. Default exports&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1683350567405&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// arithmetic.js

class Arithmetic {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
    add() {return this.a + this.b;}
    subtract() {return this.a - this.b;}
};
 
export default Arithmetic;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683350697459&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.js

import Arithmetic from './arithmetic.js'

const op = new Arithmetic(100,40)
op.add()
op.subtract()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 파일에서 한 번만 사용할 수 있고, 대표로 값을 내보낼 때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1683351066213&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// utils.js

const add = (a, b) =&amp;gt; a + b;
const subtract = (a, b) =&amp;gt; a - b;
const array = [1, 2, 3];
const object = { a: 1, b: 2, c: 3 };

const obj = {
	add,
    subtract,
    array,
    object
};

export default obj;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683351159196&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { add, subtract, array, object }  from &quot;./utils.js&quot;;&lt;/code&gt;&lt;/pre&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;import시 아무 이름이나 사용 할 수 있으며,&amp;nbsp;destructring도 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시2. Named exports&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개 이상 변수들을 객체에 담아 export할 때 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1683352572479&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const getUpload = (req, res) =&amp;gt; res.render(&quot;upload&quot;, obj);
export const postUpload = async (req, res) =&amp;gt; {};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683352580220&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { getUpload, postUpload } from &quot;../controllers/videoController.js&quot;;

❌ import upload from &quot;../controllers/videoController.js&quot;;
// SyntaxError: The requested module does not provide an export named 'default'

✅ import * as upload from &quot;../controllers/videoController.js&quot;;
// as를 통해 객체로 가져올 수 있다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default exports 방식과 named exports 방식을 함께 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1683352721558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// utils.js

const add = (a, b) =&amp;gt; a + b;
const subtract = (a, b) =&amp;gt; a - b;
const array = [1, 2, 3];

export { subtract, array };
export default add;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1683352746573&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app.js 파일

import add, { subtract, array } from './utils.js';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;변수로 바인딩 없이, 단순히 모듈만 실행할 목적이면 import만 사용하는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1683430754130&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;my-module.js&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주의&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1683352867006&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❌ export default const a = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;var, const, let 할당하면서 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;블록 {&amp;hellip;} 안에선 동작하지 않는다.&lt;/li&gt;
&lt;li&gt;import,&amp;nbsp;export&amp;nbsp;는 파일내 최상위에&amp;nbsp;배치해야한다.&lt;/li&gt;
&lt;li&gt;file 프로토콜로 열람할 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈은 로컬 파일에서 동작하지 않고, HTTP 또는 HTTPS 프로토콜을 통해서만 동작한다.&lt;/li&gt;
&lt;li&gt;로컬에서 file:// 프로토콜을 사용해 웹페이지를 열면 import, export 지시자가 동작하지 않는다.&lt;/li&gt;
&lt;li&gt;브라우저에서 origin이 존재하지 않는 것으로 취급하기 때문이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로컬 웹 서버인 &amp;lsquo;라이브 서버&amp;rsquo;&lt;/b&gt;를 사용할 것!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ESM 사용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저&lt;/b&gt;에서 사용하려면, 모듈에 해당되는 &lt;b&gt;script 태그에 type=&quot;module&quot;&lt;/b&gt;을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node.js&lt;/b&gt;에서 사용하려면, &lt;b&gt;package.json&lt;/b&gt;에&lt;b&gt; type=&quot;module&quot;&lt;/b&gt;을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1683431559255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Uncaught SyntaxError: Cannot use import statement outside a module&amp;rdquo;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;commonjs 방식에서 import를 사용하려 했기 때문에 발생한 오류다.&lt;/li&gt;
&lt;li&gt;package.json의 &amp;ldquo;type&amp;rdquo; 필드 값을 &amp;ldquo;module&amp;rdquo;로 설정해줘야함.&lt;/li&gt;
&lt;li&gt;단, 이전에 requre로 불러오던 모듈이 있었다면 import로 불러오도록 다시 선언해 줘야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;브라우저에서 ESM 특징&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지연(defer) 실행: 다운로드와 파싱은 하지만, 실행은 페이지의 파싱이 완료된 후에 수행한다.&lt;/li&gt;
&lt;li&gt;외부 오리진(도메인이나 프로토콜, 포트가 다른 오리진)에서 스크립트를 불러오려면 CORS 헤더가 있어야 한다.&lt;/li&gt;
&lt;li&gt;중복된 외부 스크립트는 무시된다.&lt;/li&gt;
&lt;li&gt;독립적인 스코프를 갖는다.&lt;/li&gt;
&lt;li&gt;항상 엄격 모드로 실행(use strict)된다.&lt;/li&gt;
&lt;li&gt;모듈 내 코드는 단 한 번만 실행된다. 모듈을 내보내면 이 모듈을 가져오기 하는 모듈 모두가 내보내진 모듈을 공유한다.&lt;/li&gt;
&lt;li&gt;모듈 내에서 정의된 변수, 함수, 클래스 등은 모듈 외부에서 직접 접근할 수 없으며, 각각의 모듈에서 해당 모듈을 &lt;b&gt;import&lt;/b&gt; 하여 가져와야 합니다. 모듈은 한 번만 실행되며, 이후에 다른 모듈에서 해당 모듈을 가져올 때는 다시 다운로드하지 않는다.&lt;/li&gt;
&lt;li&gt;인라인 모듈 스크립트도 비동기 처리 할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1683353089822&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(async () =&amp;gt; { const { someFunction } = await import('./someModule.js');})();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;잠깐, Import는 동기적 호출일까?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JavaScript 엔진은 구문의 형태를 보고 정적/동적 import를 자동으로 구분하여 처리한다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 처리 순서&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;구문 분석: 모든 import/export 찾기&lt;/li&gt;
&lt;li&gt;모듈 로딩: import된 모듈 불러오기&lt;/li&gt;
&lt;li&gt;모듈 평가: 실제 코드 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 정적 Import (Static Import)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Import는&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;모듈 초기화 단계에서 처리된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적(Static) 구문
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;코드가 실행되기 전에 처리되는 문법 구조&lt;/li&gt;
&lt;li&gt;컴파일/파싱 단계에서 처리됨&lt;/li&gt;
&lt;li&gt;위치나 형태가 고정되어야 함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드가 실행되기 전에 평가 및 로드되기 때문에 동기적 호출로 생각할 수 있다.&lt;/li&gt;
&lt;li&gt;반드시 최상위 레벨에서 사용해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;import module from './module'
import { something } from './module'
import * as all from './module'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 동적 Import (Dynamic Import)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Import는&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Promise로 감싸진 해당 모듈을 반환하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;비동기 런타임 함수다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;코드 어디서든 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;async function someFunction() {
    const module = await import('./module');
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;import('./db.js')
  .then((module) =&amp;gt; {
    const db = module.default;
    // 모듈 내 default export로 정의된 컴포넌트를 사용할 수 있음
    // module.default는 db.js 파일에서 export default로 내보낸 객체를 가리킴
  })
  .catch((error) =&amp;gt; {
    console.error('Error loading the module:', error);
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. CommonJS&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;require() 함수는 모듈을 동기적으로 불러오며&lt;/b&gt;, 처음 실행된 후 module.exports를 반환. 이후 동일한 모듈에 대한 호출은 캐시된 값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CJS vs ESM&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;1. CJS는 기본 값이다. 왜냐면 ESM은 바뀔게 넘 많아서&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 값으로 use strict가 설정되어 있어야하고, this는 global object를 참조하지 않고, 스코프는 다르게 작동 되는 등등 변화가 많다. 브라우저에서 조차 &amp;lt;script&amp;gt;가 ESM을 기본으로 지정하지 않는 이유다.&amp;nbsp; ESM을 사용하기 위해서는 type=&quot;module&quot;을 추가해 주어야 한다. 기본 값을 CJS에서 ESM으로 바꾸는 것은 호환성을 해치는 문제가 된다. (node의 대안으로 주목받고 있는 deno는 ESM을 기본값으로 사용하지만, 결과적으로 모든 생태계를 처음부터 다시 설계해야 했다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. ESM과 CJS는 완전히 다르다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommonJS에서는 require()는 동기로 이루어진다. 따라서 promise나 콜백 호출을 리턴하지 않는다. require()는 디스크로 부터 읽어서 (네트워크 일수도 있다) 그 즉시 스크립트를 실행한다. 따라서 스스로 I/O나 부수효과 (side effect)를 실행하고 module.exports에 설정되어 있는 값을 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 ESM은 모듈 로더를 비동기 환경에서 실행한다. 먼저 가져온 스크립트를 바로 실행하지 않고, import와 export구문을 찾아서 스크립트를 파싱한다. 파싱 단계에서, 실제로 ESM 로더는 종속성이 있는 코드를 실행하지 않고도도, named imports에 있는 오타를 감지하여 에러를 발생시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 ESM 모듈 로더는 가져온 스크립트를 비동기로 다운로드 하여 파싱한다음, import된 스크립트를 가져오고, 더 이상 import 할 것이 없어질 때까지 import를 찾은 다음 dependencies의 모듈 그래프를 만들어 낸다. 그리고, 스크립트는 실행될 준비를 마치게 되며, 그 스크립트에 의존하고 있는 스크립트들도 실행할 준비를 마치게 되고, 마침내 실행된다. ESM 모듈 내의 모든 자식 스크립트들은 병렬로 다운로드 되지만, 실행은 순차적으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. export default를 피해야 하는 두 가지 이유&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default 문법은 원래 기존의 CommonJS 모듈 시스템과 호환성을 유지하기 위해 만들어졌다. 그러나 named exports와는 다르게, import하는 곳에서 이름을 자유자재로 정할 수 있다. 때문에 1. 같은 모듈이더라도 사용처에 따라 이름이 달라져, &lt;b&gt;1. 큰 규모의 리팩토링을 어렵게 만든다.&lt;/b&gt; 반면 named exports는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;export 되는 값들에 이름을 부여하기 때문에&lt;/span&gt; 모든 사용처에서 동일한 이름을 사용해 IDE가 이를 찾아서 자동으로 import를 도와줄 수 있어 이러한 문제가 발생하지 않는다.&lt;/p&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. tree-shaking을 통해 번들 사이즈를 줄일 수 없다.&lt;/b&gt; 예를 들어, &lt;b&gt;하나의 거대한 객체를 default로 한번에 export하는 경우&lt;/b&gt;에는 tree-shaking을 할 수 없다. 하지만 named exports로 값을 하나씩 export 해주면 사용하지 않는 값들은 tree-shaking을 통해 번들 사이즈를 줄일 수 있다. ES Modules로 작성된 모듈의 경우에는 tree-shaking을 할 수 있지만, CommonJS 모듈로 작성된 모듈의 경우에는 tree-shaking을 할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1729931462040&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. Tree-shaking 잘 되는 경우들
// math.js
export default function add(a, b) { return a + b }  // default export도 OK
export const subtract = (a, b) =&amp;gt; a - b            // named export도 OK

// 2. Tree-shaking 안 되는 경우
// utils.js
export default {
 add: (a, b) =&amp;gt; a + b,
 subtract: (a, b) =&amp;gt; a - b,
 multiply: (a, b) =&amp;gt; a * b,
 // 하나만 써도 모든 함수가 번들에 포함됨!
}

// main.js
import { add } from './utils'  // multiply, subtract도 함께 번들링됨
// 그저 destructring 한 것일 뿐이다&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;default export 자체가 문제라기 보다는, &lt;b&gt;객체로 묶어서 export하면 tree-shaking이 안된다는 점을 명심하자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Front/  Vanilla JS</category>
      <category>export</category>
      <category>import</category>
      <category>module</category>
      <category>require</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/284</guid>
      <comments>https://psychogrammer.tistory.com/284#entry284comment</comments>
      <pubDate>Sat, 26 Oct 2024 17:34:35 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 런타임과 엔진의 차이점: 이벤트 루프는 어디에?</title>
      <link>https://psychogrammer.tistory.com/290</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 엔진? 런타임? 이 두 용어의 차이에 대해 햇갈리시는 분이 계실겁니다. 이 글에서는 자바스크립트의 엔진과 런타임을 비교해보고 &lt;b&gt;이벤트 루프&lt;/b&gt;의 역할,&lt;b&gt;&amp;nbsp;추상적 구조를 살펴보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-01 153423.png&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;1155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zrIHk/btsJRpbib53/2OY5CToFvDkXRtWXrGQEoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zrIHk/btsJRpbib53/2OY5CToFvDkXRtWXrGQEoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zrIHk/btsJRpbib53/2OY5CToFvDkXRtWXrGQEoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzrIHk%2FbtsJRpbib53%2F2OY5CToFvDkXRtWXrGQEoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;589&quot; height=&quot;461&quot; data-filename=&quot;스크린샷 2024-10-01 153423.png&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;1155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 엔진&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ECMAScript(ECMA-262 표준)코드를 파싱하고 실행한다.&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;&lt;b&gt;파서&lt;/b&gt;: 소스 코드를 AST(Abstract Syntax Tree)로 변환.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일러&lt;/b&gt;: AST를 최적화된 바이트코드 또는 네이티브 코드로 컴파일.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가상 머신 (VM)&lt;/b&gt;: 컴파일된 코드를 실행. JIT(Just-In-Time) 컴파일링을 통해 성능 최적화.&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;b&gt;V8&lt;/b&gt;: Google Chrome, Node.js에 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SpiderMonkey&lt;/b&gt;: Mozilla Firefox에 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JavaScriptCore&lt;/b&gt;: Safari 및 iOS에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;V8 엔진&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;V8은 구글이 개발한 &lt;b&gt;자바스크립트 엔진&lt;/b&gt;으로, 자바스크립트 코드를 &lt;b&gt;컴파일&lt;/b&gt;하고 &lt;b&gt;실행&lt;/b&gt;하는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;V8은 &lt;b&gt;단일 스레드&lt;/b&gt;에서 동작하며, 자바스크립트 코드를 해석하고 실행합니다.&lt;/li&gt;
&lt;li&gt;메모리 관리와 &lt;b&gt;가비지 컬렉션&lt;/b&gt;도 V8의 책임입니다.&lt;/li&gt;
&lt;li&gt;V8은 단순히 자바스크립트 코드의 실행을 처리하는 엔진일 뿐, &lt;b&gt;비동기 작업이나 이벤트 루프&lt;/b&gt;를 관리하지는 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 런타임&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔진을 포함한 ECMAScript 실행 환경을 제공한다.&lt;/li&gt;
&lt;li&gt;플랫폼에 따라 추가적인 비표준 API(DOM, fs)를 제공한다.&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;b&gt;엔진&lt;/b&gt;: ECMAScript 실행 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 루프&lt;/b&gt;: 비동기 작업을 관리, 콜백 함수 실행 순서 제어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호스트 API&lt;/b&gt;: 브라우저의 DOM API, 서버의 파일 시스템 API 등 추가 기능 제공&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;b&gt;브라우저&lt;/b&gt;: DOM, BOM, fetch API 등 웹 관련 API 제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Node.js&lt;/b&gt;: 파일 시스템, 네트워크 I/O, 프로세스 관리 등 서버 관련 API 제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Deno/Bun&lt;/b&gt;: Node.js와 유사하지만 파일 시스템, 보안 모델 등에서 차별화된 API 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 이벤트 루프는 어디에 포함되어 있는가?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Node.js 환경&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js는 libuv라는 라이브러리를 사용하여 이벤트 루프를 관리합니다. Node.js의 이벤트 루프는 파일 I/O, 네트워크, 타이머, 비동기 작업을 관리하며, 작업이 완료되면 V8에서 해당 작업의 콜백을 실행하게 됩니다.&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;HTML 명세의 이벤트 루프 스펙을 따르지만, 구체적인 구현은 각자 다릅니다.&lt;/li&gt;
&lt;li&gt;이벤트 루프 관리를 위한 별도의 공개된 라이브러리를 사용하지 않습니다.&lt;/li&gt;
&lt;li&gt;내부 구현이 브라우저 코드에 긴밀하게 통합되어 있어, Node.js처럼 명확하게 분리된 모듈로 존재하지 않습니다.&lt;/li&gt;
&lt;li&gt;각 브라우저가 자체적으로 구현하고 있고, 그 구현이 브라우저 코드에 깊이 통합되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이벤트 루프는 &lt;b&gt;자바스크립트 런타임&lt;/b&gt;(브라우저, Node.js 등)이 제공하는 기능이고, &lt;b&gt;자바스크립트 엔진&lt;/b&gt;(V8)은 이벤트 루프에서 푸쉬한 &lt;b&gt;콜백을 실행하는 역할&lt;/b&gt;을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 이벤트루프의 구조&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;flowchart TB
    subgraph ELS[&quot;Event Loop System (ELS)&quot;]

        subgraph TQ[&quot;Task Queue (TQ)&quot;]
            setTimeout[&quot;setTimeout&quot;]
            setInterval[&quot;setInterval&quot;]
            IO[&quot;I/O Operations (File, Network)&quot;]
            XHR[&quot;XHR Requests (Ajax, Fetch)&quot;]
            UIEvents[&quot;UI Events (Click, Keypress)&quot;]
        end

        subgraph JQ[&quot;Job Queue (JQ)&quot;]
            subgraph MQJ[&quot;Microtask Queue (MQJ)&quot;]
                PromiseThen[&quot;Promise.then()&quot;]
                AsyncAwait[&quot;async/await&quot;]
                MutationObserver[&quot;MutationObserver&quot;]
                QueueMicrotask[&quot;queueMicrotask()&quot;]
            end
        end

        subgraph RQ[&quot;Render Queue (RQ)&quot;]
            Layout[&quot;Layout Calculation&quot;]
            Paint[&quot;Paint (Rasterization)&quot;]
            Composite[&quot;Compositing&quot;]
            StyleRecalc[&quot;Style Recalculation&quot;]
            CSSAnimation[&quot;CSS Animation&quot;]
            Transition[&quot;CSS Transition&quot;]
        end
    end&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 이벤트 루프 시스템을 표현하는 집합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Event Loop System, ELS&lt;/b&gt; = { Task Queue, Microtask Queue, Render Queue }&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Task Queue:&lt;/b&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;b&gt;TQ&lt;/b&gt; = { setTimeout, setInterval, I/O, XHR Requests 등 }&lt;/li&gt;
&lt;li&gt;특정 이벤트(네트워크 응답, 사용자 인터페이스 이벤트 등)를 처리하는 &lt;b&gt;비동기 작업을 기다리는 대기열&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TQ&lt;/b&gt; &amp;sube; &lt;b&gt;Message Queue&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Microtask Queue&lt;/b&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;b&gt;MQ&lt;/b&gt; = { Promise.then(), async/await, MutationObserver 등 }&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MQ&lt;/b&gt; &amp;sube; &lt;b&gt;Job Queue (JQ)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Render Queue&lt;/b&gt;: 브라우저 렌더링 작업이 들어가는 큐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RQ&lt;/b&gt; = { 렌더링, 스타일 변경 }&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한마디로 정리하면 아래와 같습니다.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Event Loop System (ELS)&lt;/b&gt; = { &lt;b&gt;TQ&lt;/b&gt;, &lt;b&gt;JQ&lt;/b&gt; = { &lt;b&gt;MQ&lt;/b&gt; }, &lt;b&gt;RQ&lt;/b&gt; }&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Microtask Queue는 Job Queue의 부분집합&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이 모든 큐들은 &lt;b&gt;이벤트 루프 시스템&lt;/b&gt; 내에서 서로 역할을 분담합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Back/  Node.js</category>
      <category>microtask queue</category>
      <category>Task Queue</category>
      <category>이벤트루프</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/290</guid>
      <comments>https://psychogrammer.tistory.com/290#entry290comment</comments>
      <pubDate>Thu, 24 Oct 2024 20:41:03 +0900</pubDate>
    </item>
    <item>
      <title>Node.js 비동기 처리 모델 이해하기 - 실시간 대규모 처리에 적합한 이유</title>
      <link>https://psychogrammer.tistory.com/289</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js는 &lt;b&gt;싱글 스레드&lt;/b&gt;에서 동작하지만, 놀랍게도 &lt;b&gt;동시성&lt;/b&gt;과 효율적이고 빠른 응답 시간을 유지할 수 있는 &lt;b&gt;비동기 처리 모델&lt;/b&gt;을 제공합니다. 오케스트라의 &lt;b&gt;지휘자&lt;/b&gt;처럼 Node.js는 &lt;b&gt;메인 스레드에서 비동기 작업을 관리&lt;/b&gt;하고, &lt;b&gt;백그라운드에서 실행되는 I/O 작업(네트워크 요청, 파일 시스템 작업)&lt;/b&gt;들이 완료되면 그 결과를 처리하는 방식으로 작동하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 글에서는 Node.js의 비동기 처리 모델과 이를 가능하게 하는 &lt;b&gt;libuv&lt;/b&gt; 라이브러리의 역할을 설명하고, 대규모 데이터를 처리할 때 스레드 풀과 외부 API 호출이 어떻게 동작하고 성능을 최적화하는지 알아보겠습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-01 145816.png&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mCrqE/btsJTfx1hVU/We2MwWBDLVUE4D0PbUsxPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mCrqE/btsJTfx1hVU/We2MwWBDLVUE4D0PbUsxPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mCrqE/btsJTfx1hVU/We2MwWBDLVUE4D0PbUsxPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmCrqE%2FbtsJTfx1hVU%2FWe2MwWBDLVUE4D0PbUsxPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1543&quot; height=&quot;694&quot; data-filename=&quot;스크린샷 2024-10-01 145816.png&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Node.js의 이벤트 루프와 비동기 논블로킹 I/O 모델&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js는 단일 스레드 기반의 자바스크립트 런타임이지만, 모든 작업을 직접 처리하지 않습니다. 비동기 작업 처리를 위해 내부적으로 다중 스레드를 활용합니다. 특히, 네트워크 요청, 파일 시스템 접근, 데이터베이스 호출과 같은 &lt;b&gt;I/O 작업&lt;/b&gt;들은 &lt;b&gt;libuv 라이브러리&lt;/b&gt;를 통해 백그라운드의 스레드 풀에 위임됩니다. 메인 스레드에서는 이 작업들이 완료되기 전까지 다른 작업을 계속 수행하고,&lt;b&gt; 완료된 결과만 처리하는 방식입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이벤트 루프&lt;/b&gt;는 Node.js의 핵심적인 비동기 처리 메커니즘으로, 메인 스레드에서 &lt;b&gt;비동기 작업을 지휘&lt;/b&gt;하며 각 작업이 완료되었을 때 적절한 콜백을 실행합니다. &lt;b&gt;이 논블로킹 I/O 모델 덕분에&lt;/b&gt; Node.js는 &lt;b&gt;실시간 채팅 애플리케이션&lt;/b&gt;처럼 수천, 수만 개의 요청을 동시에 처리해야 하는 환경에서 높은 성능을 발휘합니다. 예를 들어, 각 사용자로부터 들어오는 요청을 즉시 처리하면서도 백그라운드에서 데이터베이스 작업을 수행할 수 있어, 많은 사용자들이 동시에 접속해도 원활하게 운영될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;만약 Node.js가 아닌, &lt;/span&gt;&lt;b&gt;동기적인&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;시스템 또는 서버였다면, 각 I/O 작업이 완료될 때까지 모든 다른 요청은 &lt;/span&gt;&lt;b&gt;차단&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;되어 기다려야 합니다. 하나의 네트워크 요청이 완료될 때까지 다른 요청들이 대기 상태에 머물게 되어, 동시성 처리가 불가능하고, 많은 사용자가 접속하는 상황에서 &lt;/span&gt;&lt;b&gt;성능 저하&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;와 &lt;/span&gt;&lt;b&gt;병목 현상&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 발생하게 되었겠죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 비동기 모델의 성능과 백그라운드 리소스&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비동기 처리 모델에서 중요한 점은, 작업을 메인 스레드에서 비동기로 던진다고 해서 자동으로 무한한 동시성을 보장할 수는 없다는 것입니다. Node.js의 비동기 처리 방식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;libuv의 스레드 풀&lt;/b&gt;과 같은 백그라운드 리소스에 의존합니다. 이 스레드 풀은 동시에 여러 I/O 작업을 처리할 수 있지만, 그 크기와 성능은 제한적입니다. 따라서, 대규모 데이터를 처리할 때도 백그라운드에서 실제로 병렬로 작업을 수행할 충분한 컴퓨팅 파워가 있어야 비동기 모델의 이점이 제대로 발휘됩니다. 즉, 비동기 모델이 동시성을 보장하려면 내부 리소스가 이를 뒷받침해야겠죠.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나, 대규모 작업을 처리할 때 모든 리소스를 내부적으로 처리하는 대신,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;API 호출을 통해 외부 리소스에 컴퓨팅 부담을 분산&lt;/b&gt;시키는 방법을 많이 사용하죠. Node.js는 비동기 API 호출을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;제3자 서버&lt;/b&gt;(예: 클라우드 서비스, 외부 API)에 작업을 위임하고, 실제 리소스 소비는 외부에서 이루어집니다. 이렇게 하면, Node.js 서버는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;네트워크 대역폭과 API 응답 대기 시간&lt;/b&gt;만 관리하면 되고, 직접적인 컴퓨팅 파워는 외부에서 제공받는 구조가 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Node.js의 비동기 모델 덕분에 이러한 API 호출은 논블로킹으로 처리되며, 여러 작업을 동시에 던져놓고 그 결과를 비동기적으로 받아들일 수 있습니다. 따라서 이 방식은 내부 컴퓨팅 리소스가 제한된 환경에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;동시성&lt;/b&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;성능&lt;/b&gt;을 유지하는 훌륭한 전략이 될 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. libuv의 역할과 이벤트 기반 아키텍처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js의 비동기 성능은&lt;b&gt; libuv 라이브러리&lt;/b&gt; 덕분에 가능해졌습니다. libuv는 이벤트 루프와 비동기 I/O 작업을 관리하여, Node.js가 단일 스레드로 동작하면서도 높은 성능을 발휘할 수 있게 합니다. Node.js는 전통적인 멀티스레드 방식 대신, 단일 스레드 기반에서 파일 시스템 작업, 네트워크 요청, 타이머 등 다양한 I/O 작업을 비동기적으로 처리합니다. 이를 통해 개발자는 직접적인 쓰레드 관리의 복잡성을 피하고, 간편하게 비동기 함수, 콜백, Promise, async/await 패턴을 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;libuv는 논블로킹 방식으로 작업을 처리하며, 일부 블로킹 작업의 경우 내부 쓰레드 풀을 사용하여 처리합니다. 이러한 구조는 성능 저하를 최소화하고, I/O 작업이 많은 애플리케이션에서 높은 효율성을 발휘합니다. 특히, 내부적으로 쓰레드 풀을 통해 블로킹 작업을 관리하기 때문에, 개발자는 멀티스레드 관리의 부담 없이 비동기 작업을 직관적으로 다룰 수 있으며, 성능과 생산성을 동시에 확보할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-01 144033.png&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctuZJJ/btsJRQM7NvK/Xwb2veWGzxdYYru8eqxI2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctuZJJ/btsJRQM7NvK/Xwb2veWGzxdYYru8eqxI2K/img.png&quot; data-alt=&quot;Node.js 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctuZJJ/btsJRQM7NvK/Xwb2veWGzxdYYru8eqxI2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctuZJJ%2FbtsJRQM7NvK%2FXwb2veWGzxdYYru8eqxI2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1310&quot; height=&quot;562&quot; data-filename=&quot;스크린샷 2024-10-01 144033.png&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Node.js 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. libuv의 스레드 풀&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 &lt;b&gt;Libuv의 스레드 풀&lt;/b&gt;은 &lt;b&gt;4개의 스레드&lt;/b&gt;로 구성되어 있습니다. 이는 대부분의 시스템에서 I/O 작업이 그다지 많은 스레드를 요구하지 않기 때문입니다. 그러나 &lt;b&gt;CPU 집약적인 작업&lt;/b&gt;을 자주 수행하거나, &lt;b&gt;I/O 작업이 많을 경우&lt;/b&gt;에는 스레드 풀의 크기를 조정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 성능에 따라 더 많은 병렬 작업을 처리할 수 있도록 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;UV_THREADPOOL_SIZE를 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;스레드 풀의 크기&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;를 조정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1727763463489&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UV_THREADPOOL_SIZE=8 node app.js&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 비동기 I/O 작업과 스레드 풀의 관계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js는 비동기 I/O 작업을 처리할 때 &lt;b&gt;libuv의 스레드 풀&lt;/b&gt;을 활용합니다. 다만, 모든 비동기 작업이 스레드 풀에서 처리되는 것은 아닙니다. 예를 들어, &lt;b&gt;네트워크 요청&lt;/b&gt;과 같은 작업은 &lt;b&gt;커널의 비동기 I/O&lt;/b&gt; 기능을 사용하여 처리되므로 스레드 풀을 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에, &lt;b&gt;파일 시스템 작업&lt;/b&gt;처럼 &lt;b&gt;블로킹 I/O 작업&lt;/b&gt;은 libuv의 스레드 풀을 통해 백그라운드에서 처리됩니다. 이 과정에서 메인 스레드는 블로킹 없이 다른 작업을 계속 처리할 수 있어 &lt;b&gt;동시성&lt;/b&gt;을 높이고 &lt;b&gt;자원 활용&lt;/b&gt;을 최적화할 수 있습니다. 이는 CPU 자원을 많이 사용하지 않는 I/O 작업에서는 매우 효과적입니다.&lt;/p&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;CPU 집약적인 작업&lt;/b&gt;(예: 복잡한 계산, 대규모 데이터 처리)이 발생할 경우, Node.js의 단일 스레드 모델에서는 &lt;b&gt;병목 현상&lt;/b&gt;이 발생할 수 있습니다. 이때는 &lt;b&gt;Worker Threads&lt;/b&gt;를 사용하여 CPU 작업을 멀티 스레드 환경에서 병렬로 처리할 수 있습니다. 이를 통해 CPU 사용이 많은 작업도 효율적으로 처리하면서, Node.js의 성능을 극대화할 수 있습니다. 하지만 애초에 CPU 바운드 작업을 많이 처리해야 하는 서버라면, &lt;b&gt;다른 언어&lt;/b&gt;(예: C++, Go, Rust)로 작성하는 것이 더 적합할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 스레드 풀을 늘렸을 때 이점이 있는 작업의 구분&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 제3자 API 호출 작업 (예: 클라우드 API 호출)&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;b&gt;스레드 풀 영향 없음&lt;/b&gt;: 이러한 작업들은 주로 &lt;b&gt;네트워크 I/O&lt;/b&gt;를 사용하며, 커널의 비동기 I/O 기능을 통해 처리되기 때문에 &lt;b&gt;libuv의 스레드 풀과 무관&lt;/b&gt;합니다. 네트워크 요청은 논블로킹으로 처리되며, 많은 요청을 처리하는 상황에서도 스레드 풀을 늘린다고 해서 큰 이점을 얻을 수는 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 경우에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컨테이너 기반 스케일 아웃&lt;/b&gt;이 성능 향상에 더 효과적입니다. 여러 인스턴스로 네트워크 요청을 분산 처리하여 부하를 줄이고, 대규모 트래픽에서도 안정적인 성능을 유지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. CPU 집약적인 작업 (예: 대규모 데이터 처리, 복잡한 계산)&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;b&gt;스레드 풀 영향 없음&lt;/b&gt;: 이와 같은 &lt;b&gt;CPU 집약적인 작업&lt;/b&gt;은 libuv의 스레드 풀에서 처리되지 않으며, 이러한 작업은 &lt;b&gt;Worker Threads&lt;/b&gt;를 통해 멀티스레드 방식으로 처리해야 성능을 극대화할 수 있습니다. 스레드 풀을 늘려도 CPU 작업에 대한 성능 향상은 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 내부 I/O 작업 (예: 파일 시스템, DNS 조회 등)&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;b&gt;스레드 풀 영향 있음&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;파일 시스템 접근&lt;/b&gt;이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;블로킹 I/O 작업&lt;/b&gt;은 libuv의 스레드 풀을 사용하여 처리됩니다. 스레드 풀을 늘리면 이러한 작업들을 더 많이 병렬로 처리할 수 있어, 파일 입출력이나 DNS 조회 작업을 많이 처리해야 할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;성능 향상&lt;/b&gt;을 기대할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 스레드 풀을 늘리는 것은&amp;nbsp;&lt;b&gt;블로킹 I/O 작업&lt;/b&gt;을 더 빠르게 병렬 처리할 때 유용하며, 네트워크 I/O와 같은 작업이나 CPU 바운드 작업에서는 스레드 풀을 늘려도 성능상의 큰 이점을 얻을 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js는 단일 스레드 모델임에도 비동기 처리 모델을 통해 놀라운 동시성과 성능을 제공합니다. 이는 libuv의 이벤트 루프와 스레드 풀 덕분이며, 파일 시스템과 같은 블로킹 I/O 작업에서 큰 효과를 발휘합니다. 그러나 모든 작업이 스레드 풀에 의존하지 않으며, 네트워크 I/O나 CPU 집약적인 작업은 다른 방식으로 처리됩니다. 따라서 대규모 데이터를 다룰 때는 스레드 풀의 크기 조정뿐만 아니라 &lt;b&gt;Worker Threads&lt;/b&gt;나 &lt;b&gt;스케일 아웃&lt;/b&gt; 전략을 적절히 사용하는 것이 중요하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Back/  Node.js</category>
      <category>liubv</category>
      <category>node.js</category>
      <category>nodejs</category>
      <category>비동기</category>
      <category>스레드 풀</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/289</guid>
      <comments>https://psychogrammer.tistory.com/289#entry289comment</comments>
      <pubDate>Wed, 2 Oct 2024 15:16:51 +0900</pubDate>
    </item>
    <item>
      <title>Software Design Decoded: 66 Ways Experts Think 후기</title>
      <link>https://psychogrammer.tistory.com/288</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;짤막한 느낀 점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도대체 전문가들은 어떤 식으로 사고하는지 궁금했다. 이 책을 읽으면서 66가지 주제로 사고방식을 엿볼 수 있었다. 카테고리별로 잘 나뉘어 있었고 문단이 길지 않아 읽기도 수월했다. &lt;b&gt;특히 좋았던 점은 내가 어떤 부분에서 잘하고 있는지와 부족한지가 뚜렷하게 구분되니깐 어디서 헤매고 있는지 진단하기 좋았다&lt;/b&gt;. 부족한 부분은 사실, 강점에서부터 비롯된 '트레이드오프' 현상인데 이를 고칠 수가 있나?라는 의문이 들기도 했다. 약점을 보완해 나가며 균형 잡힌 전문가가 될 것인지, 아니면 강점을 더욱 발전시켜 개성 넘치는 전문가가 될 것인지 고민하기도 했다. 그렇게 내린 결론은 &quot;전문가&quot;라는 직업 자체가 균형 잡힌 역량을 갖추는 것이 중요하다고 생각하기 때문에, 전자를 선택하고자 한다. 후자는 개인적인 활동에서 충분히 즐기도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;71vR6s2o7tL._AC_UF1000,1000_QL80_.jpg&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvIx16/btsJvtw1Z5t/ZZZ1QmHrYK7PKRqXIsyVdK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvIx16/btsJvtw1Z5t/ZZZ1QmHrYK7PKRqXIsyVdK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvIx16/btsJvtw1Z5t/ZZZ1QmHrYK7PKRqXIsyVdK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvIx16%2FbtsJvtw1Z5t%2FZZZ1QmHrYK7PKRqXIsyVdK%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;388&quot; height=&quot;434&quot; data-filename=&quot;71vR6s2o7tL._AC_UF1000,1000_QL80_.jpg&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;저자 소개&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Marian Petre&lt;/b&gt;는 &quot;전문가들의 생각을 분석&quot;하여 소프트웨어 설계 전문가들이 어떻게 전문성을 발휘하는지, 사람들이 설계와 문제 해결에 대해 어떻게 사고하고 소통하는지, 그리고 그들이 사고 과정에서 표현 방식을 어떻게 활용하는지를 탐구한다. 영국의 오픈 대학교에서 컴퓨팅 교수로 재직 중이며, 심리언어학과 컴퓨터 과학 학위를 보유하고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Andr&amp;eacute; van der Hoek&lt;/b&gt;는 본질적으로 프로그래머로서, 소프트웨어 설계자 및 개발자들과 대화하고 협력하여 그들이 더 효과적이고 효율적으로 일할 수 있도록 돕는 새로운 도구를 만드는 것을 좋아한다. 캘리포니아 대학교 어바인 캠퍼스의 정보학 교수이며, 비즈니스 중심 컴퓨터 과학과 컴퓨터 과학 학위를 보유하고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;❤️: 인상 깊게 읽었거나, 부족함을 느껴 더 노력하고 싶은 주제&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Experts keep it SIMPLE (1~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS prefer simple solution ❤️ &amp;rarr; 문제를 해결하는 데는 수많은 방법이 있다. 전문가는 그중 가장 단순한 걸 선택할 뿐이다.&lt;/li&gt;
&lt;li&gt;EXPERTS solve simpler problems first &amp;rarr; 먼저 간단한 문제를 해결하면서 복잡한 문제를 단순하게 만들어나간다.&lt;/li&gt;
&lt;li&gt;EXPERTS divide and conquer &amp;rarr; 쪼개서 이해함과 동시에 전체를 볼 줄 안다.&lt;/li&gt;
&lt;li&gt;EXPERTS do not overgeneralize &amp;rarr; 무작정 확장성을 고려해서 짜지 않는다.&lt;/li&gt;
&lt;li&gt;EXPERTS design elegant abstractions &amp;rarr; 추상화를 단지 만드는 게 아니라 설계해서 이를 통해서 고수준의 사고를 가능하게 한대.&lt;/li&gt;
&lt;li&gt;EXPERTS use metaphor &amp;rarr; 은유를 통해 바로 이미지로 이해시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Experts COLLABORATE (7~) ❤️❤️&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS prefer working with others &amp;rarr; 전문가는 은둔 고수가 아니다. 이들은 소통을 통해 영감과 통찰을 얻어간다.&lt;/li&gt;
&lt;li&gt;EXPERTS reach out ❤️ &amp;rarr; 다른 사람의 도움을 빠르면 빠를수록 먼저 요청한다. 난 다른 사람의 리소스를 소모시키지 않겠다는 생각에 읽어도 큰 도움 안 되는 코드를 파악해 가며 도메인을 파악해 갔는데 이는 좋지 않은 전략이었다.&lt;/li&gt;
&lt;li&gt;EXPERTS check with others continually ❤️ &amp;rarr; 확실히 내가 소통 능력에 약하다는 걸 깨달았다. 전문가들은 짬 내서 소통하는 걸 잘하고 이를 익숙하게 받아들인다고 한다.&lt;/li&gt;
&lt;li&gt;EXPERRS involve the user &amp;rarr; 사용자가 말하는 것을 그대로 받아들이지 않고 그 너머를 본다는 것은 내가 하는 것과 일맥상통하네. 진짜 이게 중요했음.&lt;/li&gt;
&lt;li&gt;EXPERTS socially embed and reinforce food practice &amp;rarr; 집단내에서의 네트워킹이 마련되어 있는 것도 중요하구나&lt;/li&gt;
&lt;li&gt;EXPRESTS agree to disagree &amp;rarr; 의견불일치를 받아들이고 다른 부분을 먼저 집중하고 그 후 얻어지는 추가 정보로 다시 의논해 나간다는 것이 인상 깊네.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS KEEP GOING (63~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS design all the time &amp;rarr; 어쩌다 하는 게 아니라 그냥 습관적으로 몰두해서 하는 것임&lt;/li&gt;
&lt;li&gt;EXPERTS know design is not done until the code is delivered and running &amp;rarr; 코드의 품질은 사용성 평가에 따라 달린 것이기 때문이다&lt;/li&gt;
&lt;li&gt;EXPERTS keep learning &amp;rarr; 전문성을 당연하게 여기지 않고 계속해서 배우려고 한다.&lt;/li&gt;
&lt;li&gt;EXPERTS play &amp;rarr; 영감과 흥미를 불어 일으킬만한 것들을 계속해서 가지고 논다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS REFLECT (58~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS curtail digressions &amp;rarr; 전문가들은 완벽한 기계가 아니다. 뭔가 잘못된 상황을 주기적으로 캐치하고 다시 되돌아가는 힘이 있는 사람들이다.&lt;/li&gt;
&lt;li&gt;EXPERTS retain their orientation &amp;rarr; 많은 회의, 수많은 의사결정들 속에서 방황하지 않고 나아가야 할 방향을 정해놓고 기억하고 있는다.&lt;/li&gt;
&lt;li&gt;EXPERTS think about they are not designing &amp;rarr; 이거 난데? 이 파트는 충분히 잘하고 있구나. 자부심을 갖자.&lt;/li&gt;
&lt;li&gt;EXPERTS re-assess the landscape &amp;rarr; 충분히 잘하고 있고, 너무 과하지 않게 다른 쪽 파트를 보충함으로써 균형을 맞추자&lt;/li&gt;
&lt;li&gt;EXPERTS invest now to save effort later &amp;rarr; 미래의 일을 예상하고 지금 예방한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS TEST (51~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS are skeptical &amp;rarr; 그렇기 때문에 테스트를 꼭 한다&lt;/li&gt;
&lt;li&gt;EXPERTS simulate continually &amp;rarr; 시뮬레이션을 계속 돌려보는 건 나도 마찬가지!&lt;/li&gt;
&lt;li&gt;EXPERTS draw examples alongside their diagrams &amp;rarr; 아키첵처 설계는 수동적이라고 생각하고 이에 들어맞는 예시를 생각한다&lt;/li&gt;
&lt;li&gt;EXPERTS test across representations &amp;rarr; 각각의 뷰들은 제약이 있기 때문에 전부 내려다봄으로써 점검한다&lt;/li&gt;
&lt;li&gt;EXPERTS prototype concepts &amp;rarr; 추상적인 것과 상호작용하는 것의 한계를 알기 때문에 어떻게든 일단 눈에 보이는 구체적인 프로토타입을 만들고 이를 통해 피드백한다.&lt;/li&gt;
&lt;li&gt;EXPERTS play the fool ❤️ &amp;rarr; 아무래도 담당자이다 보면 매몰되기 마련이다. 아무것도 모른다고 가정한 상태에서 재평가하는 것도 도움이 될 듯!&lt;/li&gt;
&lt;li&gt;EXPERTS are alert to evidence that challenges their theory ❤️ &amp;rarr; 나는 정상이었다. 아무래도 내가 만든 설계다 보니 이게 맞는지 의심이 가는 건 당연한 거였고, 이를 반증하는 사례에 대해 귀를 기울이는 상태는 전문가들도 하는 좋은 자세였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS ITERATE(44~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS repeat activities &amp;rarr; 내가 그린 설계도, 다이어그램, 아키텍처. 그냥 순순히 따른다기보다, 백지에서 여러 번 그려보는 것도 최선의 방법이 나올 수도 있겠다. AI적 사고 시행 횟수를 좀 더 늘리는 것을 받아들여볼까&lt;/li&gt;
&lt;li&gt;EXPERTS generate alternatives &amp;rarr; 대안을 만들 시간까지 현업에서 주어질까.. 시스템이 거대하면 필수적일 것 같긴 하다.&lt;/li&gt;
&lt;li&gt;EXPERTS explore different prespectives &amp;rarr; 구현, 배포, 유용성, 그 외 인간적 관점까지 고려하는 게 전문가다&lt;/li&gt;
&lt;li&gt;EXPERTS move among levels of abstraction &amp;rarr; 높은 추상화 단계에서 설계하고 낮은 추상화 단계에서 기술적 부분을 챙긴다&lt;/li&gt;
&lt;li&gt;EXPERTS rotate among subject pairs &amp;rarr; 전문가는 단지 뚝딱 뚝딱이 아니라, 예술이네.. 문제쌍을 가지고 싸이클 돌면서 마주하면 진전이 더 빠르다는 걸 알고 있음.&lt;/li&gt;
&lt;li&gt;EXPERTS change notation deliberately &amp;rarr; 표현방식을 달리한다라&amp;hellip; 시야를 다르게 보는 유용한 방법론이 될 수 있겠구먼&lt;/li&gt;
&lt;li&gt;EXPERTS pause &amp;rarr; 전문가는 맺고 끊을 줄 안다. 무지성으로 집착적으로 일이 안 풀린다고 늘어지지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS ARE NOT AFRAID(37~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS focus on the essence ❤️❤️ &amp;rarr; 전문가들은 먼저 본질에 집중하고, 주변적인 부분의 디자인에 대해서는 노력을 늦춘다. &lt;b&gt;갓챠!!&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;EXPERTS address knowledge deficiencies &amp;rarr; 가정을 할 수밖에 없지만 이건 지식의 부족이라고 알고 있고, 가정이 맞는지 최선을 다한다.&lt;/li&gt;
&lt;li&gt;EXPERS go as deep as needed &amp;rarr; 추상화와 구현의 일치를 통해 엄격함과 정확성을 유지한다.&lt;/li&gt;
&lt;li&gt;EXPERTS try the opposite&lt;/li&gt;
&lt;li&gt;EXPERST do something (else) &amp;rarr; 교착상태에 빠질 때 그냥 잠시동안 초점을 달리하는 것도 최선의 방법이다.&lt;/li&gt;
&lt;li&gt;EXPERTS know when to stop &amp;rarr; 이득의 증가량이 감소할 때를 귀신같이 찾아내서 중단한대&lt;/li&gt;
&lt;li&gt;EXPERTS build their own tools &amp;rarr; 장인은 도구도 잘 다룬다~&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS WORK WITH UNVETAINTY(30~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS keep options open ❤️ &amp;rarr; 결정을 확고히 하는 빨리 정하고자 하는 건 본능이다. 본능을 거스르고 옵션을 오픈하는 게 전문가다.&lt;/li&gt;
&lt;li&gt;EXPERTS make provisional decision &amp;rarr; 전문가들도 일단 잡고 달린다.&lt;/li&gt;
&lt;li&gt;EXPERTS see error as opportunity &amp;rarr; 오류, 장애를 통해 배우면 그만이다. 버그가 꼭 개인의 실책을 수반하지 않는다.&lt;/li&gt;
&lt;li&gt;EXPERTS know how things work &amp;rarr; 사고방식 못지않게 &amp;ldquo;지식&amp;rdquo; 역시 중요한 듯&lt;/li&gt;
&lt;li&gt;EXPERTS make tradeoffs &amp;rarr; 최선이란 존재하지 않고 여러 후보군들 중 종합평가 1위를 선택하면 그만이다&lt;/li&gt;
&lt;li&gt;EXPERTS prioritize among stakeholders ❤️ &amp;rarr; EXPERTS는 Engineer인 동시에 Employee이기 때문에 이거 역시 중요하다&lt;/li&gt;
&lt;li&gt;EXPERTS adjust to the degree of uncertainity present &amp;rarr; 불확실성에 따라 업무의 강도가 정해지는 듯&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS SKETCH(24~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS externalize their thoughts &amp;rarr; 생각을 그림으로 자주 그린대. 생각을 말, 그림, 등등의 표현으로 자꾸 발산하는 게 검증과 발전의 기회라는 걸 앎.&lt;/li&gt;
&lt;li&gt;EXPERTS draw the problem as much as they draw the solution &amp;rarr; 문제상황을 해결방법만큼이나 중요하게 생각해서 그린다.&lt;/li&gt;
&lt;li&gt;EXPERTS draw what they need and no more &amp;rarr; 필요한 만큼만 그리지 막 세심하게 그리진 않는다. 노력을 낭비하지 않는다.&lt;/li&gt;
&lt;li&gt;EXPERTS shift between formal and informal &amp;rarr; 정석, 비정석적인 방법으로 다이어그램을 모두 잘 다룰 줄 알며 상황에 따라 다르게 선택한다.&lt;/li&gt;
&lt;li&gt;EXPERTS invent notation &amp;rarr; 새로운 프로젝트에 어울리는 독창적인 표기법을 알아서 만들고 쓸 줄 안다.&lt;/li&gt;
&lt;li&gt;EXPERTS keep sketches &amp;rarr; 이건 좀 납득이 안 가네. 보관을 잘해도 꺼내다 보지 않으면 말짱 도루묵인데. 암튼 언젠가 쓰일 걸 알고 보관한대.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Experts BORROW (13~)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS prefer solutions that they know work ❤️ &amp;rarr; 이미 검증된 해결책을 좋아한다.&lt;/li&gt;
&lt;li&gt;EXPRETS look around &amp;rarr; 어떤 기술을 적용할지 다른 기술 구경도 많이 한대&lt;/li&gt;
&lt;li&gt;EXPERTS take inspiration from wherever they can &amp;rarr; 관찰을&amp;nbsp;많이 하며 아이디어를 수집하고 다닌다&lt;/li&gt;
&lt;li&gt;EXPERTS use analogy &amp;rarr; 다루기 힘든 문제는 유추를 한대&lt;/li&gt;
&lt;li&gt;EXPERTS use design methods SELECTIVELY ❤️ &amp;rarr; 새로운 설계 방법을 발명하려고 하지 않는다. 이미 발명된 걸 쓰되 한계점을 인지하고 있는다.&lt;/li&gt;
&lt;li&gt;EXPERTS network ❤️❤️ &amp;rarr; 모든 걸 알 수 없다고 인정한다. 대신 네트워킹을 통해 전문 지식을 보충한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EXPERTS BREAK THE RULES&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXPERTS relax constraints &amp;rarr; 필요에 따라, 상황에 따라, 단계에 따라 제약 조건을 내려놓기도 한다.&lt;/li&gt;
&lt;li&gt;EXPERTS reshape the problem space &amp;rarr; 갑자기 든 생각. 이것도 개발 경험, 트러블 슈팅 경험이 있어야 공감이 되고 읽히겠다고 느낌. 암튼 문제를 재구성한다는 추상적인 말이 너무 뻔하게 느껴지지만 그렇대.&lt;/li&gt;
&lt;li&gt;EXPERTS use notations as lenses, rather than starightjacktes ❤️ &amp;rarr; 이거 내가 잘 못하는 부분임. 도구를 자유자재로 활용해야지 도구에 빠져버리면 안 됨.&lt;/li&gt;
&lt;li&gt;EXPERTS design throughout the creation of software &amp;rarr; 설계는 단순히 구현물로 변환되어야 하는 &amp;lsquo;전&amp;rsquo; 단계가 아니라 모든 걸 포함한다.&lt;/li&gt;
&lt;li&gt;EXPERTS do not feel obliged to use things as intended &amp;rarr; 도구를 의도대로만 쓰지 않고 필요에 의해 조합해서 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.amazon.com/Software-Design-Decoded-Experts-Think/dp/0262035189&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.amazon.com/Software-Design-Decoded-Experts-Think/dp/0262035189&lt;/a&gt;&lt;/p&gt;</description>
      <category>  Theory/  Architecture</category>
      <category>Architecture</category>
      <category>Design</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/288</guid>
      <comments>https://psychogrammer.tistory.com/288#entry288comment</comments>
      <pubDate>Sun, 8 Sep 2024 15:52:35 +0900</pubDate>
    </item>
    <item>
      <title>React-19 useActionState 살펴보기</title>
      <link>https://psychogrammer.tistory.com/287</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Action&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;: 비동기 전환을 사용하는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(Before) Example1. passive process&lt;/h2&gt;
&lt;pre id=&quot;code_1716708731880&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function OrderCoffee({}) {
  const [coffee, setCoffee] = useState(&quot;&quot;);
  const [error, setError] = useState(null);
  const [isPending, setIsPending] = useState(false);

  const handleSubmit = async () =&amp;gt; {
    setIsPending(true);
    const error = await orderCoffee(coffee);
    setIsPending(false);
    if (error) {
      setError(error);
      return;
    } 
    redirect(&quot;/order-success&quot;);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input value={coffee} onChange={(event) =&amp;gt; setCoffee(event.target.value)} /&amp;gt;
      &amp;lt;button onClick={handleSubmit} disabled={isPending}&amp;gt;
        Order Coffee
      &amp;lt;/button&amp;gt;
      {error &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{error}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;데이터를 변경하고, 이에 대응하여 상태를 업데이트할 때, 예를 들어, 사용자가 이름을 변경하는 양식을 제출할 때 API 요청을 수행한 후 응답을 처리하는 경우 과거에는 보류 상태, 오류, 낙관적 업데이트, 순차적 요청 등을 수동으로 처리해야 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;(react-19) Example2. async transition&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1716708878219&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Using pending state from Actions
function OrderCoffee({}) {
  const [coffee, setCoffee] = useState(&quot;&quot;);
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () =&amp;gt; {
    startTransition(async () =&amp;gt; {
      const error = await orderCoffee(coffee);
      if (error) {
        setError(error);
        return;
      } 
      redirect(&quot;/order-success&quot;);
    })
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input value={coffee} onChange={(event) =&amp;gt; setCoffee(event.target.value)} /&amp;gt;
      &amp;lt;button onClick={handleSubmit} disabled={isPending}&amp;gt;
        Order Coffee
      &amp;lt;/button&amp;gt;
      {error &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{error}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;async transition은 즉시 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt; 상태를 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;로 설정하고, 비동기 요청을 수행한 후, 모든 전환이 끝나면 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;을 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;로 전환합니다. 이를 통해 데이터가 변경되는 동안 현재 UI를 &lt;span style=&quot;background-color: #ffffff; color: #23272f; text-align: start;&quot;&gt;responsive and interactive할 수 있게 만들어줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useTransition`은 React의 동시성 모드를 활용하여 상태 업데이트를 비동기적으로 처리하는 기능으로, 내부적으로 React의 스케줄러와 Fiber 아키텍처를 사용하여 작업의 우선 순위를 관리하고, 메인 스레드가 차단되지 않도록 합니다. 이를 통해 사용자 인터페이스의 반응성을 유지하면서 복잡한 상태 업데이트를 효율적으로 처리할 수 있습니다.&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #23272f; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #0d0d0d;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;(react-19) Example3. useActionState&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1716709087219&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Using &amp;lt;form&amp;gt; Actions and useActionState
function OrderCoffee({ coffee, setCoffee }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) =&amp;gt; {
      const error = await orderCoffee(formData.get(&quot;coffee&quot;));
      if (error) {
        return error;
      }
      redirect(&quot;/order-success&quot;);
      return null;
    },
    null,
  );

  return (
    &amp;lt;form action={submitAction}&amp;gt;
      &amp;lt;input type=&quot;text&quot; name=&quot;coffee&quot; /&amp;gt;
      &amp;lt;button type=&quot;submit&quot; disabled={isPending}&amp;gt;Order Coffee&amp;lt;/button&amp;gt;
      {error &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{error}&amp;lt;/p&amp;gt;}
    &amp;lt;/form&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;액션(Actions)을 기반으로, 리액트 19는 일반적인 액션 케이스를 처리하기 위한 새로운 훅 React.useActionState를 도입했습니다. &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;액션은 데이터를 제출하는 작업을 자동으로 관리해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;useActionState&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;는 함수(&amp;ldquo;액션&amp;rdquo;)를 인수로 받아, 호출할 래핑된 액션을 반환합니다. 이는 액션들이 조합되기 때문에 작동합니다. 래핑된 액션이 호출되면, &lt;/span&gt;useActionState&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;는 액션의 마지막 결과를 데이터로 반환하고, 액션의 보류 상태를 보류로 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;(react-19) Example4. 실제 사용 예시&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1716709689317&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) =&amp;gt; {
      if (!formData) {
        setCoffeeImage(null);
        return null;
      }
      const coffeeName = formData.get(&quot;coffee&quot;);
      const shot = formData.get(&quot;shot&quot;);
      const temperature = formData.get(&quot;temperature&quot;);
      const coffeeSrc = await orderCoffee(coffeeName, temperature, shot);

      if (!coffeeSrc) {
        return &quot;Failed to fetch coffee image&quot;;
      }

      setCoffeeImage(coffeeSrc);
      return null;
    },
    null,
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;React.useActionState는 이전에는 canary release 에서 ReactDOM.useFormState라고 불렸으나, 이름이 변경되었고 useFormState는 폐기되었습니다.&lt;/p&gt;</description>
      <category>  Front/⚛️ React JS</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/287</guid>
      <comments>https://psychogrammer.tistory.com/287#entry287comment</comments>
      <pubDate>Sun, 26 May 2024 16:48:37 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 내의 자바스크립트 동작 원리</title>
      <link>https://psychogrammer.tistory.com/235</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGFVjw/btrtYcYpASx/cePhbV8whvAVAUYTzxxICk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGFVjw/btrtYcYpASx/cePhbV8whvAVAUYTzxxICk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGFVjw/btrtYcYpASx/cePhbV8whvAVAUYTzxxICk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGFVjw%2FbtrtYcYpASx%2FcePhbV8whvAVAUYTzxxICk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;406&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. JavaScript Runtime 구성 요소&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브라우저 내의 JavaScript *런타임은 크게 &lt;b&gt;&lt;u&gt;JavaScript 엔진&lt;/u&gt;&lt;/b&gt;과 &lt;u&gt;&lt;b&gt;웹 API&lt;/b&gt;&lt;/u&gt;로 구성되어있다. 그리고 여기에 &lt;b&gt;테스크 큐&lt;/b&gt;와&amp;nbsp;&lt;b&gt;이벤트 루프&lt;/b&gt;라는 개념 또한 존재한다. 이를 통해 &lt;b&gt;싱글&amp;nbsp;쓰레드 기반&lt;/b&gt;의 프로그래밍 언어라 불리는 JavaScript도 &lt;b&gt;비동기&lt;/b&gt;적으로 코드를 실행시킬 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*런타임:&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 프로그래밍 언어로 작성된 코드가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;구동되는 환경&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처.PNG&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b23Pk6/btrs9h05GJP/KCJv4n2KkyUUA09k9FN6K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b23Pk6/btrs9h05GJP/KCJv4n2KkyUUA09k9FN6K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b23Pk6/btrs9h05GJP/KCJv4n2KkyUUA09k9FN6K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb23Pk6%2Fbtrs9h05GJP%2FKCJv4n2KkyUUA09k9FN6K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;248&quot; data-filename=&quot;캡처.PNG&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;2. JavaScript 엔진&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JavaScript 코드를 읽고, 해석, 실행을 담당하는 &lt;b&gt;인터프리터&lt;/b&gt;이다. 쓰레드 하나로만 동작하기 때문에&lt;b&gt; 스택 영역도 단 하나&lt;/b&gt;만 사용하게 된다. 이로 인해 하나의 함수가 &lt;b&gt;실행이 끝나기&amp;nbsp;전에는&lt;/b&gt; 다른 함수가 실행될 수 없는 &lt;b&gt;&quot;Run to Completion&quot;&lt;/b&gt; 특성을 가지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;JavaScript 엔진의 메모리 모델의 두 가지 영역&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1) 힙 영역&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리를 관리하는 영역&lt;/li&gt;
&lt;li&gt;동적으로 생성되는 &lt;b&gt;객체&lt;/b&gt;들을 할당하는 곳&lt;/li&gt;
&lt;li&gt;구조화되지 않은 넓은 메모리 영역을 지칭한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;ex) 변수 할당시 관련 정보 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2) 스택 영역&lt;/b&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;b&gt;실행할 테스크&lt;/b&gt;를 관리하는 영역&lt;/li&gt;
&lt;li&gt;함수가 실행될 때마다 해당 스택 프레임이 푸시되고,&lt;/li&gt;
&lt;li&gt;함수가 종료될 때마다 해당 스택 프레임이 팝 되는 영역을 지칭한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;ex) 함수 호출 시 스택에 쌓임&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYlPzw/btrtWtzB6Lm/oswUEbDAmD9GTiQWak9Gc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYlPzw/btrtWtzB6Lm/oswUEbDAmD9GTiQWak9Gc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYlPzw/btrtWtzB6Lm/oswUEbDAmD9GTiQWak9Gc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYlPzw%2FbtrtWtzB6Lm%2FoswUEbDAmD9GTiQWak9Gc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;235&quot; height=&quot;303&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&amp;nbsp;스택 영역에 푸시되는 함수의 스택 프레임은 그 함수의 &lt;b&gt;실행 맥락(Execution Context)&lt;/b&gt;을 의미한다. 실행 맥락이란, 해당 함수를 실행하기 위해 필요한 각종 정보들(ex. 지역 변수)의 집합이다. &lt;b&gt;JavaScript 엔진은 JavaScript 코드의 실행을 시작함과 동시에 전역 실행 맥락(Global Execution Context)을 스택 영역에 푸시&lt;/b&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;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt; JavaScript 코드의 &lt;b&gt;실행이 전부 다 끝나기 전까지는&lt;/b&gt; 전역 실행 맥락이 스택 영역에서 팝 되지 않고 남아 있다. 이를 기억하고 있어야 뒤에서 설명할 &quot;스택이 비는 순간&quot;이라는 말의 의미를 조금 더 명확히 이해할 수 있다. &lt;b&gt;즉, 스택이 비는 순간이라는 것은 결국 더 이상 실행할 JavaScript 코드가 없어서 전역 실행 맥락까지 스택 영역에서 팝이 된 경우&lt;/b&gt;를 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2MuTD/btrkS8EXpix/FYoSDzMHBXACURAj6CQxyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2MuTD/btrkS8EXpix/FYoSDzMHBXACURAj6CQxyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2MuTD/btrkS8EXpix/FYoSDzMHBXACURAj6CQxyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2MuTD%2FbtrkS8EXpix%2FFYoSDzMHBXACURAj6CQxyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;268&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. 비동기 방식 Event loop로 Javascript의 동시성을 지원한다.&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;903&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OuEbC/btrk7uzBh4j/ZAKHIxCoCXnOZGcVMcfSuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OuEbC/btrk7uzBh4j/ZAKHIxCoCXnOZGcVMcfSuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OuEbC/btrk7uzBh4j/ZAKHIxCoCXnOZGcVMcfSuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOuEbC%2Fbtrk7uzBh4j%2FZAKHIxCoCXnOZGcVMcfSuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;322&quot; data-origin-width=&quot;903&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이벤트 루프(Event Loop)&lt;/b&gt;를 이용해서&amp;nbsp;&lt;b&gt;callback queue(브라우저에서 발생한 이벤트) 를 call stack에 보낸&lt;/b&gt;다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;call stack을 지켜보고있다가, 스택이 비는 순간 callback queue에서 callback function을 꺼내서 실행시켜주는 일만 한다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이벤트 루프는 계속 stack의 empty 여부만을 신경쓰기 때문에, &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;비동기 방식으로 실행중인 태스크가 call stack에 있는지, 없는지를 확인&lt;/b&gt;한다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브라우저 입장에서는 single thread 로 처리중인 javascript가 마치 동시에 처리되고 있는 것 처럼 보인다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구동환경 애니메이션 보기: &lt;a href=&quot;https://www.jsv9000.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jsv9000.app/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4. Web APIs&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Web api란?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;웹 API는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JavaScript 엔진의 바깥에 위치한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Ajax 요청, setTimeout(), 이벤트 핸들러의 등록과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;웹 브라우저에서 제공하는 기능&lt;/b&gt;들을 말한다. 그런데 중요한 것은&amp;nbsp;&lt;b&gt;이러한 요청들의 처리가 JavaScript 엔진의 쓰레드와는 다른 쓰레드들에서 이뤄진다&lt;/b&gt;는 점이다. JavaScript 엔진의 스택에서 실행된 비동기 함수가 요청하는 비동기 작업에 대한 정보와 콜백 함수를 웹 API를 통해 브라우저에게 넘기면,&amp;nbsp;&lt;b&gt;브라우저는 이러한 요청들을 별도의 쓰레드에 위임&lt;/b&gt;하게 되는 것이다. 그러면&amp;nbsp;&lt;b&gt;그 쓰레드는 해당 요청이 완료되는 순간 전달받았던 콜백 함수를 JavaScript 엔진의 태스크 큐라는 곳에 집어넣는다.&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OABjO/btrkPHniO73/xu9ayBGMVgxrZXXkKqSaZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OABjO/btrkPHniO73/xu9ayBGMVgxrZXXkKqSaZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OABjO/btrkPHniO73/xu9ayBGMVgxrZXXkKqSaZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOABjO%2FbtrkPHniO73%2Fxu9ayBGMVgxrZXXkKqSaZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;402&quot; height=&quot;577&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어, setTimeout() 함수가 실행되면 JavaScript 엔진은 웹 API를 통해 브라우저에게 setTimeout() 작업을 요청하면서 콜백 함수를 전달하고, 브라우저는 이러한 타이머 작업을 별도의 쓰레드에게 위임한다. 그러고 나면 JavaScript 엔진의 스택에서는 setTimeout() 함수의 스택 프레임이 즉시 팝 된다. 그리고 인자로 명시한 시간이 흐르고 나면 해당 타이머 작업을 처리하고 있던 쓰레드는 전달받았던 콜백 함수를 JavaScript 엔진의 태스크 큐에 집어넣게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;5. 이벤트 루프 (Event Loop)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한편, 위와 같은 동작이 가능하려면&amp;nbsp;&lt;b&gt;매 순간 스택이 비어있는지 여부와 태스크 큐에 콜백 함수가 기다리고 있는지 여부를 확인&lt;/b&gt;해야만 한다. 이러한 역할을 수행하는 것이 바로&amp;nbsp;&lt;b&gt;이벤트 루프&lt;/b&gt;이다. 즉, 이벤트 루프는 매 순간 스택이 비어있는지 확인을 해서 스택이 비어있다면 태스크 큐에 콜백 함수가 들어올 때까지 기다렸다가 첫 번째로 들어오는 콜백 함수를 스택에 쌓는 역할을 한다. 만약 스택이 비어있지 않다면 일반적인 방식으로 스택에서 함수의 호출을 처리하게 된다. 결국 이벤트 루프는 이름 그대로 같은 작업을 무한히 반복하는 무한 루프라고 생각하면 이해하기 쉬울 것이다. 참고로 이렇게 매 순간 태스크 큐와 스택을 확인하는 작업을&amp;nbsp;&lt;b&gt;틱(Tick)&lt;/b&gt;이라고 부른다. 이벤트 루프의 동작 방식을 가상의 코드로 설명한다면 다음과 같다고 한다(출처 : MDN).&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1636962094907&quot; class=&quot;cpp&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (queue.waitForMessage()) {
    queue.processNextMessage();
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;The event loop facilitates this process; it constantly checks &lt;b&gt;whether or not the call stack is empty&lt;/b&gt;. If it is empty, new functions &lt;b&gt;are added from the event queue.&lt;/b&gt; If it is not, then the current function call is processed. (발췌)&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 이벤트 루프 기반 비동기 처리 모델&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 팀에게 작업을 지시한 후, 그 팀들이 작업을 완료하면 결과를 받아보고 후속 조치를 하는 것과 비슷합니다. 메인 스레드(매니저)는 실제 작업을 하지 않고, &lt;b&gt;작업을 분배&lt;/b&gt;하고 결과를 받아 처리하는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;메인 스레드는 &lt;b&gt;작업을 백그라운드로 던져줌&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;각 작업이 완료되면 &lt;b&gt;콜백을 통해 다시 메인 스레드에서 처리&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;병렬 처리&lt;/b&gt;: 메인 스레드가 실제로 무거운 작업을 직접 하지 않기 때문에, 여러 비동기 작업들이 백그라운드에서 병렬로 처리될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 작업의 효율적 관리&lt;/b&gt;: 메인 스레드는 &lt;b&gt;이벤트 루프&lt;/b&gt;를 통해 여러 비동기 작업이 완료될 때마다 콜백을 순서대로 처리하여 효율적인 실행 흐름을 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메인 스레드의 핵심 역할&lt;/b&gt;은 &lt;b&gt;작업을 외부 쓰레드에 위임하고&lt;/b&gt; 그 작업이 완료되면 &lt;b&gt;콜백으로 처리하는 것&lt;/b&gt;입니다. 이를 통해 자바스크립트는 단일 스레드 모델을 유지하면서도, &lt;b&gt;비동기적인 비효율을 최소화&lt;/b&gt;하고, &lt;b&gt;동시성&lt;/b&gt;을 활용하여 복잡한 작업을 효율적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;웹 브라우저&lt;/b&gt;는 다양한 작업을 &lt;b&gt;최대한 역할별로 여러 쓰레드에 분산&lt;/b&gt;시켜 병렬 처리함으로써 성능을 최적화합니다.최종적으로 자바스크립트의 &lt;b&gt;단일 스레드&lt;/b&gt;는 특정한 핵심 역할을 맡고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. 브라우저의 여러 쓰레드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 성능과 효율성을 위해 여러 쓰레드를 사용하여 특정 작업을 비동기적으로 처리합니다. 예를 들면:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;렌더링 엔진&lt;/b&gt;: 레이아웃 계산, 페인팅, 컴포지팅 작업 등을 처리하는 렌더링 관련 쓰레드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 쓰레드&lt;/b&gt;: 네트워크 요청과 응답 처리 (XHR, Fetch API 등).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입출력(I/O) 쓰레드&lt;/b&gt;: 파일 읽기/쓰기, 데이터베이스 접근 등과 같은 I/O 작업.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타이머 쓰레드&lt;/b&gt;: setTimeout, setInterval 같은 타이머 작업을 비동기적으로 관리.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GPU 쓰레드&lt;/b&gt;: 그래픽 렌더링, 하드웨어 가속 작업 등을 담당하는 GPU 관련 쓰레드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Compositor 쓰레드&lt;/b&gt;: 화면에 여러 레이어를 조합해 최종적인 결과물을 렌더링하는 쓰레드.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도, 브라우저는 비동기 작업을 효율적으로 처리하기 위해 여러 백그라운드 쓰레드를 활용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저의 메인 스레드&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;작업 위임&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 스레드는 시간이 오래 걸리는 작업(예: 네트워크 요청, 파일 입출력, 렌더링 등)을 &lt;b&gt;해당하는 쓰레드에 위임&lt;/b&gt;합니다. 예를 들어, XHR이나 fetch 요청을 보내면, 메인 스레드는 해당 요청을 네트워크 쓰레드로 넘기고 즉시 다음 작업을 수행할 수 있습니다.&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;b&gt;메인 스레드는 즉시 다른 코드&lt;/b&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;다른 쓰레드에서 비동기 작업이 완료되면, 해당 작업의 &lt;b&gt;결과는 콜백 형태로 메인 스레드에 전달&lt;/b&gt;됩니다. 이 때 메인 스레드는 &lt;b&gt;Task Queue&lt;/b&gt;나 &lt;b&gt;Microtask Queue&lt;/b&gt;에 있는 콜백 함수를 차례대로 실행합니다.&lt;/li&gt;
&lt;li&gt;예를 들어, setTimeout이나 Promise가 완료되면 그 콜백이 메인 스레드에서 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DOM 조작 및 UI 업데이트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 스레드는 &lt;b&gt;DOM&lt;/b&gt; 조작과 &lt;b&gt;UI 업데이트&lt;/b&gt;도 담당합니다. 사용자가 버튼을 클릭하거나 폼을 제출하는 등의 UI 이벤트를 처리하고, 그에 따른 DOM 변경 작업도 메인 스레드에서 이루어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 &lt;b&gt;다양한 작업을 병렬로 처리&lt;/b&gt;하지만, &lt;b&gt;자바스크립트 단일 스레드&lt;/b&gt;는 비동기 작업이 완료된 후 큐에 대기 중인 작업들을 순차적으로 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;병목 현상 방지&lt;/b&gt;: 브라우저의 여러 쓰레드는 네트워크 요청, 렌더링, I/O 작업 등 시간이 오래 걸리는 작업을 병렬로 처리하고, 자바스크립트 엔진의 단일 스레드는 완료된 비동기 작업의 콜백만 처리하므로 병목 현상을 줄입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;8. 태스크 큐 (Task Queue)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;태스크 큐는 웹 API를 처리하고 있던 쓰레드로부터 전달받은 콜백 함수들을 FIFO(First-In First-Out) 구조로 저장하고 있는 일종의 큐(Queue)&lt;/b&gt;로,&amp;nbsp;&lt;b&gt;JavaScript 엔진 자체에 포함&lt;/b&gt;되어 있는 부분이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;여기에 저장된 콜백 함수들은 스택이 비는 순간 스택에 순서대로 푸시&lt;/b&gt;된다. 이러한 원리로 비동기 작업이 완료된 이후 콜백 함수가 실행되는 것이다. 그런데 비동기 작업이 완료되어 태스크 큐에 콜백 함수가 들어가 있더라도 스택이 비어있지 않다면 해당 콜백 함수가 바로 실행되지 못한다는 특징이 있다. 그래서 setTimeout() 함수도 인자로 명시한 시간은 '잠드는 최소 시간'일 뿐, 그것보다 더 오래 잠들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;*&lt;/span&gt;여기서 말하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;태스크란 곧 콜백 함수를 의미하기 때문에,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;태스크 큐&lt;/b&gt;는&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;콜백 큐&lt;/b&gt;(Callback Queue)&lt;span style=&quot;color: #000000;&quot;&gt;라고 부르고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이벤트 큐&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메세지 큐&lt;/b&gt;라고도 부른다. (콜스택이 비어있을 경우에 등록된 함수를 콜스택에 추가)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;9. 잡 큐 (Job Queue)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ECMA 2015 에서 나온 개념으로 Promises에 의해 사용된다. promises는 async 함수를 콜스택에 넣는 대신에 가능한 빨리 실행하도록 하는 방법이다. Promises 는 현재 함수가 끝나기 전에 resolve 된후 현재 함수가 끝나자마자 바로 실행된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;job queue는 event queue 보다 우선 권을 가지고 있어 event queue에 쌓인 함수보다 먼저 실행된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(상위함수가 종료되기 전에 콜스택이 비어있지 않더라도 잡큐에 등록된 콜백을 콜스택에 추가)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729770589644&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const task = () =&amp;gt; console.log('task');
const job = () =&amp;gt; console.log('job');

const run = () =&amp;gt; {
  console.log('callStack');
  setTimeout(task, 0);
  new Promise((resolve, reject) =&amp;gt; resolve('job'))
  	.then((resolve) =&amp;gt; job());
};

run();

// 'callStak'
// 'job'
// 'task'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 대부분의 무거운 작업(렌더링, 네트워크, I/O 등)을 &lt;b&gt;별도의 쓰레드&lt;/b&gt;로 분리하여 비동기적으로 처리하고, 그 결과를 &lt;b&gt;단일 자바스크립트 스레드&lt;/b&gt;에서 &lt;b&gt;콜백 함수&lt;/b&gt;로 처리합니다. 단일 스레드는 자바스크립트 코드 실행, 이벤트 루프 관리, DOM 조작 등을 담당하며, 비동기 작업의 콜백을 순차적으로 실행하여 브라우저가 매끄럽게 동작할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 스레드(자바스크립트의 단일 스레드)**의 주된 역할은 여러 작업을 적절한 &lt;b&gt;백그라운드 쓰레드&lt;/b&gt;나 &lt;b&gt;시스템 작업자&lt;/b&gt;에게 넘겨주고, 그 작업이 완료되었을 때 &lt;b&gt;콜백을 실행&lt;/b&gt;하는 것입니다. 이 과정에서 자바스크립트는 &lt;b&gt;비동기적으로&lt;/b&gt; 여러 작업을 처리할 수 있지만, 실제로 작업을 실행하는 것은 메인 스레드가 아닌 &lt;b&gt;다른 쓰레드들&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Front/  Vanilla JS</category>
      <category>call stack</category>
      <category>EVENT LOOP</category>
      <category>job queue</category>
      <category>single thread</category>
      <author>Psychogrammer</author>
      <guid isPermaLink="true">https://psychogrammer.tistory.com/235</guid>
      <comments>https://psychogrammer.tistory.com/235#entry235comment</comments>
      <pubDate>Sun, 5 May 2024 17:09:37 +0900</pubDate>
    </item>
  </channel>
</rss>