<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>beomsic</title>
    <link>https://beomsic.tistory.com/</link>
    <description>나의 공부 기록 </description>
    <language>ko</language>
    <pubDate>Sat, 27 Jun 2026 03:52:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>beomsic</managingEditor>
    <item>
      <title>네이버 부스트캠프 웹・모바일 10기 수료 회고</title>
      <link>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-10%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/51r2g/dJMcafFzX0E/VZBe8nsG5657Z11LZDwSt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/51r2g/dJMcafFzX0E/VZBe8nsG5657Z11LZDwSt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/51r2g/dJMcafFzX0E/VZBe8nsG5657Z11LZDwSt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F51r2g%2FdJMcafFzX0E%2FVZBe8nsG5657Z11LZDwSt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;328&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;620&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;부스트캠프에 참여한 지 벌써 7개월이 지났습니다. 지난 여름에 시작했던 과정이 어느새 추운 겨울에 마무리되었네요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 시간이 훅 지나간 것처럼 느껴질 만큼 빠르게 지나간 시간이었지만 그 안에서 좋은 사람들도 많이 만나고 스스로의 변화도 분명히 느낄 수 있었던 소중한 시간이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 앞으로 어떤 방향으로 고민하고 성장해 나가야 할지에 대한 감을 잡을 수 있었다는 점에서 개인적으로 매우 의미 있는 시간이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;(더 함께 했으면 좋겠지만&amp;hellip;!!)&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 부스트캠트 멤버십 과정을 다시 한 번 되돌아보며 각 과정에서 제가 어떤 경험을 했고 어떤 생각들을 하게 되었는지를 정리해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/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;챌린지 기간을 통해서 느꼈던 저의 문제점은 &amp;lsquo;알고 있다고 생각했지만 실제로는 그렇지 않았구나&amp;rsquo;라는 것이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익숙하다고 느꼈던 개념들도 막상 구현하거나 설명하려는 순간 무너지는 경험을 여러 번 했고 그 과정에서 꽤 큰 충격을 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 멤버십 과정에서는 단순히 기능을 완성하는 것보다 학습하거나 구현하면서 생기는 작은 의문들, 애매한 이해, 찜찜한 부분들을 가능한 한 넘기지 말고 해결해보기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람들이 많이 사용하는 정해진 정답을 선택하는 것이 아닌 &lt;code&gt;왜 이런 문제가 생겼는지&lt;/code&gt;, &lt;code&gt;내가 어디까지 이해하고 있는지&lt;/code&gt; 를 고민해보면서 스스로의 사고와 문제 해결 능력을 키우고자 이런 목표를 세웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;동료들을 최대한 활용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 저는 문제를 혼자 해결하려는 성향이 꽤 강한 편이었습니다. 물론 스스로 고민하는 과정이 중요하다는 생각 때문이었지만 돌이켜보면 불필요하게 오래 끙끙대고 있었던 순간들도 많았던 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;(이전에 했던 인턴과정에서도 이런 경험이 있었는데 지금와서 보니 정말 후회되는 순간인거 같습니다&amp;hellip;! 좀 더 주변을 이용할걸!)&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서!! 멤버십에서는 이런 제 문제를 해결하고자 노력해보려고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막히는 부분이나 고민되는 설계가 있다면 혼자 붙잡고 있기보다 주변 캠퍼분들에게 공유하고 제 생각, 트러블슈팅 과정을 설명하는 연습을 해보고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 질문을 던지기 전에 &lt;code&gt;나는 지금 무엇을 이해했고 어디에서 막혔는가&lt;/code&gt;를 스스로 정리해보는 과정 자체가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 좋은 시간이 될 것 같다는 기대도 있었습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;지치지 않고, 재미있게, 함께 하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 개인적인 기준이지만 저는 &amp;lsquo;재미&amp;rsquo; 라는 요소를 꽤나 중요하게 생각하고 있습니다  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즐기면서 몰입할 때 오히려 더 깊이 배우게 된다는 경험을 여러 번 했기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 과정에서도 단순히 &amp;lsquo;성장해야 한다&amp;rsquo;는 압박감을 가진다기보다는 동료분들과 함께 배우고 고민하는 시간 자체를 최대한 즐기고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론..! 학습과 프로젝트가 힘들지 않을 수는 없겠지만&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 가볍게 웃을 수 있는 분위기&lt;/li&gt;
&lt;li&gt;개발 이야기뿐 아니라 사람으로서 서로를 알아가는 시간&lt;/li&gt;
&lt;li&gt;함께하는 과정에서 생기는 유대감&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 것들도 이 과정에서 꽤 중요한 의미를 가진다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;학습 스프린트&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJZ1G/dJMcahXHa8Q/PO63wInwajZ8cb35vNXTi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJZ1G/dJMcahXHa8Q/PO63wInwajZ8cb35vNXTi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJZ1G/dJMcahXHa8Q/PO63wInwajZ8cb35vNXTi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJZ1G%2FdJMcahXHa8Q%2FPO63wInwajZ8cb35vNXTi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;421&quot; height=&quot;429&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버십 학습 스프린트에서는 &lt;u&gt;10주&lt;/u&gt; 동안 다양한 미션을 수행하며 프론트엔드와 백엔드를 함께 경험할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 구현하는 것 뿐 아니라 웹 서비스가 실제로 어떤 구조와 흐름으로 동작하는지 전체적인 그림을 이해해 볼 수 있는 좋은 시간이였던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매주 새로운 캠퍼분들과 그룹을 이루며 미션을 진행하다 보니 새롭고 다양한 사고 방식과 접근 방법을 접할 수 있었고 미처 고려하지 못했던 부분이나 부족한 점들도 많이 발견할 수 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 문제를 두고도 사람마다 완전히 다른 관점과 설계를 가져간다는 점이 인상 깊게 남았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;저는 이전까지 백엔드 중심으로 학습을 해왔기 때문에 학습 스프린트 과정에서 프론트엔드 영역을 학습하고 개발하는 것은 낯선 경험이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익숙하지 않은 개념과 기술들을 빠르게 이해하고 적용해야 했고 이미 알고 있다고 생각했던 내용들도 다시 정리해야 하는 순간들이 많았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 가장 크게 느낀 점은 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;기록&lt;/span&gt;과 &lt;span style=&quot;color: #006dd7;&quot;&gt;문서화&lt;/span&gt;의 중요성&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 시간 동안 많은 내용을 학습하다 보니 단순히 이해하고 넘어가는 것만으로는 머릿속에 오래 남지 않는다는 느낌을 받았고 팀원들과 제 생각이나 구현 의도를 공유하는 과정에서도 말보다 정리된 문서가 훨씬 효과적이라는 것을 느낄 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경험을 통해 스스로 이런 기준을 가지고 남은 멤버십 과정을 진행하고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✏️ &lt;b&gt;학습한 것, 고민한 것, 설계한 것은 가능한 한 문서로 남기자 !!&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완벽한 문서가 아니더라도 스스로의 사고 과정을 정리하고 다시 돌아볼 수 있다는 점에서 큰 도움이 되었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업 상황에서도 의사소통 비용을 줄이는 데 꽤 효과적이였다고 생각합니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNdU6U/dJMcaadc2Bq/IarZ2ymtBIRpR60q5yj7fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNdU6U/dJMcaadc2Bq/IarZ2ymtBIRpR60q5yj7fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNdU6U/dJMcaadc2Bq/IarZ2ymtBIRpR60q5yj7fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNdU6U%2FdJMcaadc2Bq%2FIarZ2ymtBIRpR60q5yj7fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;423&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;시니어 리뷰어 피드백&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 스프린트 과정에서는 시니어 리뷰어님에게 코드 리뷰와 피드백을 받을 수 있는 소중한 기회가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;피드백 주신 &lt;b&gt;테오(유용태)&lt;/b&gt; 님께 다시 한 번 감사합니다..!  &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적인 정답을 알려주는 느낌이라기보다는 제가 어떤 기준과 맥락에서 선택을 했는지 어떤 관점으로 문제를 바라보고 있는지를 되돌아보게 만드는 질문들이 많았고 그 과정에서 스스로의 사고 방식을 점검해볼 수 있는 기회가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 기억에 남는 부분은 구현 결과 자체보다 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;결정 과정&lt;/span&gt;과 &lt;span style=&quot;color: #ee2323;&quot;&gt;이유를 설명하는 능력&lt;/span&gt;의 중요성&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;무엇을 만들었는가&lt;/code&gt;만큼이나 &lt;code&gt;왜 그렇게 만들었는가&lt;/code&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 캠퍼들과 함께 5주라는 기간 동안 서비스를 만들어가는 과정이었지만 너무나 재미있게 협업을 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 기능 구현이나 결과물 완성에만 집중하기보다 &lt;code&gt;일단 재미있게 만들어보자&lt;/code&gt;는 공감대와 목표를 가지고 프로젝트에 임했고 이런 분위기가 프로젝트 전반에 긍정적인 영향을 주었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;팀 위키&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uA6OK/dJMcaf6DKS6/LOPouHjyFScTvC68AAD7MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uA6OK/dJMcaf6DKS6/LOPouHjyFScTvC68AAD7MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uA6OK/dJMcaf6DKS6/LOPouHjyFScTvC68AAD7MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuA6OK%2FdJMcaf6DKS6%2FLOPouHjyFScTvC68AAD7MK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;409&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 그룹 스프린트에서 특히 좋았던 경험 중 하나는 &lt;b&gt;팀 위키&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;code&gt;누가 어떤 작업을 진행하고 있는지&lt;/code&gt; &lt;code&gt;어떤 부분에서 고민하고 있는지&lt;/code&gt;를 빠르게 파악할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말로만 공유할 때보다 훨씬 명확했고 작업 맥락을 이해하는 데 드는 비용도 크게 줄어든다는 느낌을 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;문서화는 단순한 기록이 아니라 협업 효율을 높이는 도구구나!!&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS101g/dJMcafexQA2/6N4GdVVsJKRimvckbGUKj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS101g/dJMcafexQA2/6N4GdVVsJKRimvckbGUKj0/img.png&quot; data-alt=&quot;종각역 할리스 못 잊어.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS101g/dJMcafexQA2/6N4GdVVsJKRimvckbGUKj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS101g%2FdJMcafexQA2%2F6N4GdVVsJKRimvckbGUKj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;278&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;종각역 할리스 못 잊어.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 팀은 &lt;b&gt;주 2회 오프라인&lt;/b&gt;으로 만나 작업을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 공간에서 함께 작업하다 보니 빨리 서로와 친해지고 이를 바탕으로 서로에게 신뢰가 쌓여 질문이나 의견 교환이 훨씬 자연스럽게 이루어지고 의사결정 속도가 눈에 띄게 빨라졌으며 팀으로 함께 무언가를 만들어가고 있다는 경험을 할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 그룹 스프린트를 통해 협업이 반드시 어렵고 소모적인 과정만은 아니라는 점을 느낄 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀의 분위기, 공유 방식, 그리고 함께 만들어가는 과정이 정말 재미있게 프로젝트를 할 수 있게 한다는 것을 다시 느낄 수 있었습니다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께했던 팀원분들이 있었기에 이번 그룹 스프린트가 더 즐거운 기억으로 남은 것 같습니다.&lt;/p&gt;
&lt;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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;그룹 스프린트 쥬니버팀 - 대니, 제리, 조이&lt;/b&gt;&lt;/span&gt; 함께해서 너무 재밌었습니다~!!!&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; &amp;zwj; &lt;b&gt; 그룹 프로젝트 &amp;ndash; Locus&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSllHi/dJMcafyPpuU/HzxnvyOk3td8eKZ7FFAaL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSllHi/dJMcafyPpuU/HzxnvyOk3td8eKZ7FFAaL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSllHi/dJMcafyPpuU/HzxnvyOk3td8eKZ7FFAaL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSllHi%2FdJMcafyPpuU%2FHzxnvyOk3td8eKZ7FFAaL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;336&quot; height=&quot;678&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;1138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버십의 마지막 과정이였던 그룹 프로젝트에서 해핑팀으로 참여해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Locus&lt;/b&gt;&lt;/span&gt; 서비스를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;장소를 기반으로 기록을 작성하고 탐색할 수 있는 기록 서비스&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 메모 서비스들은 대부분 텍스트 중심으로 구성되어 있고 이는 시간이 지났을 때 &lt;code&gt;무엇을 기록했는지&lt;/code&gt;는 남지만 &lt;b&gt;왜 그런 생각을 하게 되었는지에 대한 맥락은 쉽게 사라진다&lt;/b&gt;는 문제의식에서 프로젝트가 시작되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 프로젝트에서 백엔드 개발자로 참여해 아이디어 도출과정, 기획, 개발을 하는 과정에 참여해 팀원들과 의견을 나누고 함께 의사결정을 하는 과정으로 프로젝트를 진행했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;해핑팀 - 휴고, 아리, 피넛, 민다&lt;/b&gt;&lt;/span&gt; 다들 열심히 참여해주시고 맡은 바를 다 해주셔서 너무 감사합니다!!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기획&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JRxsF/dJMcaa5k0rp/yyMuGroucIhFfVyp7KxOkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JRxsF/dJMcaa5k0rp/yyMuGroucIhFfVyp7KxOkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JRxsF/dJMcaa5k0rp/yyMuGroucIhFfVyp7KxOkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJRxsF%2FdJMcaa5k0rp%2FyyMuGroucIhFfVyp7KxOkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;392&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 시작하며 가장 먼저 마주한 어려움은 &lt;i&gt;&lt;b&gt;기획 &lt;/b&gt;&lt;/i&gt;이었습니다.&lt;/p&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;이런 서비스를 만들면 좋겠다&lt;/code&gt;는 아이디어가 있었지만 이를 실제 서비스 형태로 구체화하는 과정은 전혀 다른 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무엇을 해결하는 서비스인지?&lt;/b&gt; &lt;b&gt;사용자는 왜 이 기능을 사용해야 하는지?&lt;/b&gt; 어떤 흐름으로 동작해야 자연스러운지 등을 정의하는 과정에서 예상보다 많은 논의와 수정이 반복되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 인상 깊었던 점은 &lt;b&gt;좋은 아이디어와 좋은 기획은 완전히 다른 영역&lt;/b&gt;이라는 사실이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어는 쉽게 떠오르지만 이를 설계 가능한 형태로 정리하는 일은 끊임없는 질문과 선택의 과정이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험을 통해 개발 이전 단계에서의 사고 과정이 얼마나 중요한지를 체감할 수 있었습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;본격적인 개발&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 단계에 들어나고 나서는 기술적인 고민을 정말 많이 해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버십에서 세웠던 목표처럼 어떤 기술을 선택하고 왜 도입해야 하는지를 스스로 고민하고 상황에 맞는 기술을 선택하고자 했고 어떤 기술을 왜 선택해야 하는지 스스로 설명이 가능하고 이를 팀원들에게 공유했을 때 모두가 납득이 가게끔 하고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 검색 기능, 데이터 저장 구조, 시스템간 역할 분리 등을 설계하고 개발하면서 단순한 구현 문제가 아니라 &lt;b&gt;설계와 트레이드오프의 문제&lt;/b&gt;를 계속해서 고민할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 특히 인상 깊게 느낀 점들은 다음과 같았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기술 선택에는 반드시 맥락과 이유가 필요하다는 점&lt;/li&gt;
&lt;li&gt;익숙함과 적합함은 서로 다른 기준이라는 점&lt;/li&gt;
&lt;li&gt;구현 난이도보다 시스템 전체 관점이 더 중요할 수 있다는 점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서는 여러 선택의 순간들이 존재했고 각 선택에는 팀원들을 설득하거나 함께 공감대를 만들어야 하는 과정이 필요했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;다른 사람을 설득하기 이전에 나 스스로 충분히 납득하고 있어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람마다 경험과 관점, 중요하게 생각하는 기준이 모두 다르기 때문에 단순히 &lt;code&gt;이 방식이 좋아 보인다&lt;/code&gt;는 것만으로는 충분하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 이 기술이 필요한지&lt;/b&gt;, &lt;b&gt;어떤 문제를 해결하기 위한 선택인지&lt;/b&gt;, 그리고 &lt;b&gt;어떤 트레이드오프를 감수해야 하는지&lt;/b&gt;에 대해 스스로 명확히 이해하고 있을 때 비로소 의미 있는 논의와 공유가 가능하다는 것을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배운 점&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Locus 프로젝트를 통해 얻은 가장 큰 변화는 개발을 바라보는 시각이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 기능 구현이나 기술 자체에 더 집중했다면 이번에는 문제 정의, 사용자 관점, 설계 의도 같은 요소들을 더 많이 고민하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;  &lt;b&gt;좋은 개발은 코드를 잘 작성하는 것만으로 완성되지 않는다&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 문제를 해결하려는지, 왜 이런 구조를 선택했는지, 사용자는 어떤 경험을 하게 되는지를 함께 고민하는 과정이 개발의 중요한 일부라는 것을 배우게 되었습니다.&lt;/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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;윌리의 보드게임 &amp;amp; 가상 면접 사례로 배우는 대규모 설계 기초 뿌시기 스터디&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M1hHI/dJMcajgQ2GK/otzKSsUmic8NtATfIHQhCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M1hHI/dJMcajgQ2GK/otzKSsUmic8NtATfIHQhCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M1hHI/dJMcajgQ2GK/otzKSsUmic8NtATfIHQhCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM1hHI%2FdJMcajgQ2GK%2FotzKSsUmic8NtATfIHQhCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;690&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩌다 그룹 스프린트의 팀원이였던 대니가 함께 다른 캠퍼분들과 보드게임을 하자고 이야기해주셔서 참여한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;윌리의 보드게임!!!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 낯을 많이 가리는 성격이라 모르는 분들이 있어서 참여할지 말지 고민이 많았는데 참여하지 않았더라면..! 정말 후회했을 것 같습니다&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 인연으로 스터디도 함께 하게 되었는데..! 제가 처음 스터디를 같이 하자고 했을 때 모두 흔쾌히 함께 해주셔서 너무 좋은 시간이였던 것 같습니다. 다들 너무 잘하시고 각자의 이야기, 의견을 재미있게 말씀해주셔서 많이 배울 수 있었습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 모각코를 하기도 하고 장난도 많이 하고 고민, 기술적인 이야기도 하면서 친해진 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;대니, 윌리, 벤자민, 다니, 로키&lt;/b&gt; &lt;/span&gt;너무 재미있었고 저랑 놀아주고 장난도 받아주셔서 너무 감사했습니다!@!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;앞으로도 계속 보드게임도 하고 스터디도 하는 등 교류하고 자주 만났으면 좋겠습니다! 계속 제 장난 받아주세요~!!&lt;/b&gt;&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/berhsM/dJMcadAVkEk/zGkJ1xCFpw1BpJA57pQxIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/berhsM/dJMcadAVkEk/zGkJ1xCFpw1BpJA57pQxIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/berhsM/dJMcadAVkEk/zGkJ1xCFpw1BpJA57pQxIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FberhsM%2FdJMcadAVkEk%2FzGkJ1xCFpw1BpJA57pQxIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;793&quot; height=&quot;153&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부스트캠프에 지원하던 때나 멤버십 과정에 참여할 때 스스로 분명한 목표를 가지고 참여를 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 과정을 수료하는 것이 아니라 개발자로서의 태도와 사고방식을 바꾸고 싶다는 마음이 더 컸던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌이켜보면 이런 목표들은 기술적인 성장을 넘어 &lt;b&gt;개발을 대하는 자세에 대한 고민&lt;/b&gt;에 더 가까웠던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7개월의 시간을 지나 멤버십 과정을 마무리하는 지금, 완벽하다고 말할 수는 없지만 적어도 나아가는 방향성만큼은 분명히 달라졌다고 느끼고 있습니다.&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;가장 변했다고 느끼는 것 중 하나는 소통에 대한 태도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 제 코드나 생각을 공유하고 피드백을 요청하는 일이 부담스럽고 조심스러웠습니다. 실력이 부족하다는 생각에 스스로 확신을 갖지 못했고 충분히 정리된 뒤에야 의견을 꺼내려 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 부스트캠프 과정에서는 제 상황과 고민을 빠르게 공유하고 이해되지 않는 부분을 적극적으로 질문하려 노력했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서와 대화를 통해 생각을 정리하고 의견을 나누는 과정 속에서 소통은 부담스러운 과정이 아니라 문제 해결을 위한 중요한 도구라는 점을 체감할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 그룹 프로젝트에서는 단순히 제 구현에만 집중하는 것이 아니라 동료들의 작업에도 적극적으로 관심을 가지려 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 PR을 가능한 한 읽어보고 이해되지 않는 부분은 질문하고 제 생각을 정리해 리뷰로 남기면서 자연스럽게 더 많은 논의와 학습이 이루어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험을 통해 협업은 역할을 나누는 과정이 아니라 서로의 사고 과정을 공유하는 일에 가깝다는 느낌을 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 해결 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나의 변화는 문제 해결 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 충분히 준비되고 확신이 생긴 이후에야 구현을 시작하려는 경향이 강했지만 과정 속에서 러닝 바이 두잉의 방식을 자연스럽게 받아들이게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 시도해보고 구현하며 마주한 문제를 학습으로 연결하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;러닝 바이 두잉&lt;/b&gt;&lt;/span&gt; 를 통해서 긍정적인 효과를 본 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 실제 문제를 해결하는 과정 자체에서 큰 재미를 느낄 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;이렇게 지원 당시 세웠던 목표들은 완전히 달성했다고 말하기보다는 자연스러운 습관과 저만의 기준으로 정해진 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부스트캠프 과정을 통해서 생각을 공유하고, 질문하고, 함께 고민하는 과정이 더 이상 어색하지 않게 되었고 이 변화만으로도 제게 충분히 의미 있는 시간이었다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 이런 선택을 해야 하는지 고민하고&lt;/li&gt;
&lt;li&gt;스스로 납득하며 이해하고&lt;/li&gt;
&lt;li&gt;동료들과 생각을 적극적으로 공유하는&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로 성장해 나가고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 혼자 잘하는 개발자가 아니라 함께 고민하고 성장하는 개발자가 되고 싶다는 마음도 더욱 분명해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 멤버십 과정에서 느꼈던 즐거움과 배움을 잊지 않고 앞으로의 개발 과정에서도 꾸준히 이어가 보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  부스트캠프 웹&amp;middot;모바일 10기 모든 캠퍼분들, 마스터님들, 운영진분들 정말 수고 많으셨습니다! (나도~)&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqRrZq/dJMcaajYujS/nSvwT1JgUjQCmmwx0opEYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqRrZq/dJMcaajYujS/nSvwT1JgUjQCmmwx0opEYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqRrZq/dJMcaajYujS/nSvwT1JgUjQCmmwx0opEYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqRrZq%2FdJMcaajYujS%2FnSvwT1JgUjQCmmwx0opEYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;482&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>네이버 부스트캠프 웹・모바일 10기</category>
      <category>네이버 부스트캠프</category>
      <category>부캠</category>
      <category>부캠 후기</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/202</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-10%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0#entry202comment</comments>
      <pubDate>Wed, 18 Feb 2026 22:03:49 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 12장. 채팅 시스템 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-12%EC%9E%A5-%EC%B1%84%ED%8C%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 문제 이해 및 설계 범위 확정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 채팅앱을 설계하는지 확실히 해두는 것이 중요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1:1 채팅에 집중하는 앱&lt;/li&gt;
&lt;li&gt;그룹 채팅에 중점을 둔 업무용 앱&lt;/li&gt;
&lt;li&gt;대규모 그룹의 소통과 응답지연이 낮은 음성 채팅에 집중하는 앱&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  요구사항&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1:1 채팅과 그룹 채팅 둘 다 지원&lt;/li&gt;
&lt;li&gt;모바일 앱, 웹 앱 모두 지원&lt;/li&gt;
&lt;li&gt;일별 능동 사용자 수 DAU: 5천만명&lt;/li&gt;
&lt;li&gt;그룹 채팅 인원 제한: 최대 100명&lt;/li&gt;
&lt;li&gt;1:1 채팅, 그룹 채팅, 사용자 접속상태 표시 지원&lt;/li&gt;
&lt;li&gt;텍스트 메시지만 주고받을 수 있음&lt;/li&gt;
&lt;li&gt;메시지 길이는 100,000자 이하&lt;/li&gt;
&lt;li&gt;채팅 이력은 영구적으로 보관&lt;/li&gt;
&lt;/ol&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;클라이언트는 서로 직접 통신하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;채팅 서비스는 다음 기능을 제공해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트들로부터 메시지 수신&lt;/li&gt;
&lt;li&gt;메시지 수신자 결정 및 전달&lt;/li&gt;
&lt;li&gt;수신자가 접속 상태가 아닌 경우에는 접속할 때까지 해당 메시지 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qjT81/dJMcaaYmftl/N6PPbUtvVDmsuMPp4LN7dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjT81/dJMcaaYmftl/N6PPbUtvVDmsuMPp4LN7dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjT81/dJMcaaYmftl/N6PPbUtvVDmsuMPp4LN7dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjT81%2FdJMcaaYmftl%2FN6PPbUtvVDmsuMPp4LN7dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;183&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;HTTP&lt;/b&gt;&lt;/span&gt;는 웹에서 가장 널리 사용되는 프로토콜이지만 HTTP는 클라이언트가 연결을 만드는 프로토콜이며 서버에서 클라이언트로 임의 시점에 메시지를 보내는 데 쉽게 사용할 수 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 연결을 만드는 것처럼 동작하기 위한 기법이 제안되는데 &lt;code&gt;폴링&lt;/code&gt;, &lt;code&gt;롱 폴링&lt;/code&gt;, &lt;code&gt;웹 소켓&lt;/code&gt; 등이 그런 기술이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;폴링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트가 주기적으로 서버에게 새 메시지가 있는지 물어보는 방법&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴링을 자주하면 비용은 올라가고 답해줄 메시지가 없는 경우 서버 자원이 불필요하게 낭비된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;1570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUIyBZ/dJMcacu6ss2/LDkmLOQ98jeGwFebluEitK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUIyBZ/dJMcacu6ss2/LDkmLOQ98jeGwFebluEitK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUIyBZ/dJMcacu6ss2/LDkmLOQ98jeGwFebluEitK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUIyBZ%2FdJMcacu6ss2%2FLDkmLOQ98jeGwFebluEitK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;777&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;1570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;롱 폴링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폴링을 개선한 방법으로 클라이언트가 주기적으로 서버와 통신할 때 새 메시지가 반환되거나 타임아웃 될때까지 연결을 유지하는 방식이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 새 메시지를 받으면 기존 연결을 종료하고 서버에 새로운 요청을 보내 모든 과정을 다시 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qn2R7/dJMcaioxudJ/1z8IFxVke6G8QZmY6Dekh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qn2R7/dJMcaioxudJ/1z8IFxVke6G8QZmY6Dekh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qn2R7/dJMcaioxudJ/1z8IFxVke6G8QZmY6Dekh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqn2R7%2FdJMcaioxudJ%2F1z8IFxVke6G8QZmY6Dekh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;531&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;1. 메시지를 보내는 클라이언트와 수신하는 클라이언트가 같은 채팅 서버에 접속하지 않을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 서버는 stateless 서버임으로 로드밸런싱을 위해 라운드로빈 알고리즘을 사용하면 메시지를 받은 서버는 해당 메시지를 수신할 클라이언트와 롱 폴링 연결을 가지고 있지 않을 수 있다.&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;2. 서버 입장에서 클라이언트가 연결을 해제했는지 아닌지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 메시지를 많이 받지 않는 클라이언트도 타임아웃이 일어날 때 마다 주기적으로 서버에 다시 접속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;웹 소켓&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버가 클라이언트에게 비동기 메시지를 보낼 때 가장 널리 사용하는 기술이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3FB5R/dJMcacPpMbN/OksJP3iPU8nC7TeBZvDlk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3FB5R/dJMcacPpMbN/OksJP3iPU8nC7TeBZvDlk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3FB5R/dJMcacPpMbN/OksJP3iPU8nC7TeBZvDlk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3FB5R%2FdJMcacPpMbN%2FOksJP3iPU8nC7TeBZvDlk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;378&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;웹 소켓을 이용하면 메시지를 보낼 때나 받을 때 동일한 프로토콜을 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계, 구현이 단순하고 직관적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;zwj;  주의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 소켓연결은 항구적으로 유지되어야 하기 때문에 서버 측에서 연결 관리를 효율적으로 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개략적 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서버 사이의 주 통신 프로토콜로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;웹 소켓&lt;/b&gt;&lt;/span&gt;을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;1292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7Dz7K/dJMcaf6qxEV/WijkpIaMWVbyKegf4nj3v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7Dz7K/dJMcaf6qxEV/WijkpIaMWVbyKegf4nj3v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7Dz7K/dJMcaf6qxEV/WijkpIaMWVbyKegf4nj3v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7Dz7K%2FdJMcaf6qxEV%2FWijkpIaMWVbyKegf4nj3v1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;811&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;1292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 시스템은 &lt;b&gt;3부분&lt;/b&gt;으로 나누어서 볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무상태 서비스&lt;/li&gt;
&lt;li&gt;상태유지 서비스&lt;/li&gt;
&lt;li&gt;제 3자 서비스&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;1️⃣ &lt;b&gt;무상태 서비스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인, 회원가입 등 전통적인 &lt;b&gt;요청-응답 서비스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 탐색 서비스(Service discovery)&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;클라이언트가 접속할 채팅 서버의 DNS 호스트명을 클라이언트에게 알려주는 역할&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;2️⃣ &lt;b&gt;상태 유지 서비스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 유지가 필요한 서비스는&lt;b&gt; 채팅 서비스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 클라이언트가 채팅 서버와 독립적인 네트워크 연결을 유지해야 하기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;제 3자 서비스 연동&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 메시지를 받았다면 앱이 실행되지 않더라도 알림을 받아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규모 확장성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 시스템은 대규모 트래픽을 처리해야 하는 경우에도 한 대의 서버로 처리를 할 수 있지만 &lt;u&gt;&lt;b&gt;SPOF&lt;/b&gt;&lt;/u&gt;를 방지하기 위해서는 확장하는 것이 좋다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때도 클라이언트가 채팅 서버와 웹 소켓 연결을 끊지 않고 유지해야하는 것을 유의해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIEtO3/dJMcac9EIMx/3uMY1jzlQG7rKNkwbviViK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIEtO3/dJMcac9EIMx/3uMY1jzlQG7rKNkwbviViK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIEtO3/dJMcac9EIMx/3uMY1jzlQG7rKNkwbviViK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIEtO3%2FdJMcac9EIMx%2F3uMY1jzlQG7rKNkwbviViK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;659&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1146&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;채팅 서버&lt;/td&gt;
&lt;td&gt;클라이언트 사이에 메시지 중계 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;접속상태 서버&lt;/td&gt;
&lt;td&gt;사용자의 접속 여부 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API 서버&lt;/td&gt;
&lt;td&gt;로그인, 회원가입 등 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;알림 서버&lt;/td&gt;
&lt;td&gt;푸시 알림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;키-값 저장소&lt;/td&gt;
&lt;td&gt;채팅 이력 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;저장소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 시스템이 다루는 데이터는 2가지이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 데이터(사용자 프로파일, 설정 등) &amp;rarr; 안정성을 보장하는 RDB에 보관&lt;/li&gt;
&lt;li&gt;채팅 이력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;채팅 이력 데이터의 읽기 쓰기 연산 패턴&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;채팅 이력의 데이터는 매우 많다&lt;/li&gt;
&lt;li&gt;빈번하게 사용되는 것은 최근에 주고받은 메시지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 최근에 주고받은 메시지 데이터만 보게되는 것이 맞지만 검색 기능을 이용하거나 특정 사용자가 언급된 메시지를 보거나 특정 메시지로 점프하거나 하여 무작위적인 데이터 접근을 하게 되는 일도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 계층에서는 이런 기능도 지원해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;키-값 저장소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수평적 규모확장이 쉽다.&lt;/li&gt;
&lt;li&gt;데이터 접근 지연시간이 낮다 (latency)&lt;/li&gt;
&lt;li&gt;RDB는 데이터 가운데 롱 테일에 해당하는 부분을 잘 처리리하지 못한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스가 커지면 데이터에 대한 무작위적 접근을 처리하는 비용이 늘어난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 많은 채팅 시스템이 키-값 저장소를 채택&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이스북 메신저: &lt;b&gt;HBase&lt;/b&gt;&lt;/li&gt;
&lt;li&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  long tail&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;데이터 분포 그래프를 그렸을 때 발생 빈도가 높은 데이터는 소수(Head)에 집중되고 나머지는 낮은 빈도로 완만하게 긴 꼬리(Tail)를 형성하는 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  상세 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스 탐색&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;b&gt;아파치 주키퍼 같은 오픈소스 솔루션&lt;/b&gt;을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 가능한 모든 채팅서버를 등록시켜두고 클라이언트가 접속을 시도하면 사전에 정한 기준에 따라 최적의 채팅 서버를 골라준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HbQOp/dJMcagK247u/zvQMSaNdQMnd8krpIxREn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HbQOp/dJMcagK247u/zvQMSaNdQMnd8krpIxREn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HbQOp/dJMcagK247u/zvQMSaNdQMnd8krpIxREn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHbQOp%2FdJMcagK247u%2FzvQMSaNdQMnd8krpIxREn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;736&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 시스템에 로그인 시도&lt;/li&gt;
&lt;li&gt;로드밸런서가 로그인 요청을 API 서버로 보낸다&lt;/li&gt;
&lt;li&gt;API 서버가 사용자 인증을 처리하고 난 뒤 서비스 탐색 기능이 동작해 해당 사용자를 서비스할 최적의 채팅 서버를 찾는다.&lt;/li&gt;
&lt;li&gt;사용자는 채팅 서버와 웹 소켓 연결을 맺는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메시지 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;1:1 채팅 메시지 흐름&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lqjd7/dJMcafyAQzQ/viIfNum64d6RW9EekU0Erk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lqjd7/dJMcafyAQzQ/viIfNum64d6RW9EekU0Erk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lqjd7/dJMcafyAQzQ/viIfNum64d6RW9EekU0Erk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flqjd7%2FdJMcafyAQzQ%2FviIfNum64d6RW9EekU0Erk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;434&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자 A가 채팅 서버로 메시지 전송&lt;/li&gt;
&lt;li&gt;채팅 서버는 ID 생성기를 사용해 해당 메시지의 ID 결정&lt;/li&gt;
&lt;li&gt;채팅 서버는 해당 메시지를 메시지 동기화 큐로 전송&lt;/li&gt;
&lt;li&gt;메시지가 키-값 저장소에 보관&lt;/li&gt;
&lt;li&gt;사용자 B가 온라인일 경우 사용자 B가 접속 중인 채팅 서버로 전송&lt;/li&gt;
&lt;li&gt;사용자 B가 오프라인일 경우 사용자 B에게 푸시 알림 전송&lt;/li&gt;
&lt;li&gt;채팅 서버는 사용자 B에게 메시지 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;여러 단말 사이의 메시지 동기화&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcKJGT/dJMcaaRAUnZ/UdKUMJpSWPf7vioTYG0H0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcKJGT/dJMcaaRAUnZ/UdKUMJpSWPf7vioTYG0H0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcKJGT/dJMcaaRAUnZ/UdKUMJpSWPf7vioTYG0H0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcKJGT%2FdJMcaaRAUnZ%2FUdKUMJpSWPf7vioTYG0H0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;596&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단말은 &lt;code&gt;cur_max_message_id&lt;/code&gt; 라는 변수를 유지하는데 해당 단말에서 관측된 가장 최신 메시지의 ID를 추적하는 용도이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 두 조건을 만족하는 메시지는 새 메시지로 간주한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수신자 ID가 현재 로그인한 사용자 ID와 같다&lt;/li&gt;
&lt;li&gt;키-값 저장소에 보관된 메시지로서, 그 ID가 &lt;code&gt;cur_max_message_id&lt;/code&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;3️⃣ &lt;b&gt;소규모 그룹 채팅에서의 메시지 흐름&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNpUrI/dJMcad1NlRQ/DvzPcGJbkidgjO6NzVkxiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNpUrI/dJMcad1NlRQ/DvzPcGJbkidgjO6NzVkxiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNpUrI/dJMcad1NlRQ/DvzPcGJbkidgjO6NzVkxiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNpUrI%2FdJMcad1NlRQ%2FDvzPcGJbkidgjO6NzVkxiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;529&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 A가 그룹 채팅방에서 메시지를 보냈을 때 어떤 일이 벌어질지 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 A가 보낸 메시지가 사용자 B와 C의 메시지 동기화 큐에 복사된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 메시지가 왔는지 확인하기 위해서 자기 큐만 바라보면 된다.&lt;/li&gt;
&lt;li&gt;그룹이 크지 않다면 메시지를 수신자별로 복사하여 큐에 넣는 작업의 비용이 문제가 되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;접속 상태 표시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 접속 상태를 표시하는 것은 채팅 서비스의 핵심적 기능이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;접속상태 서버는 클라이언트와 웹 소켓으로 통신하는 실시간 서비스의 일부이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;사용자 로그인&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 실시간 서비스 사이에 웹 소켓 연결이 맺어지고 나면 접속상태 서버는 사용자의 상태와 last_active_at 타임스탬프 값을 키-값 저장소에 보관한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 사용자는 접속중인 것으로 표시될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;로그아웃&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키-값 저장소에 보관된 사용자의 상태가 online에서 offline으로 바뀌게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;접속 장애&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷 연결이 항상 안정적이지 않기 때문에 그런 상황에 대응할 수 있는 설계를 준비해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 방법은 사용자를 오프라인 상태로 표시하고 연결이 복구되면 온라인 상태로 변경하는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;짧은 시간 동안 인터넷 연결이 끊어졌다 복구되는 일에는 바람직하지 않다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;heartbeat&lt;/b&gt;&lt;/span&gt; 검사를 통해 이 문제를 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온라인 상태의 클라이언트가 주기적으로 heartbeat event를 접속상태 서버로 보내도록 하고 마지막 이벤트를 받은 지 x초 이내에 또 다른 이벤트 메시지를 받으면 사용자의 접속상태를 온라인으로 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상태 정보의 전송&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 A와 친구 관계에 있는 사용자들은 어떻게 해당 사용자의 상태 변화를 알게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태정보 서버는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;pub-sub&lt;/b&gt; &lt;/span&gt;모델을 사용해 각각의 친구관계마다 채널을 하나씩 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A의 접속상태가 변경되었다고 하면 A-B, A-C, A-D 채널에 그 사실을 쓰는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 하면 친구관계에 있는 사용자가 상태정보 변화를 쉽게 통지받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 그룹이 커지면 이런 식으로 접속상태 변화를 알리게되면 비용이나 시간이 많이 들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 그룹 채팅에 입장하는 순간에만 상태정보를 읽어가게 하거나 친구 리스트에 있는 사용자의 접속상태를 갱신하고 싶다면 수동으로 처리하도록 유도하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/201</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-12%EC%9E%A5-%EC%B1%84%ED%8C%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84#entry201comment</comments>
      <pubDate>Mon, 19 Jan 2026 12:46:39 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 11장. 뉴스 피드 시스템 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-11%EC%9E%A5-%EB%89%B4%EC%8A%A4-%ED%94%BC%EB%93%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;뉴스 피드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 페이지 중앙에 지속적으로 업데이트되는 스토리들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 상태 정보 업데이트, 사진, 비디오, 링크 등&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이스북 도움말 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모바일 앱과 웹 모두 지원 필요&lt;/li&gt;
&lt;li&gt;뉴스 피드는 시간 흐름 역순으로 표시&lt;/li&gt;
&lt;li&gt;한 명의 사용자는 최대 5,000명의 친구를 가질 수 있음&lt;/li&gt;
&lt;li&gt;매일 천만 명이 방문하는 트래픽 규모&lt;/li&gt;
&lt;li&gt;뉴스 피드에는 이미지나 비디오 등의 미디어 파일 포함될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  개략적 설계안 제시 및 동의 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;피드 발행&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 스토리를 포스팅하면 데이터를 캐시와 데이터베이스에 기록&lt;/li&gt;
&lt;li&gt;새 포스팅은 친구의 뉴스 피드에도 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;뉴스 피드 생성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 친구의 포스팅을 시간 흐름 역순으로 모아서 만든다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;뉴스 피드 발행&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M7p9c/dJMb996duC6/NsBxkUNerizG3LVkwOTLQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M7p9c/dJMb996duC6/NsBxkUNerizG3LVkwOTLQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M7p9c/dJMb996duC6/NsBxkUNerizG3LVkwOTLQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM7p9c%2FdJMb996duC6%2FNsBxkUNerizG3LVkwOTLQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;558&quot; height=&quot;594&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;사용자&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;새 포스팅을 올리는 주체. POST /v1/me/feed API&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로드밸런서&lt;/td&gt;
&lt;td&gt;사용자 트래픽을 웹 서버들로 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹 서버&lt;/td&gt;
&lt;td&gt;HTTP 요청을 내부 서비스로 중계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포스팅 저장 서비스&lt;/td&gt;
&lt;td&gt;새 포스팅을 DB와 캐시에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포스팅 전송 서비스&lt;/td&gt;
&lt;td&gt;새 포스팅을 친구의 뉴스 피드에 푸시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;알림 서비스&lt;/td&gt;
&lt;td&gt;친구들에게 새 포스팅이 올라왔음을 알려주거나 푸시 알림을 보내는 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;뉴스 피드 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;1044&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1RBQZ/dJMcacBSpoB/ygk9rksTxnL4UaDwq8TtL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1RBQZ/dJMcacBSpoB/ygk9rksTxnL4UaDwq8TtL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1RBQZ/dJMcacBSpoB/ygk9rksTxnL4UaDwq8TtL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1RBQZ%2FdJMcacBSpoB%2Fygk9rksTxnL4UaDwq8TtL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;780&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;1044&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;뉴스 피드를 읽는 주체. GET /v1/me/feed API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로드밸런서&lt;/td&gt;
&lt;td&gt;사용자 트래픽을 웹 서버들로 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹 서버&lt;/td&gt;
&lt;td&gt;트래픽을 뉴스 피드 서비스로 보낸다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;뉴스 피드 서비스&lt;/td&gt;
&lt;td&gt;캐시에서 뉴스 피드를 가져오는 서비스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;뉴스 피드 캐시&lt;/td&gt;
&lt;td&gt;뉴스 피드를 렌더링할 때 필요한 피드 ID 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  상세 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;피드 발행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;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; 등의 기능도 수행&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 인증 토큰 검증&lt;/li&gt;
&lt;li&gt;특정 기간 한 사용자가 올릴 수 있는 포스팅의 수 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✉️ &lt;b&gt;포스팅 전송(팬아웃)서비스&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oO2Zy/dJMcabwbP9W/g1TrR6PMYGcEBNnvqa1gdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oO2Zy/dJMcabwbP9W/g1TrR6PMYGcEBNnvqa1gdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oO2Zy/dJMcabwbP9W/g1TrR6PMYGcEBNnvqa1gdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoO2Zy%2FdJMcabwbP9W%2Fg1TrR6PMYGcEBNnvqa1gdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;745&quot; height=&quot;673&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 사용자의 새 포스팅을 그 사용자와 친구 관계에 있는 모든 사용자에게 전달하는 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팬 아웃에는 두 가지 모델이 존재&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쓰기 시점의 팬아웃 (푸시 모델)&lt;/li&gt;
&lt;li&gt;읽기 시점의 팬아웃 (풀 모델)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;쓰기 시점에 팬아웃하는 모델&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 포스팅을 기록하는 시점에 뉴스 피드를 갱신&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포스팅이 완료되면 바로 해당 사용자의 캐시에 포스팅을 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실시간으로 갱신. 친구들에게 즉시 전송&lt;/td&gt;
&lt;td&gt;친구가 많은 경우 친구 목록을 가져와 갱신하는데 많은 시간이 소요 (hotkey 문제)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;새 포스팅 기록 순간에 뉴스 피드가 갱신되어 읽는데 시간이 빨라짐.&lt;/td&gt;
&lt;td&gt;서비스를 자주 이용하지 않는 사용자의 피드까지 갱신해야해 자원이 낭비&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;읽기 시점에 팬아웃하는 모델&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드를 읽어야 하는 시점에 뉴스 피드를 갱신&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청 기반 모델 (on-demand)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 본인 페이지나 타임라인을 로딩하는 시점에 새로운 포스트를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 48px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;비활성화된 사용자. 거의 로그인하지 않는 사용자의 경우에 유리&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;뉴스 피드를 읽는데 많은 시간이 소요될 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;데이터를 친구 각각에 푸시하는 작업이 없어 핫키 문제가 발생하지 않음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;하이브리드 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 사용자에 대해서는 푸시 모델을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팔로어가 많은 사용자의 경우에는 팔로어가 해당 사용자의 포스팅을 볼 때 가져가도록 하는 풀 모델을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;피드 읽기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHPWbP/dJMcac9EIJk/smNd7tUl1Py4W3DOncJyKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHPWbP/dJMcac9EIJk/smNd7tUl1Py4W3DOncJyKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHPWbP/dJMcac9EIJk/smNd7tUl1Py4W3DOncJyKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHPWbP%2FdJMcac9EIJk%2FsmNd7tUl1Py4W3DOncJyKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;532&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지나 비디오같은 미디어 콘텐츠는 CDN에 저장하여 빨리 읽어갈 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시는 뉴스 피드 시스템의 핵심 컴포넌트&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWhvDE/dJMcabQuggm/ccE6Ypuy8EM8MUBOw4gIg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWhvDE/dJMcabQuggm/ccE6Ypuy8EM8MUBOw4gIg0/img.png&quot; data-alt=&quot;https://earth-95.tistory.com/220&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWhvDE/dJMcabQuggm/ccE6Ypuy8EM8MUBOw4gIg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWhvDE%2FdJMcabQuggm%2FccE6Ypuy8EM8MUBOw4gIg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;337&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://earth-95.tistory.com/220&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;뉴스 피드&lt;/td&gt;
&lt;td&gt;사용자별 최신 뉴스 피드 Id보관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;콘텐츠&lt;/td&gt;
&lt;td&gt;포스팅 데이터를 보관. 인기 콘텐츠는 따로 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소셜 그래프&lt;/td&gt;
&lt;td&gt;사용자 간 관계 정보 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;행동&lt;/td&gt;
&lt;td&gt;포스팅에 대한 사용자의 행위 정보(좋아요, 답글 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;횟수&lt;/td&gt;
&lt;td&gt;좋아요 수, 응답 수, 팔로우 수, 팔로잉 수 등의 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/200</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-11%EC%9E%A5-%EB%89%B4%EC%8A%A4-%ED%94%BC%EB%93%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84#entry200comment</comments>
      <pubDate>Mon, 19 Jan 2026 12:37:20 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 10장. 알림 시스템 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-10%EC%9E%A5-%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;⏰ 알림 시스템&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 시스템은 고객에게 중요할 만한 정보를 &lt;b&gt;비동기적&lt;/b&gt;으로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &lt;code&gt;모바일 푸시 알림(mobile push notification)&lt;/code&gt; 에 한정하지 않고 SMS 메시지, 이메일 3가지로 분류할 수 있다.&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;알림의 종류&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;푸시 알림, SMS 메시지, 이메일&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;code&gt;연성 실시간 시스템 (soft real-time)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림을 가능한 빨리 전달하지만 부하가 걸렸을 경우 약간의 지연 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/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;iOS 단말, 안드로이드 단말, 랩톱/데스크톱&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;서버 측 스케줄링&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;/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;백만 건의 SMS 메시지&lt;/li&gt;
&lt;li&gt;5백만 건의 이메일&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;h3 data-ke-size=&quot;size23&quot;&gt;알림 유형별 지원 방안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;iOS 푸시 알림&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서 푸시 알람을 보내기 위해서는 3가지 컴포넌트가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sRduF/dJMb99LTLkT/Se0jMPlKYK8m4Rkg26U5d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sRduF/dJMb99LTLkT/Se0jMPlKYK8m4Rkg26U5d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sRduF/dJMb99LTLkT/Se0jMPlKYK8m4Rkg26U5d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsRduF%2FdJMb99LTLkT%2FSe0jMPlKYK8m4Rkg26U5d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;278&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;알림 제공자&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림 요청을 만들어 애플 푸시 알림 서비스로 보내는 주체&lt;/li&gt;
&lt;li&gt;요청을 만들기 위해서 다음과 같은 데이터가 필요
&lt;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;페이로드: 알림 내용을 담은 JSON&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;APNS&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;푸시 알림을 iOS 장치로 보내는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iOS 단말&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;푸시 알림을 수신하는 사용자 단말&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;안드로이드 푸시 알림&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 푸시 알림도 비슷한 절차로 전송된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dazP2F/dJMcaacZcu2/8kUrEB6oIEdl5WFV7fLSk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dazP2F/dJMcaacZcu2/8kUrEB6oIEdl5WFV7fLSk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dazP2F/dJMcaacZcu2/8kUrEB6oIEdl5WFV7fLSk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdazP2F%2FdJMcaacZcu2%2F8kUrEB6oIEdl5WFV7fLSk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1326&quot; height=&quot;352&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;APNS 대신 FCM 을 사용&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;SMS 메시지&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMS 메시지를 보낼 때는 트윌리오, 넥스모 같은 제3 사업자의 서비스를 많이 이용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상용 서비스라 이용 요금을 내야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✉️ &lt;b&gt;이메일&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&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;/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;h3 data-ke-size=&quot;size23&quot;&gt;연락처 정보 수집 절차&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림을 보내기 위해서는 모바일 단말 토큰, 전화번호, 이메일 주소 등의 정보가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 앱을 설치하거나 처음으로 계정을 등록하면 API 서버는 사용자의 정보를 수집해 DB에 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D4l9z/dJMcaa47Oed/JFT552Umd3ZHu16uBodUoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D4l9z/dJMcaa47Oed/JFT552Umd3ZHu16uBodUoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D4l9z/dJMcaa47Oed/JFT552Umd3ZHu16uBodUoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD4l9z%2FdJMcaa47Oed%2FJFT552Umd3ZHu16uBodUoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1176&quot; height=&quot;412&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일 주소와 전화번호는 user 테이블에 저장&lt;/li&gt;
&lt;li&gt;단말 토큰은 device 테이블에 저장.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 사용자가 여러 단말을 가질 수 있고 알람은 모든 단말에 전송되어야 하기 때문.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알림 전송 및 수신 절차&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5LZw/dJMcadnbGd5/damJQv49Nd1XvnbyunIo7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5LZw/dJMcadnbGd5/damJQv49Nd1XvnbyunIo7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5LZw/dJMcadnbGd5/damJQv49Nd1XvnbyunIo7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5LZw%2FdJMcadnbGd5%2FdamJQv49Nd1XvnbyunIo7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;488&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-N 까지 서비스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 각각은 마이크로서비스일수도 크론잡일 수도 분산 시스템 컴포넌트일 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제3자 서비스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자에게 알림을 실제로 전달하는 역할&lt;/li&gt;
&lt;li&gt;유의할 것은 확장성&lt;/li&gt;
&lt;li&gt;새로운 서비스틑 통합하거나 기존 서비스를 제거할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iOS, 안드로이드, SMS, 이메일 단말&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자는 자기 단말에서 알림을 수신한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;해당 설계의 문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SPOF&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;알림 서비스에 서버가 한 대인 것은 서버에 장애가 생기면 전체 서비스의 장애로 이어진다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;규모 확장성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;한 대 서비스로 푸시 알림에 관계된 모든 것을 처리하기 때문에 DB, 캐시 등 중요 컴포넌트의 규모를 개별적으로 늘릴 방법이 없다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;성능 병목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;알림을 처리하고 보내는 것은 자원이 많이 필요할 수 있다. 사용자 트래픽이 많이 몰리는 시간에 시스템 과부하 상태에 빠질 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개략적 설계안 (개선 버전)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 방향으로 개선&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB, 캐시를 알림 시스템의 주 서버에서 분리&lt;/li&gt;
&lt;li&gt;알림 서버를 증설하고 자동으로 수평적 규모 확장이 이루어지도록 한다.&lt;/li&gt;
&lt;li&gt;메시지 큐를 이용해 시스템 컴포넌트 사이의 강한 결합을 끊는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUbCXa/dJMb99SGINK/FMssOdTzqbhU293Nkr3VTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUbCXa/dJMb99SGINK/FMssOdTzqbhU293Nkr3VTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUbCXa/dJMb99SGINK/FMssOdTzqbhU293Nkr3VTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUbCXa%2FdJMb99SGINK%2FFMssOdTzqbhU293Nkr3VTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;301&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-N 까지 서비스&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;알림 시스템의 서버의 API를 통해 알림을 보낼 서비스&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;알림 전송 API: 스팸 방지를 위해 사내 서비스 또는 인증된 클라이언트만 이용 가능&lt;/li&gt;
&lt;li&gt;알림 검증: 이메일 주소, 전화번호 등에 대한 기본적 검증 수행&lt;/li&gt;
&lt;li&gt;DB, 캐시 질의: 알림에 포함시킬 데이터를 가져오는 기능&lt;/li&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;/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;이메일 알림 API 예시 - POST &amp;lt;&lt;code&gt;https://api.example.com/v/sms/send&lt;/code&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;{
    &quot;to&quot;: [
    {
       &quot;user_id&quot;: 123456
    }
  ],
  &quot;from&quot;: {
   &quot;email&quot;: &quot;from_address@example.com&quot;
  },
  &quot;subject&quot;: &quot;Hello, World!&quot;,
  &quot;content&quot;: [
       {
          &quot;type&quot;: &quot;text/plain&quot;,
          &quot;value&quot;: &quot;Hello, world!&quot;
       }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;캐시&lt;/td&gt;
&lt;td&gt;사용자 정보, 단말 정보, 알림 템플릿 등을 캐시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;td&gt;사용자, 알림, 설정 등 다양한 정보를 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지 큐&lt;/td&gt;
&lt;td&gt;시스템 컴포넌트 간 의존성을 제거하기 위해 사용. 다량의 알림이 전송되어야 하는 경우를 대비한 버퍼 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작업 서버&lt;/td&gt;
&lt;td&gt;메시지 큐에서 전송할 알림을 꺼내 제 3자 서비스로 전달하는 역할을 하는 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;알림 전송 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API를 호출하여 알림 서버로 알림을 보낸다.&lt;/li&gt;
&lt;li&gt;알림 서버는 사용자 정보, 단말 토큰, 알림 설정 같은 메타데이터를 캐시나 데이터베이스에서 가져온다.&lt;/li&gt;
&lt;li&gt;알림 서버는 전송할 알림에 맞는 이벤트를 만들어 해당 이벤트를 위한 큐에 넣는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 푸시 알림 이벤트는 iOS 푸시 알림 큐에 넣는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;작업 서버는 메시지 큐에서 알림 이벤트를 꺼낸다.&lt;/li&gt;
&lt;li&gt;작업 서버는 알림을 제3자 서비스로 보낸다.&lt;/li&gt;
&lt;li&gt;제3자 서비스는 사용자 단말로 알림을 전송한다.&lt;/li&gt;
&lt;/ol&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;분산 환경에서 운영될 알림 시스템을 설계할 때는 안정성을 확보하기 위한 몇 가지를 반드시 고려해야 한다.&lt;/p&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️⃣ &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;b&gt;소실&lt;/b&gt;되면 안된다.&lt;/li&gt;
&lt;li&gt;지연또는 순서는 틀려도 되지만 사라지면 곤란하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해서 알림 데이터를 데이터베이스에 보관하고 재시도 메커니즘을 구현해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림 로그 데이터베이스를 유지하는 것이 한 가지 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfFknT/dJMcadOfxiJ/3UDvk6aTjq9OSHr5fS2fO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfFknT/dJMcadOfxiJ/3UDvk6aTjq9OSHr5fS2fO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfFknT/dJMcadOfxiJ/3UDvk6aTjq9OSHr5fS2fO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfFknT%2FdJMcadOfxiJ%2F3UDvk6aTjq9OSHr5fS2fO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;401&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;알림 중복 전송 방지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 알림이 여러 번 반복되는 것을 완전히 막는 것은 가능하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템의 특성상 가끔 같은 알림이 중복되어 전송되기도 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복을 방지하는 메커니즘을 도입하고 오류를 신중하게 처리해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;보내야 할 알림이 도착 시 이벤트 ID를 검사하여 이전에 본 적이 있는 이벤트인지 확인&lt;/li&gt;
&lt;li&gt;중복된 이벤트라면 버리고 그렇지 않으면 알림을 발송&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가로 필요한 컴포넌트 및 고려사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 시스템은 많이 복잡하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림 템플릿, 알림 설정, 이벤트 추적, 시스템 모니터링, 처리율 제한 등 필요한 추가 컴포넌트를 알아보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;알림 템플릿&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대형 알림 시스템은 하루에 수백만 건 이상의 알림을 처리한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 알림 메시지 대부분은 형식이 비슷하기 때문에 알림 메시지의 모든 부분을 처음부터 다시 만들 필요가 없도록 템플릿을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️ &lt;b&gt;알림 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 알림 설정을 상세히 조정할 수 있도록 한다.&lt;/p&gt;
&lt;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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;컬럼&lt;/td&gt;
&lt;td&gt;타입&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user_id&lt;/td&gt;
&lt;td&gt;bigint&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;channel&lt;/td&gt;
&lt;td&gt;varchar&lt;/td&gt;
&lt;td&gt;알림이 전송될 채널(푸시 알림, 이메일, SMS 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opt_in&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;해당 채널로 알림을 받을지의 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⛔ &lt;b&gt;전송률 제한&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자에게 너무 많은 알림을 보내지 않도록 하는 방법중 하나는 한 사용자가 받을 수 있는 알림의 빈도를 제한하는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림을 너무 많이 보내면 사용자가 알림 기능을 아예 꺼버릴 수 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;제3자 서비스가 알림 전송에 실패했을 경우 해당 알림을 재시도 전용 큐에 넣는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 문제가 계속 발생할 시 개발자에게 alert&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS와 안드로이드 앱의 경우 알림 전송 API는 appKey와 appSecret을 사용하여 보안을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증된, 승인된 클라이언트만 해당 API를 사용하여 알림을 보낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;큐 모니터링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 시스템을 모니터링할 때 중요한 메트릭중 하나는 큐에 쌓인 알림의 개수이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 수가 너무 크면 작업 서버들이 이벤트를 빠르게 처리하고 있지 못하다는 뜻이다.&lt;/li&gt;
&lt;li&gt;작업 서버를 증설&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이벤트 추적&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 확인율, 클릭율, 실제 앱 사용으로 이어지는 비율 같은 메트릭은 사용자를 이해하는데 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분석 서비스는 보통 이벤트 추적기능도 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림 시스템을 만들기 위해서는 데이터 분석 서비스와도 통합해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정된 설계안&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CMNhN/dJMcai26AKQ/9kml2wAFSWkUKIQNTYJ72k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CMNhN/dJMcai26AKQ/9kml2wAFSWkUKIQNTYJ72k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CMNhN/dJMcai26AKQ/9kml2wAFSWkUKIQNTYJ72k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCMNhN%2FdJMcai26AKQ%2F9kml2wAFSWkUKIQNTYJ72k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;374&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt; &amp;nbsp;Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=CmTO68I2HSc&quot;&gt;https://www.youtube.com/watch?v=CmTO68I2HSc&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://speakerdeck.com/kakao/alrim-seobiseuro-sijaghaneun-seobeo-gaebal&quot;&gt;https://speakerdeck.com/kakao/alrim-seobiseuro-sijaghaneun-seobeo-gaebal&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.im/tmc-25/sessions/engineering/data-35?srsltid=AfmBOopzA7lqkAmIxU2QaKYSr-NbD-IwLJ0zmz5v3R2Oov08HD8VKLNb&quot;&gt;https://toss.im/tmc-25/sessions/engineering/data-35?srsltid=AfmBOopzA7lqkAmIxU2QaKYSr-NbD-IwLJ0zmz5v3R2Oov08HD8VKLNb&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fcTPQAt7KkA&amp;amp;feature=youtu.be&quot;&gt;https://www.youtube.com/watch?v=fcTPQAt7KkA&amp;amp;feature=youtu.be&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bzJJt4YdhgI&quot;&gt;https://www.youtube.com/watch?v=bzJJt4YdhgI&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/10000/&quot;&gt;https://techblog.woowahan.com/10000/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/199</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-10%EC%9E%A5-%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84#entry199comment</comments>
      <pubDate>Mon, 19 Jan 2026 12:31:34 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 9장. 웹 크롤러 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-9%EC%9E%A5-%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%9F%AC-%EC%84%A4%EA%B3%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹 크롤러는 로봇 또는 스파이더라고도 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 엔진에서 널리 쓰이는 기술로 웹에 새로 올라오거나 갱신된 콘텐츠를 찾아내는 것이 주된 목적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  크롤러 활용 예시&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;활용 예시&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색 엔진 인덱싱&lt;/td&gt;
&lt;td&gt;검색 엔진의 로컬 인덱스를 구축하기 위해 사용. 예시: &lt;b&gt;Googlebot&lt;/b&gt; (구글 검색 엔진이 사용하는 웹 크롤러)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹 아카이빙&lt;/td&gt;
&lt;td&gt;웹 사이트 정보를 주기적으로 수집하고 저장하여 아카이빙.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹 마이닝&lt;/td&gt;
&lt;td&gt;인터넷에서 유용한 지식을 찾아 분석해 의미있는 지식을 도출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹 모니터링&lt;/td&gt;
&lt;td&gt;저작권, 상표권이 침해되는 사례를 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 크롤러의 복잡도는 처리해야 하는 데이터의 규모에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학급 프로젝트 수준일 수도 있고 별도의 엔지니어링 팀을 꾸려서 지속적으로 관리하고 개선해야 하는 초대형 프로젝트가 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 설계한 &lt;b&gt;웹 크롤러가 감당해야 하는 데이터의 규모와 기능&lt;/b&gt;들을 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;✅ 문제 이해 및 설계 범위 확정&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  웹 크롤러의 기본 동작 원리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;URL 집합이 입력으로 주어지면 해당 URL들이 가리키는 모든 웹 페이지를 다운로드&lt;/li&gt;
&lt;li&gt;다운받은 웹 페이지에서 URL들을 추출한다.&lt;/li&gt;
&lt;li&gt;추출한 URL을 다시 수집할 URL 목록에 추가하여 이 과정을 반복한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 단순해보이지만 엄청난 규모 확장성을 갖는 웹 크롤러를 설계하는 것은 매우 어려운 작업이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항을 질문을 통해 알아내고 설계 범위를 좁혀야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&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;/li&gt;
&lt;li&gt;매달 얼마나 많은 웹 페이지를 수집해야 하나요?&lt;/li&gt;
&lt;li&gt;새로 만들어진 웹 페이지나 수정된 웹 페이지도 고려해야 하나요?&lt;/li&gt;
&lt;li&gt;수집한 웹 페이지는 저장해야 하나요?&lt;/li&gt;
&lt;li&gt;중복된 콘텐츠는 어떻게 해야 하나요?&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;속성&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;규모 확장성&lt;/td&gt;
&lt;td&gt;웹은 거대하기 때문에 &lt;b&gt;병행성(병렬 처리)&lt;/b&gt; 등을 활용하여 수십억개의 페이지의 대량 데이터를 빠르게 처리할 수 있어야 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안정성&lt;/td&gt;
&lt;td&gt;잘못된 HTML, 장애 서버, 악성 코드 등 비정상적 입력이나 환경에 잘 대응할 수 있어야 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예절&lt;/td&gt;
&lt;td&gt;수집 대상 웹 사이트에 짧은 시간 동안 너무 많은 요청을 보내면 안된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확장성&lt;/td&gt;
&lt;td&gt;새로운 형태의 콘텐츠를 지원하기 쉬워야 한다. (예: 이미지)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  개략적 규모 추정&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;산출된 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;월별 웹페이지 다운로드 수&lt;/td&gt;
&lt;td&gt;10억 개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QPS (초당 요청수)&lt;/td&gt;
&lt;td&gt;10억 / 30 / 24 / 3600 = 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최대 QPS&lt;/td&gt;
&lt;td&gt;2 * QPS = 800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균 웹 페이지 크기&lt;/td&gt;
&lt;td&gt;500KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;월별 저장 용량&lt;/td&gt;
&lt;td&gt;10억 * 500KB = 500TB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5년치 데이터 저장 용량&lt;/td&gt;
&lt;td&gt;500TB * 12 * 5 = 30PB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  개략적 설계안 제시 및 동의 구하기&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGY1bz/dJMcagRHY9z/kR6QqjGHz9Ik2IKMZeJ7Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGY1bz/dJMcagRHY9z/kR6QqjGHz9Ik2IKMZeJ7Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGY1bz/dJMcagRHY9z/kR6QqjGHz9Ik2IKMZeJ7Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGY1bz%2FdJMcagRHY9z%2FkR6QqjGHz9Ik2IKMZeJ7Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;422&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작 URL 집합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 URL 집합은 웹 크롤러가 크롤링을 시작하는 출발점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 웹을 크롤링해야 하는 경우에는 시작 URL을 고를 때 가능한 많은 링크를 탐색할 수 있도록 하는 URL을 고르는 것이 바람직하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로는 전체 URL 공간을 작은 부분 집합으로 나누는 전략을 사용&lt;/li&gt;
&lt;li&gt;예를 들어 URL 공간을 쇼핑, 스포츠, 건강 등 주제별로 세분화하고 각각에 다른 시작 URL을 사용&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;미수집 URL 저장소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 웹 크롤러는 크롤링 상태를 2가지로 나누어 관리한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운로드할 URL&lt;/li&gt;
&lt;li&gt;다운로드된 URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드할 URL을 저장 관리하는 컴포넌트를 &lt;code&gt;미수집 URL 저장소(URL frontier)&lt;/code&gt; 라고 부른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FIFO Queue&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;HTML 다운로더&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지를 실제 다운로드 하는 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드할 페이지의 URL은 미수집 URL 저장소가 제공&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;웹 페이지를 다운로드 받기 위해서는 URL을 IP 주소로 변환해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 다운로더는 도메인 이름 변환기를 통해 URL의 IP 주소를 알아낸다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상한 웹 페이지는 문제를 발생시킬 수 있고 저장 공간 낭비이기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤링 서버 안에 콘텐츠 파서를 구현하면 크롤링 과정이 느려질 수 있기 때문에 독립된 컴포넌트로 만든다.&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;공개된 연구 결과에 따르면 29% 가량의 웹 페이지 콘텐츠가 중복이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 같은 콘텐츠를 여러 번 저장할 수 있기 때문에 &lt;b&gt;데이터 중복을 줄이고 데이터 처리에 소요되는 시간을 줄이기&lt;/b&gt; 위해서 해시 값을 비교하는 등의 방법을 이용해서 이미 시스템에 저장된 콘텐츠인지 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;콘텐츠 저장소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 문서를 보관하는 시스템으로 데이터의 유형, 크기, 저장소 접근 빈도, 데이터 유효 기간등을 고려해야 한다.&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;URL 추출기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 문서들을 파싱하여 링크들을 골라내는 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXOgBP/dJMcaiWgrrR/aNr4g9PfzlsKXnhcqoUE30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXOgBP/dJMcaiWgrrR/aNr4g9PfzlsKXnhcqoUE30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXOgBP/dJMcaiWgrrR/aNr4g9PfzlsKXnhcqoUE30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXOgBP%2FdJMcaiWgrrR%2FaNr4g9PfzlsKXnhcqoUE30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;337&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상대 경로는 모두 절대 경로로 변환&lt;/li&gt;
&lt;/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;URL 필터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출한 URL을 걸러내어 크롤링 대상에서 배제하는 역할&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 콘텐츠 타입이나 파일 확장자를 갖는 URL (예: jpg, zip 등 제외)&lt;/li&gt;
&lt;li&gt;접속시 오류가 발생하는 URL&lt;/li&gt;
&lt;li&gt;접근 제외 목록 (deny list)에 포함된 URL&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;이미 방문한 URL?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 방문한 적이 있는 URL인지 확인하면 같은 URL을 여러 번 처리하는 일을 방지할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 방문한 URL, 미수집 URL 저장소에 보관된 URL을 추적할 수 있는 자료구조를 사용&lt;/li&gt;
&lt;li&gt;블룸 필터나 해시 테이블이 널리 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;URL 저장소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 방문한 URL을 보관하는 저장소&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RHbiy/dJMcac2OuEq/xKI6TnfOGS4tBHEkhzNguK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RHbiy/dJMcac2OuEq/xKI6TnfOGS4tBHEkhzNguK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RHbiy/dJMcac2OuEq/xKI6TnfOGS4tBHEkhzNguK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRHbiy%2FdJMcac2OuEq%2FxKI6TnfOGS4tBHEkhzNguK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;380&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시작 URL들을 미수집 URL 저장소에 저장&lt;/li&gt;
&lt;li&gt;HTML 다운로더는 미수집 URL 저장소에서 URL 목록을 가져온다.&lt;/li&gt;
&lt;li&gt;HTML 다운로더는 도메인 이름 변환기를 사용해 URL의 IP 주소를 가져와 접속해 웹 페이지를 다운&lt;/li&gt;
&lt;li&gt;콘텐츠 파서는 다운된 HTML 페이지를 파싱해 올바른 페이지인지 검증&lt;/li&gt;
&lt;li&gt;콘텐츠 파싱과 검증이 끝나면 중복 콘텐츠인지 확인&lt;/li&gt;
&lt;li&gt;해당 페이지가 이미 저장소에 저장되어 있는지 확인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장소에 없다면 저장소에 저장한 뒤 URL 추출기로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;URL 추출기는 해당 HTML 페이지에서 링크를 골라낸다.&lt;/li&gt;
&lt;li&gt;골라낸 링크를 URL 필터로 전달&lt;/li&gt;
&lt;li&gt;필터링이 끝나고 남은 URL만 중복 URL 판별 단계로 전달&lt;/li&gt;
&lt;li&gt;이미 처리한 URL인지 확인하기 위해 URL 저장소에 보관된 URL인지 확인&lt;/li&gt;
&lt;li&gt;저장소에도 없는 URL은 URL 저장소에 저장하고 미수집 URL 저장소에도 전달한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  상세 설계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 컴포넌트와 구현 기술을 살펴보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DFS vs BFS&lt;/li&gt;
&lt;li&gt;미수집 URL 저장소&lt;/li&gt;
&lt;li&gt;HTML 다운로더&lt;/li&gt;
&lt;li&gt;안정성 확보 전략&lt;/li&gt;
&lt;li&gt;확장성 확보 전략&lt;/li&gt;
&lt;li&gt;문제 있는 콘텐츠 감지 및 회피 전략&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS를 쓸 것인가 BFS를 쓸 것인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹은 유향 그래프(directed graph)와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지는 노드, 하이퍼링크는 엣지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;u&gt;&lt;b&gt;크롤링 프로세스는 &lt;span style=&quot;color: #ee2323;&quot;&gt;유향 그래프를 에지를 따라 탐색&lt;/span&gt;하는 과정이다.&lt;/b&gt;&lt;/u&gt;&lt;/p&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;DFS&lt;/code&gt;, &lt;code&gt;BFS&lt;/code&gt;는 그래프 탐색에 널리 사용되는 알고리즘이다.&lt;/p&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;DFS&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 선택지는 아니다.&lt;/li&gt;
&lt;li&gt;그래프 크기가 클 수록 어느정도로 깊게 탐색할지 가늠하기 어렵기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;BFS&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 FIFO queue 를 사용하는 알고리즘&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;⚠️ BFS FIFO queue 방식의 2가지 문제점&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;/li&gt;
&lt;li&gt;BFS queue는 우선순위를 다루지 못해 페이지의 순위, 사용자 트래픽의 양 등 여러 기준을 반영해 처리 우선순위를 구별해주어야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;미수집 URL 저장소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미수집 URL 저장소를 활용하면 예의를 갖춘 크롤러, URL 사이의 우선순위와 신선도를 구별하는 크롤러를 구현할 수 있다.&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;수집 대상 서버로 짧은 시간안에 너무 많은 요청을 보내는 것을 삼가야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일 웹 사이트에 대해서는 한 번에 한 페이지만 요청&lt;/li&gt;
&lt;li&gt;같은 웹 사이트의 페이지를 다운받는 태스크는 시간차를 두고 실행하도록 하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;유용성에 따라 URL의 우선순위를 나눌 때는 페이지랭크, 트래픽 양, 갱신 빈도 등의 다양한 척도를 사용할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;구글 검색에 쓰이는 알고리즘&lt;/li&gt;
&lt;li&gt;상대적 중요도에 따라 가중치를 부여&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순위 결정 장치는 URL 우선순위를 정하는 컴포넌트로 큐에 URL을 저장하기전에 순위 결정 장치를 거치도록 설계를 수정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPTvZH/dJMcadN95Kt/Cv2QZW47HJrk6m0Yi3dyx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPTvZH/dJMcadN95Kt/Cv2QZW47HJrk6m0Yi3dyx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPTvZH/dJMcadN95Kt/Cv2QZW47HJrk6m0Yi3dyx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPTvZH%2FdJMcadN95Kt%2FCv2QZW47HJrk6m0Yi3dyx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;749&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전면 큐: 우선순위 결정 과정 처리&lt;/li&gt;
&lt;li&gt;후면 큐: 크롤러가 예의 바르게 동작하도록 보증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;신선도&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지는 수시로 추가, 삭제, 변경되기 때문에 데이터의 신선도를 유지하기 위해서는 이미 다운로드한 페이지라고 하더라도 재수집할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 URL을 재수집하는 것은 많은 자원이 필요하기 때문에 이 작업을 최적화하기 위한 전략으로는 다음과 같은 것들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 페이지의 변경 이력 활용&lt;/li&gt;
&lt;li&gt;우선순위를 활용해 중요한 페이지는 자주 재수집&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;미수집 URL 저장소를 위한 지속정 저장장치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 엔진을 위한 크롤러의 경우 처리해야 하는 URL의 수는 수억 개.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 URL을 메모리에 저장하는 방식: 안정성이나 확장성 측면에서 문제&lt;/li&gt;
&lt;li&gt;모든 URL을 디스크에 저장: 느려서 성능저하 (병목 지점)&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;따라서 절충안으로 대부분의 URL은 디스크에 두지만 IO 비용을 줄이기 위해 메모리 버퍼에 queue를 두어 관리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버퍼에 있는 데이터는 주기적으로 디스크에 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTML 다운로더&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 다운로더는 HTTP 프로토콜을 통해 웹 페이지를 내려받는다.&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;Robots.txt&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일에는 크롤러가 수집해도 되는 페이지 목록이 들어있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;크롤러는 사이트를 다운로드하기 전에 해당 파일에 나열된 규칙을 먼저 확인해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Robots.txt 파일을 중복으로 다운로드하는 것을 피하기 위해 주기적을도 다시 다운받아 캐시에 보관한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;User-agent: Googlebot
Disallow: /creatorhub/*&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;code&gt;Creatorhub&lt;/code&gt;와 같은 디렉터리 내용은 다운받을 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;성능 최적화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTML 다운로더&lt;/b&gt;에 사용할 수 있는 성능 최적화 기법&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기법&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분산 크롤링&lt;/td&gt;
&lt;td&gt;크롤링 작업을 여러 서버에 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도메인 이름 변환 결과 캐시&lt;/td&gt;
&lt;td&gt;DNS resolver는 크롤러 성능의 병목 중 하나. DNS 조회 결과로 얻은 도메인 이름과 IP 주소를 캐시에 보관하고 주기적으로 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지역성&lt;/td&gt;
&lt;td&gt;크롤링 작업을 수행하는 서버를 지역별로 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;짧은 타임아웃&lt;/td&gt;
&lt;td&gt;어떤 웹 서버는 응답이 느리거나 아예 응답하지 않기 때문에 최대 얼마나 크롤러가 기달릴지를 정해두고 타임아웃의 경우 해당 페이지 다운을 중단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;안정성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안정성&lt;/b&gt; 역시 최적화된 성능처럼 설계시 중요한 부분이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기법&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분산 크롤링&lt;/td&gt;
&lt;td&gt;크롤링 작업을 여러 서버에 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도메인 이름 변환 결과 캐시&lt;/td&gt;
&lt;td&gt;DNS resolver는 크롤러 성능의 병목 중 하나. DNS 조회 결과로 얻은 도메인 이름과 IP 주소를 캐시에 보관하고 주기적으로 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지역성&lt;/td&gt;
&lt;td&gt;크롤링 작업을 수행하는 서버를 지역별로 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;짧은 타임아웃&lt;/td&gt;
&lt;td&gt;어떤 웹 서버는 응답이 느리거나 아예 응답하지 않기 때문에 최대 얼마나 크롤러가 기달릴지를 정해두고 타임아웃의 경우 해당 페이지 다운을 중단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;새로운 형태의 콘텐츠를 쉽게 지원할 수 있도록 신경써야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 새로운 모듈을 추가함으로 새로운 형태의 콘텐츠를 지원할 수 있도록 설계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O9o4u/dJMcaiaVKss/JAeHT6W9VOXAl53NtnAnA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O9o4u/dJMcaiaVKss/JAeHT6W9VOXAl53NtnAnA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O9o4u/dJMcaiaVKss/JAeHT6W9VOXAl53NtnAnA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO9o4u%2FdJMcaiaVKss%2FJAeHT6W9VOXAl53NtnAnA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;347&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PNG 다운로더는 PNG 파일을 다운로드하는 모듈&lt;/li&gt;
&lt;li&gt;웹 모니터는 웹을 모니터링하여 저작권, 상표권 침해를 막는 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ &lt;b&gt;문제 있는 콘텐츠 감지 및 회피&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 중복 콘텐츠&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시나 체크섬 등의 방법을 통해 중복 콘텐츠를 쉽게 탐지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 거미 덫 (spider trap)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤러를 무한루프에 빠뜨리도록 설계한 웹 페이지&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무한히 깊은 디렉토리 구조 (&lt;code&gt;spidertrapexample.com/foo/bar/foo/bar/foo/bar&amp;hellip;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL의 최대 길이를 제한하면 회피할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 종류를 다 피할수는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수작업을 통해 확인하고 찾아내어 크롤러 탐색 대상에서 제외하거나 URL 제외 필터 목록에 넣어둔다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;광고, 스크립트 코드 등 이런 콘텐츠는 가능한 제외해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  마무리&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋은 크롤러가 갖추어야하는 특성&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;특성&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;규모 확장성&lt;/td&gt;
&lt;td&gt;수십억개의 웹 페이지를 수집하고 처리할 수 있도록 분산 시스템 구조, 확장 가능한 저장소 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예의&lt;/td&gt;
&lt;td&gt;크롤러 대상 서버에 과도한 부하를 주지 않도록 한 호스트에 일정 시간동안 간격을 두고 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확장성&lt;/td&gt;
&lt;td&gt;새로운 콘텐츠 유형이 추가될 때 쉽게 기능을 확장할 수 있어야 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안정성&lt;/td&gt;
&lt;td&gt;예외 상황에서도 서비스가 멈추지 않도록 장애 대응, 데이터 무결성, 자동 복구 등을 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/googlebot?hl=ko&quot;&gt;https://developers.google.com/search/docs/crawling-indexing/googlebot?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://crystal.uta.edu/~mcguigan/cse6350/papers/Bloom.pdf&quot;&gt;https://crystal.uta.edu/~mcguigan/cse6350/papers/Bloom.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/198</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-9%EC%9E%A5-%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%9F%AC-%EC%84%A4%EA%B3%84#entry198comment</comments>
      <pubDate>Sun, 4 Jan 2026 23:38:01 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 8장. URL 단축기 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-8%EC%9E%A5-URL-%EB%8B%A8%EC%B6%95%EA%B8%B0-%EC%84%A4%EA%B3%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  단축 URL을 사용하는 이유?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 읽기 전에 왜 URL 단축기를 사용하여 단축된 URL을 사용하는지 궁금해져 한 번 정리를 해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.4419%;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 82.4418%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.4419%;&quot;&gt;공간 절약 및 가독성&lt;/td&gt;
&lt;td style=&quot;width: 82.4418%;&quot;&gt;웹사이트 링크는 많은 공간을 차지하기 때문에 SNS 환경이나 메시지 전송시에서 불편할 수 있다. 단축 URL은 가독성이 좋게 글자 수를 절약하며 링크를 전달할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.4419%;&quot;&gt;클릭 추적 및 분석&lt;/td&gt;
&lt;td style=&quot;width: 82.4418%;&quot;&gt;언제 / 어디서 / 누가 클릭을 했는지 데이터 추적이 가능하다. (국가, 시간대, 디바이스 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.4419%;&quot;&gt;마케팅 효과&lt;/td&gt;
&lt;td style=&quot;width: 82.4418%;&quot;&gt;A/B 테스트 등을 통해 어떤 메시지가 더 높은 클릭을 유도하는지 확인. 유입 경로(SNS, 이메일, 블로그 등)를 분석해 어떤 채널이 효과적인지 파악&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.4419%;&quot;&gt;주소 마스킹&lt;/td&gt;
&lt;td style=&quot;width: 82.4418%;&quot;&gt;원래 URL을 감춰 보안에 좀 더 유리. 민감한 정보 노출을 막음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 문제 이해 및 설계 범위 확정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단축 URL 시스템의 기본적 기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL 단축: 주어진 긴 URL을 짧게 줄인다.&lt;/li&gt;
&lt;li&gt;URL 리다이렉션: 축약된 URL로 HTTP 요청이 오면 원래 URL로 안내&lt;/li&gt;
&lt;li&gt;높은 가용성과 규모 확장성, 장애 감내&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개략적 추정&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; 매일 1억 개 단축 URL 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초당 1억 / 24 / 3600 = 1160개 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;읽기 :&lt;/b&gt; 읽기 쓰기 비율이 10 : 1이라고 가정하면 초당 11,600회 발생한다.&lt;/li&gt;
&lt;li&gt;URL 단축 서비스를 10년 운영한다 가정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1억 * 365 * 10 = 3650 억개 레코드 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;축약전 URL 평균 길이 100이라 가정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;10년간 필요한 저장 용량은 100Byte * 3650억 = 36.5TB&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;장애 감내 (Fault Tolerance)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템의 일부에 오류, 고장이 발생해도 전체 시스템이 멈추지 않고 정상적으로 동작하거나 최소한의 기능이라도 유지하는 능력&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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;h3 data-ke-size=&quot;size23&quot;&gt;API 엔드포인트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API로 엔드포인트를 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. URL 단축용 엔드포인트&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: #ee2323;&quot;&gt;&lt;code&gt;POST /api/v1/data/shorten&lt;/code&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;새 단축 URL을 생성하는 엔드포인트&lt;/li&gt;
&lt;li&gt;인자로 단축할 URL을 실어줘야 한다.&lt;/li&gt;
&lt;li&gt;반환값: 단축 URL&lt;b&gt;&lt;/b&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;2. URL 리다이렉션용 엔드포인트&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: #006dd7;&quot;&gt;&lt;code&gt;GET /api/v1/shortUrl&lt;/code&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;단축 URL에 대해서 원래 URL로 보내주기 위한 엔드포인트&lt;/li&gt;
&lt;li&gt;반환값: HTTP 리다이렉션 목적지가 될 원래 URL&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;URL 리다이렉션&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfyOQh/dJMcagxo5jq/BbtqYZwQ2StQsUv7otHYTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfyOQh/dJMcagxo5jq/BbtqYZwQ2StQsUv7otHYTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfyOQh/dJMcagxo5jq/BbtqYZwQ2StQsUv7otHYTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfyOQh%2FdJMcagxo5jq%2FBbtqYZwQ2StQsUv7otHYTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;323&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 브라우저에 단축 URL을 입력하면 무슨 일이 생기는지 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단축 URL을 받은 서버는 그 URL을 원래 URL로 바꾸어 &lt;code&gt;301&lt;/code&gt; 응답의 &lt;code&gt;Location&lt;/code&gt; 헤더에 넣어 반환&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LdZCF/dJMb99LOtCy/jIrdjFeygHjjJlouFmCJok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LdZCF/dJMb99LOtCy/jIrdjFeygHjjJlouFmCJok/img.png&quot; data-alt=&quot;클라이언트와 서버와 통신&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LdZCF/dJMb99LOtCy/jIrdjFeygHjjJlouFmCJok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLdZCF%2FdJMb99LOtCy%2FjIrdjFeygHjjJlouFmCJok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;479&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클라이언트와 서버와 통신&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;301 응답과 302 응답&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;301 응답과 302 응답 둘 다 리다이렉션 응답이지만 차이가 있다.&lt;/p&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;code&gt;301 Permanently Moved&lt;/code&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;해당 URL에 대한 HTTP 요청의 처리 책임이 영구적으로 Location 헤더에 반환된 URL로 이전되었다는 응답.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영구적으로 이전되어 브라우저는 이 응답을 &lt;b&gt;캐시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 추후 같은 단축 URL에 요청을 보낼때 브라우저는 캐시된 원래 URL로 요청을 보내게 된다.&lt;/p&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;code&gt;302 Found&lt;/code&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;주어진 URL로의 요청이 일시적으로 Location 헤더가 지정하는 URL에 의해 처리되어야 한다는 응답.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 클라이언트의 요청은 언제나 단축 URL 서버에 먼저 보내진 이후 원래 URL로 리다이렉션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;서버 부하를 줄이는 것이 중요한 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;301 Permanent Moved&lt;/code&gt; 를 사용하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;첫 번째 요청만 단축 URL 서버로 전송될 것이기 때문&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;302 Found&lt;/code&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;URL 리다이렉션을 구현하는 가장 직관적인 방법은 &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;b&gt;&amp;lt;단축 URL, 원래 URL&amp;gt;&lt;/b&gt; 쌍을 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원래 URL = hashTable.get(단축 URL)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;301 또는 302 응답 Location 헤더에 원래 URL을 넣은 후 전송&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;URL 단축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단축 URL이 &lt;code&gt;www.tinyurl.com/{hashValue}&lt;/code&gt; 라고 가정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐ 중요한 것은 긴 URL을 해당 해시 값으로 대응시킬 해시 함수 &lt;b&gt;fx&lt;/b&gt; 를 찾는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ORspc/dJMcachtuii/LEdskSSKFVmdoKsom1ngV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ORspc/dJMcachtuii/LEdskSSKFVmdoKsom1ngV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ORspc/dJMcachtuii/LEdskSSKFVmdoKsom1ngV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FORspc%2FdJMcachtuii%2FLEdskSSKFVmdoKsom1ngV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;391&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해시 함수 fx&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;입력으로 주어지는 긴 URL이 다른 값이라면 해시 값도 달라야 한다.&lt;/li&gt;
&lt;li&gt;계산된 해시 값은 원래 입력으로 주어졌던 긴 URL로 복원될 수 있어야 한다.&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;3️⃣ 상세 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블을 사용하는 것은 초기 전략으로는 괜찮지만 실제 시스템에서 사용하기에는 힘들다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리는 유한하고 비싸기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나은 방법은 &lt;b&gt;&amp;lt;단축 URL, 원래 URL&amp;gt;&lt;/b&gt; 의 순서쌍을 &lt;code&gt;&lt;b&gt;RDB&lt;/b&gt;&lt;/code&gt; 에 저장하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WAHUW/dJMb99LOtCH/PdmHTrpqIPKuuQOwTjotSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WAHUW/dJMb99LOtCH/PdmHTrpqIPKuuQOwTjotSK/img.png&quot; data-alt=&quot;간단한 RDB 설계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WAHUW/dJMb99LOtCH/PdmHTrpqIPKuuQOwTjotSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWAHUW%2FdJMb99LOtCH%2FPdmHTrpqIPKuuQOwTjotSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;395&quot; height=&quot;312&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;간단한 RDB 설계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;shortURL&lt;/code&gt;, &lt;code&gt;longURL&lt;/code&gt; 세 개 컬럼&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해시 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수는 원래 URL을 단축 URL으로 변환하는데 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수가 계산하는 단축 URL 값을 &lt;b&gt;hashValue&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashValue 값으로 사용 가능 문자가 &lt;code&gt;0~9, a-Z&lt;/code&gt; 까지 총 62개이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 3650억개의 레코드를 보관할 수 있어야 하기 때문에 몇 자리의 문자가 필요한지 계산을 해본다면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ew8Xyd/dJMcah39DZF/3WPkH2DplPDjo1hnbbo4R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ew8Xyd/dJMcah39DZF/3WPkH2DplPDjo1hnbbo4R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ew8Xyd/dJMcah39DZF/3WPkH2DplPDjo1hnbbo4R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Few8Xyd%2FdJMcah39DZF%2F3WPkH2DplPDjo1hnbbo4R1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;366&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;552&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;62^n 이 3650억 개보다 커지는 n을 찾아야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;n이 7이면 3.5조 개의 URL을 만들 수 있다.&lt;/li&gt;
&lt;li&gt;따라서 해시 값의 길이는 7로 한다.&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 URL을 7글자 문자열로 줄이는 해시 함수가 필요하다.&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;CRC32, MD5, SHA-1&lt;/b&gt; 같이 잘 알려진 해시 함수를 이용하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;해시 함수&lt;/td&gt;
&lt;td&gt;해시 결과 (16진수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC32&lt;/td&gt;
&lt;td&gt;5cb54054&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MD5&lt;/td&gt;
&lt;td&gt;5a62509a84df9ee03fe1230b9dfb84e&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;0eeae7916c06853901d9ccvefvfcaf4de57ed85b&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 짧은 해시 값인 CRC32가 계산한 것의 길이도 7보다 길다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 어떻게 하면 줄일 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;계산된 해시 값에서 처음 7개 글자만 이용하기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해시 결과가 서로 충돌할 확률이 높아진다.&lt;/li&gt;
&lt;li&gt;실제로 충돌이 일어났을 경우 충돌이 해소될 때까지 사전에 정한 문자열을 해시값에 덧붙인다.&lt;/li&gt;
&lt;li&gt;충돌을 해소할 수는 있지만 단축 URL을 생성할 때 한 번 이상 DB 질의를 해야해 오버헤드 가 크다.&lt;/li&gt;
&lt;li&gt;DB 대신 블룸 필터를 사용하면 성능을 높일 수 있다.&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;base-62 변환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수의 표현 방식이 다른 두 시스템이 같은 수를 공유해야 하는 경우에 유용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;62진법을 사용하는 이유는 hashValue에 사용할 수 있는 문자 개수가 62개이기 때문&lt;/p&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;11157&lt;/code&gt;을 62진수로 변환&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;0은 0&lt;/code&gt;으로 &lt;code&gt;9는 9&lt;/code&gt;로, &lt;code&gt;10은 a&lt;/code&gt; , &lt;code&gt;61은 Z&lt;/code&gt;로 표현&lt;/li&gt;
&lt;li&gt;&lt;code&gt;11157 (10) = 2TX (62)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단축 URL은 &lt;code&gt;https://tinyurl.com/2TX&lt;/code&gt; 가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;두 접근법 비교&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;해시 후 충돌 해소 전략&lt;/td&gt;
&lt;td&gt;base-62 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단축 URL의 길이가 고정&lt;/td&gt;
&lt;td&gt;단축 URL의 길이가 가변적, ID 값이 커지면 같이 커짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유일성이 보장되는 ID 생성기가 필요하지 않음&lt;/td&gt;
&lt;td&gt;유일성 보장 ID 생성기가 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;충돌이 가능 - 해소 전략 필요&lt;/td&gt;
&lt;td&gt;ID 유일성이 보장되고 난 후 적용하기 때문에 충돌 불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ID로 부터 단축 URL을 계산하는 방식이 아니라 다음에 사용할 수 있는 URL을 알아내는 것이 불가능&lt;/td&gt;
&lt;td&gt;ID가 1씩 증가하는 값이라고 가정하면 다음에 사용할 수 있는 단축 URL이 무엇인지 쉽게 알 수 있어 보안상 문제가 될 수 있음.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;URL 단축키 상세 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL 단축키는 시스템의 핵심 컴포넌트로 처리 흐름이 논리적으로는 단순해야 하고 기능적으로는 언제나 동작하는 상태로 유지되어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;62진법 (base-62) 변환 기법을 사용해 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;1074&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBzXV4/dJMb99SAOkO/ksdQTjiBGTaOZ6qcCl5b1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBzXV4/dJMb99SAOkO/ksdQTjiBGTaOZ6qcCl5b1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBzXV4/dJMb99SAOkO/ksdQTjiBGTaOZ6qcCl5b1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBzXV4%2FdJMb99SAOkO%2FksdQTjiBGTaOZ6qcCl5b1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;498&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;1074&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;입력으로 긴 URL을 받는다.&lt;/li&gt;
&lt;li&gt;데이터베이스에 해당 URL이 있는지 검사&lt;/li&gt;
&lt;li&gt;있다면 해당 URL에 대한 단축 URL을 만든적이 있다는 것으로 해당 단축 URL을 가져와 반환&lt;/li&gt;
&lt;li&gt;없다면 유일한 ID를 생성한 후 데이터베이스의 기본키로 사용&lt;/li&gt;
&lt;li&gt;62진법 변환을 적용하여 ID를 단축 URL으로 만든다.&lt;/li&gt;
&lt;li&gt;ID, 단축 URL, 원래 URL로 새 데이터베이스 record를 만든 후 단축 URL을 클라이언트에 전달한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;URL 리다이렉션 상세 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기보다 읽기를 자주하는 시스템이기 때문에 캐시에 저장하여 성능을 높인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AUZE2/dJMcahQCQNg/3YwO2Rk6oAphcXXkrNUxZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AUZE2/dJMcahQCQNg/3YwO2Rk6oAphcXXkrNUxZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AUZE2/dJMcahQCQNg/3YwO2Rk6oAphcXXkrNUxZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAUZE2%2FdJMcahQCQNg%2F3YwO2Rk6oAphcXXkrNUxZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;345&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;로드밸런서 동작 흐름&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 단축 URL을 클릭&lt;/li&gt;
&lt;li&gt;로드밸런서가 해당 클릭으로 발생한 요청을 웹 서버에 전달&lt;/li&gt;
&lt;li&gt;캐시에 해당 단축 URL이 없는 경우 데이터베이스에서 꺼낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스에도 없다면 사용자가 잘못된 단축 URL을 입력한 경우이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터베이스에서 꺼낸 URL을 캐시에 넣은 후 사용자에게 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vivoldi.com/blog/url-shortener/why-use-long-links-to-be-shortened&quot;&gt;https://vivoldi.com/blog/url-shortener/why-use-long-links-to-be-shortened&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vivoldi.com/blog/url-shortener/track-realtime-clicks&quot;&gt;https://vivoldi.com/blog/url-shortener/track-realtime-clicks&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/aws/comments/133gtno/what_database_to_use_for_url_shortener_project/&quot;&gt;https://www.reddit.com/r/aws/comments/133gtno/what_database_to_use_for_url_shortener_project/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/197</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-8%EC%9E%A5-URL-%EB%8B%A8%EC%B6%95%EA%B8%B0-%EC%84%A4%EA%B3%84#entry197comment</comments>
      <pubDate>Sun, 4 Jan 2026 23:28:17 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 7장. 분산 시스템을 위한 유일 ID 생성기 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-7%EC%9E%A5-%EB%B6%84%EC%82%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9C%A0%EC%9D%BC-ID-%EC%83%9D%EC%84%B1%EA%B8%B0-%EC%84%A4%EA%B3%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 문제 이해 및 설계 범위 확정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문을 통해서 요구사항을 이해하고 모호함을 해소하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ID는 &lt;b&gt;유일&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;li&gt;ID는 &lt;b&gt;숫자&lt;/b&gt;로만 구성되어야 한다.&lt;/li&gt;
&lt;li&gt;ID는 64비트로 표현될 수 있는 값이어야 한다.&lt;/li&gt;
&lt;li&gt;ID는 &lt;b&gt;발급 날짜에 따라 정렬&lt;/b&gt; 가능해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;초당 10,000&lt;/b&gt;개의 ID를 만들 수 있어야 한다.&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;2️⃣ 개략적 설계안 제시 및 동의 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분산 시스템에서 유일성이 보장되는 ID&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;다중 마스터 복제 (Multi-master replication)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UUID (Universally Unique Identifier)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;티켓 서버 (ticket server)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트위터 스노플레이트 접근법 (snowflake)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;copy;️ 다중 마스터 복제&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;auto_increment&lt;/b&gt; 기능을 활용하는 방법&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ID 값을 구할 때 1만큼 증가시켜 얻는 것이 아닌 &lt;code&gt;k&lt;/code&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;code&gt;k&lt;/code&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;를 어느 정도 해결할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 수를 늘리면 초당 생산 가능 ID 수도 늘릴 수 있기 때문이다.&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;&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;ID의 유일설은 보장되겠지만 그 값이 시간 흐름에 맞추어 커지도록 보장할 수 없다.&lt;/li&gt;
&lt;li&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  UUID&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 시스템에 저장되는 정보를 유일하게 식별하기 위한 128비트 수&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;독립적으로 생성이 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUID를 만드는 것은 단순.&lt;/td&gt;
&lt;td&gt;ID가 128비트로 길다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 사이의 조율이 필요 없어 동기화 이슈도 없다.&lt;/td&gt;
&lt;td&gt;ID를 시간순으로 정렬할 수 없다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;각 서버에서 ID를 알아서 만드는 구조로 규모 확장이 쉽다.&lt;/td&gt;
&lt;td&gt;ID에 숫자가 아닌 값이 포함될 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 티켓 서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티켓 서버는 유일성이 보장되는 ID를 만들어 내는 데 쓰일 수 있는 방법 중 하나이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;플리커&lt;/code&gt;는 분산 기본 키를 만들어 내기 위해 이 기술을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJf3IG/dJMcafZvlEm/NYHtDoqwRKHES2wtkFO3RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJf3IG/dJMcafZvlEm/NYHtDoqwRKHES2wtkFO3RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJf3IG/dJMcafZvlEm/NYHtDoqwRKHES2wtkFO3RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJf3IG%2FdJMcafZvlEm%2FNYHtDoqwRKHES2wtkFO3RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;738&quot; height=&quot;206&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;auto_increment&lt;/code&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유일성이 보장되는 숫자로만 구성된 ID를 쉽게 만들 수 있다.&lt;/td&gt;
&lt;td&gt;티켓 서버가 SPOF가 된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현하기 쉽고 중소 규모 애플리케이션에 적합하다.&lt;/td&gt;
&lt;td&gt;이슈를 피하기 위해서는 티켓 서버를 여러 대 준비해야 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;티켓 서버가 여러대라면 동기화 문제가 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❄️ 트위터 스노플레이크 접근법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트위터는 &lt;code&gt;스노플레이크(snowflake)&lt;/code&gt;라는 &lt;b&gt;ID 생성 기법&lt;/b&gt;을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 이번 장에서의 문제 요구사항을 만족시킬 수 있다  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqsyAE/dJMcaiaRJuS/eRzkJdjTqKEEGcDfAlHcUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqsyAE/dJMcaiaRJuS/eRzkJdjTqKEEGcDfAlHcUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqsyAE/dJMcaiaRJuS/eRzkJdjTqKEEGcDfAlHcUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqsyAE%2FdJMcaiaRJuS%2FeRzkJdjTqKEEGcDfAlHcUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;165&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;268&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;타입&lt;/td&gt;
&lt;td&gt;비트&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사인 비트(sign)&lt;/td&gt;
&lt;td&gt;1비트&lt;/td&gt;
&lt;td&gt;음수와 양수를 구별하는데 사용. 미래 확장을 위함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타임스탬프&lt;/td&gt;
&lt;td&gt;41비트&lt;/td&gt;
&lt;td&gt;Epoch(기준 시각. 2010-11-04 01:42:54 UTC)이후 경과한 밀리초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터센터 ID&lt;/td&gt;
&lt;td&gt;5비트&lt;/td&gt;
&lt;td&gt;최대 32개의 데이터센터 구분가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 ID&lt;/td&gt;
&lt;td&gt;5비트&lt;/td&gt;
&lt;td&gt;데이터센터당 32개의 서버를 사용가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일련번호&lt;/td&gt;
&lt;td&gt;12비트&lt;/td&gt;
&lt;td&gt;각 서버에서 ID를 생성할 때 일련번호를 1만큼 증가. 1밀리초가 경과할 때마다 0으로 초기화.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ 상세 설계&lt;/h2&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;데이터 센터 ID와 서버 ID의 경우는 시스템이 시작할 때 결정되며 &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;데이터 센터 ID나 서버 ID를 실수로 바꾸게 되면 &lt;b&gt;ID 충돌&lt;/b&gt;이 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;따라서 서버 증설, 설정 변경 시에는 반드시 &lt;b&gt;ID 중복 여부를 철저히 검증&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⏱️ 타임스탬프&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임스탬프 값은 ID 생성기가 동작할 때 &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;code&gt;41비트&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;시간의 흐름에 따라 큰 값을 갖게되어 ID는 시간 순으로 정렬이 가능하게 된다.&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;ID 구조에서 UTC 시각을 추출할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkVJ3S/dJMcaaDTMAp/l23qjkMkYQ2v6pCYi8rk1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkVJ3S/dJMcaaDTMAp/l23qjkMkYQ2v6pCYi8rk1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkVJ3S/dJMcaaDTMAp/l23qjkMkYQ2v6pCYi8rk1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkVJ3S%2FdJMcaaDTMAp%2Fl23qjkMkYQ2v6pCYi8rk1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;306&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;41비트로 표현할 수 있는 타임스탬프의 최댓값은 &lt;b&gt;2199023255551 밀리초로 대략 69년&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;69년이 지나면 기원 시각을 바꾸거나 ID 체계를 다른 것으로 이전&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  일련번호&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일변번호는 &lt;b&gt;12비트로 4096(2의 12제곱)&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;동일한 밀리초내에서 하나 이상의 ID를 만들어낸 경우에만 0보다 큰 값을 갖게 된다.&lt;/li&gt;
&lt;li&gt;즉, 1밀리초 안에 최대 4096개의 ID 생성이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/&quot;&gt;https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/196</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-7%EC%9E%A5-%EB%B6%84%EC%82%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9C%A0%EC%9D%BC-ID-%EC%83%9D%EC%84%B1%EA%B8%B0-%EC%84%A4%EA%B3%84#entry196comment</comments>
      <pubDate>Thu, 25 Dec 2025 14:31:46 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 6장. 키:값 저장소 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-6%EC%9E%A5-%ED%82%A4%EA%B0%92-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%84%A4%EA%B3%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;키-값 저장소는 &lt;u&gt;&lt;b&gt;키-값 데이터베이스&lt;/b&gt;&lt;/u&gt;라고도 불리는 &lt;u&gt;&lt;b&gt;비 관계형 데이터베이스&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장되는 값은 고유 식별자를 키로 가지고 각 값은 키를 통해서만 접근이 가능하다.&lt;/li&gt;
&lt;li&gt;키는 일반 텍스트일 수도 있고 해시 값일 수도 있다.&lt;/li&gt;
&lt;li&gt;키는 짧을수록 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;키-값 저장소&lt;/span&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;b&gt;Amazon Dynamo&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Memcached&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Redis&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  이번 챕터에서 설계한 키-값 저장소 특성&lt;/h1&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;는 10KB 이하이다.&lt;/li&gt;
&lt;li&gt;큰 데이터를 저장할 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;높은 가용성&lt;/b&gt;을 제공해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애가 있더라도 빨리 응답해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;높은 규모 확장성&lt;/b&gt;을 제공해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트래픽 양에 따라 자동적으로 서버 증설 및 삭제가 이루어져야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 일관성 수준&lt;/b&gt;은 조정이 가능해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답 지연시간&lt;/b&gt;(latency)이 짧아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  단일 서버 키-값 저장소&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 대 서버&lt;/b&gt;만 사용하는 키-값 저장소를 설계하는 가장 직관적인 방법은 &lt;code&gt;키-값&lt;/code&gt; 쌍 전부를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메모리&lt;/b&gt;&lt;/span&gt;에 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;해시 테이블&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 해결하기 위한 개선책&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 압축&lt;/li&gt;
&lt;li&gt;자주 쓰이는 데이터만 메모리에 두고 나머지는 디스크에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;  분산 키-값 저장소&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 키-값 저장소는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;분산 해시 테이블&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템을 설계할 때는 &lt;code&gt;CAP 이론&lt;/code&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;  CAP 이론 (Consistency, Availability, Partition Tolerance theorem)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlCwIL/dJMcahpvceO/b1ufRUoEqH5hI5LBQegMwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlCwIL/dJMcahpvceO/b1ufRUoEqH5hI5LBQegMwk/img.png&quot; data-alt=&quot;https://javacatcher.tistory.com/226?category=1275585&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlCwIL/dJMcahpvceO/b1ufRUoEqH5hI5LBQegMwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlCwIL%2FdJMcahpvceO%2Fb1ufRUoEqH5hI5LBQegMwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;473&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://javacatcher.tistory.com/226?category=1275585&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 일관성, 가용성, 파티션 감내&lt;/b&gt; 라는 세 가지 요구사항을 동시에 만족하는 분산 시스템을 설계하는 것은 불가능하다.&lt;/p&gt;
&lt;/blockquote&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;CAP 이론 3가지 요소&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;데이터 일관성(Consistency)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;모든 클라이언트는 어떤 노드에 접속했는에 상관없이 언제나 같은 데이터를 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가용성 (Availability)&lt;/td&gt;
&lt;td&gt;일부 노드에 장애가 발생하더라도 항상 응답을 받을 수 있어야 함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파티션 감내 (Partition tolerance)&lt;/td&gt;
&lt;td&gt;네트워크에 파티션이 생기더라도 시스템은 계속 동작해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;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: #006dd7;&quot;&gt;&lt;b&gt;파티션 감내&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;code&gt;CP&lt;/code&gt; 또는 &lt;code&gt;AP&lt;/code&gt; 둘 중 하나를 선택한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;  &lt;b&gt;CAP 시스템 종류&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CP 시스템&lt;/td&gt;
&lt;td&gt;일관성과 파티션 감내를 지원 (가용성을 희생)&lt;/td&gt;
&lt;td&gt;은행 서비스 같은 일관성이 중요한 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AP 시스템&lt;/td&gt;
&lt;td&gt;가용성과 파티션 감내를 지원 (일관성 희생)&lt;/td&gt;
&lt;td&gt;SNS, 캐시 등에서 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CA 시스템&lt;/td&gt;
&lt;td&gt;일관성과 가용성을 지원&lt;/td&gt;
&lt;td&gt;❌&amp;nbsp;실제 CA 시스템은 존재하지 않음.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 장애는 피할 수 없는 일이므로 분산 시스템에서는 파티션 문제를 반드시 감내가 되도록 설계가 되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  예시 - 이상적 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템에서 데이터는 &lt;b&gt;보통 여러 노드에 복제&lt;/b&gt;되어 보관된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 대의 복제노드(replica) &lt;b&gt;n1, n2, n3&lt;/b&gt;에 데이터를 복제하여 보관하는 상황을 가정.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HxZHP/dJMcaaDTLGr/9SR61TCAnrsdWrLu43mZOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HxZHP/dJMcaaDTLGr/9SR61TCAnrsdWrLu43mZOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HxZHP/dJMcaaDTLGr/9SR61TCAnrsdWrLu43mZOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHxZHP%2FdJMcaaDTLGr%2F9SR61TCAnrsdWrLu43mZOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;286&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상적인 환경에서는 네트워크가 파티션이 되는 상황은 일어나지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;n1에 기록된 데이터는 자동으로 n2, n3에 복제&lt;/li&gt;
&lt;li&gt;따라서 일관성과 가용성도 만족한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  예시 - 실제 분산 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템은 파티션 문제를 피할 수 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파티션 문제가 발생하면 일관성과 가용성 사이에서 하나를 선택해야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4xHuP/dJMcahQyGHH/a4PBnurfTivu213lMmwQck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4xHuP/dJMcahQyGHH/a4PBnurfTivu213lMmwQck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4xHuP/dJMcahQyGHH/a4PBnurfTivu213lMmwQck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4xHuP%2FdJMcahQyGHH%2Fa4PBnurfTivu213lMmwQck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;280&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;n3&lt;/code&gt;에 장애가 발생하여 &lt;code&gt;n1&lt;/code&gt;, &lt;code&gt;n2&lt;/code&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;n1&lt;/b&gt; 또는 &lt;b&gt;n2&lt;/b&gt; 에 데이터를 기록했다면 이 데이터는 &lt;b&gt;n3&lt;/b&gt;에 전달되지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;n3&lt;/b&gt;에 데이터가 기록이 되었지만 &lt;b&gt;n1&lt;/b&gt;, &lt;b&gt;n2&lt;/b&gt;로 전달되지 않은 데이터가 있다면 &lt;b&gt;n1&lt;/b&gt;과 &lt;b&gt;n2&lt;/b&gt;는 계속해서 이를 모르고 오래된 정보를 가지고 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☑️ &lt;b&gt;일관성을 선택한 경우 - &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;code&gt;CP&lt;/code&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 3개의 서버사이에 생길 수 있는 데이터 불일치 문제를 피하기 위해 n1, n2에 대해 쓰기 연산을 중단해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 되면 가용성이 깨지게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;온라인 뱅킹 시스템&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계좌 최신 정보를 전달하지 않으면 큰 문제일 것이다.&lt;/li&gt;
&lt;li&gt;파티션때문에 일관성이 깨지게 되면 해당 서비스는 해결될 때까지 오류를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☑️ &lt;b&gt;가용성을 선택한 경우 - &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;code&gt;AP&lt;/code&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;낡은 데이터를 반환하는 문제가 있더라도 계속 읽기 연산을 허용해주어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;n3&lt;/b&gt;에 문제가 생긴 경우라도 &lt;b&gt;n1&lt;/b&gt;, &lt;b&gt;n2&lt;/b&gt;는 계속 쓰기 연산을 허용하고 파티션 문제가 해결된 이후 &lt;b&gt;n3&lt;/b&gt;에 새로운 데이터 정보를 전송해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 분산 키-값 저장소를 만들 때는 요구사항에 맞게 &lt;code&gt;CAP 정리&lt;/code&gt;를 적용해야 한다.&lt;/p&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;code&gt;키-값 저장소&lt;/code&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;데이터 다중화 (replication)&lt;/li&gt;
&lt;li&gt;일관성&lt;/li&gt;
&lt;li&gt;일관성 불일치 해소&lt;/li&gt;
&lt;li&gt;장애 처리&lt;/li&gt;
&lt;li&gt;시스템 아키텍처 다이어그램&lt;/li&gt;
&lt;li&gt;쓰기 경로&lt;/li&gt;
&lt;li&gt;읽기 경로&lt;/li&gt;
&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;대규모 애플리케이션 상황에서 전체 데이터를 하나의 서버에 넣는 것을 불가능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 작은 파티션들로 분할하여 여러 대 서버에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;데이터를 파티션 단위로 나눌 때 고려해야할 문제&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 여러 서버에 고르게 분산할 수 있나&lt;/li&gt;
&lt;li&gt;노드가 추가, 삭제될 때 데이터의 이동을 최소화할 수 있나&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;  5장에서의 &lt;code&gt;안정 해시&lt;/code&gt; 를 이용하면 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;안정 해시를 사용해 데이터를 파티션했을 때의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;/li&gt;
&lt;/ul&gt;
&lt;/li&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;/li&gt;
&lt;li&gt;고성능의 서버는 많은 가상 노드를 갖도록 설정가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;u&gt;&lt;b&gt;N개의 서버에 비동기&lt;/b&gt;&lt;/u&gt;적으로 &lt;u&gt;&lt;b&gt;다중화&lt;/b&gt;&lt;/u&gt;해주어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 해시 링에서 시계방향으로 순회하면서 만나는 첫 N개의 서버에 데이터 사본을 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RDZ3k/dJMcabQlcfW/6q6pxiTbS9f44jFyC6EiJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RDZ3k/dJMcabQlcfW/6q6pxiTbS9f44jFyC6EiJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RDZ3k/dJMcabQlcfW/6q6pxiTbS9f44jFyC6EiJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRDZ3k%2FdJMcabQlcfW%2F6q6pxiTbS9f44jFyC6EiJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;365&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;가상 노드를 사용한다면 선택한 N개의 노드가 대응될 실제 물리 서버의 개수가 N보다 작아질 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 문제를 피하기 위해서는 노드 선택 시 같은 물리 서버를 중복 선택하지 않도록 해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;무슨 의미이지?&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;code&gt;s0, s1, s2&lt;/code&gt; 가 모두 서버 A 한 대에 있는 가상 노드들일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;N = 3&lt;/code&gt; 일 때 key 0의 다중화된 데이터들이 물리적으로 &lt;b&gt;같은 서버인 A&lt;/b&gt;에 모두 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문제&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 장애&lt;/td&gt;
&lt;td&gt;서버 하나에 장애가 발생시 key의 복제본이 함께 손실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;고가용성 불가&lt;/td&gt;
&lt;td&gt;정전, 네트워크 이슈, 자연 재해등의 문제를 함께 겪어 대응이 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복제 의미가 없어짐&lt;/td&gt;
&lt;td&gt;논리적으로는 3개의 노드에 저장, 물리적으로는 1개의 서버에 몰려 있어 복제 의미가 없어짐.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;harr;️ 데이터 일관성&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;  &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;정족수 합의 (Quorum Consensus)&lt;/b&gt;&lt;/span&gt; 프로토콜을 사용하면 읽기 / 쓰기 연산에 모두 일관성을 보장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정족수 합의&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;b&gt;회의나 의결을 진행하기 위해 필요한 최소한의 참석 인원 수&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 시스템에서는 어떤 연산을 성공으로 간주하기 위해 필요한 최소한의 응답 수&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;정의&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;td&gt;사본 개수&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;쓰기 연산에 대한 정족수&lt;/td&gt;
&lt;td&gt;쓰기 연산이 성공으로 간주되기 위해서는 적어도 W개의 서버로부터 쓰기 연산이 성공했다는 응답을 받아야 함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;읽기 연산에 대한 정족수&lt;/td&gt;
&lt;td&gt;읽기 연산이 성공으로 간주되기 위해서는 적어도 R개의 서버로부터 읽기 연산이 성공했다는 응답을 받아야 함.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;예시: N - 3&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l3mwE/dJMcachptuy/Frh2IBkhBqROdmWmfbI7Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l3mwE/dJMcachptuy/Frh2IBkhBqROdmWmfbI7Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l3mwE/dJMcachptuy/Frh2IBkhBqROdmWmfbI7Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl3mwE%2FdJMcachptuy%2FFrh2IBkhBqROdmWmfbI7Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;498&quot; height=&quot;454&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;code&gt;W=1&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 한 대 서버에만 기록된다는 뜻이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 &lt;b&gt;서버0&lt;/b&gt;, &lt;b&gt;서버1&lt;/b&gt;, &lt;b&gt;서버2&lt;/b&gt;에 다중화가 된 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;W=1&lt;/code&gt; 의 의미는 &lt;b&gt;쓰기 연산이 성공했다고 판단&lt;/b&gt;하기 위해서 &lt;b&gt;중재자(coordinator)&lt;/b&gt; 는 &lt;b&gt;최소 한 대 서버&lt;/b&gt;로부터 쓰기 성공 응답을 받아야 한다는 의미.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버1로 부터 성공 응답을 받았다면 서버0, 서버2의 응답을 기다릴 필요가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;b&gt;프락시(proxy)&lt;/b&gt; 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;code&gt;W=1&lt;/code&gt; 또는 &lt;code&gt;R=1&lt;/code&gt;&lt;/p&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;rarr; &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;W또는 R의 값이 1보다 큰 경우에는 데이터 일관성의 수준을 향상되지만 중재자의 응답 속도는 가장 느린 서버로부터 오는 응답을 기다려야 하기 때문에 느려질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;code&gt;W + R &amp;gt; N&lt;/code&gt;&lt;/p&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: #006dd7;&quot;&gt;&lt;b&gt;강한 일관성&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;/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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;R=1&lt;/code&gt;, &lt;code&gt;W=N&lt;/code&gt;: 빠른 읽기 연산에 최적화된 시스템&lt;/li&gt;
&lt;li&gt;&lt;code&gt;W=1&lt;/code&gt;, &lt;code&gt;R=N&lt;/code&gt;: 빠른 쓰기 연산에 최적화된 시스템&lt;/li&gt;
&lt;li&gt;&lt;code&gt;W+R &amp;gt; N&lt;/code&gt;: 강한 일관성이 보장&lt;/li&gt;
&lt;li&gt;&lt;code&gt;W+R &amp;lt; N&lt;/code&gt;: 강한 일관성이 보장되지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☑️ 일관성 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관성 모델은 데이터 일관성의 수준을 결정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;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;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;강한 일관성&lt;/td&gt;
&lt;td&gt;모든 읽기 연산은 항상 최신 결과를 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;약한 일관성&lt;/td&gt;
&lt;td&gt;읽기 연산은 최신 결과를 반환하지 않을 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결과적 일관성&lt;/td&gt;
&lt;td&gt;약한 일관성의 한 형태. 결국에는 모든 사본이 동기화된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강한 일관성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다이나모 또는 카산드라 같은 저장소는 결과적 일관성 모델을 선택하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적 일관성 모델을 따르면 쓰기 연산이 병렬적으로 발생하면 일관성이 깨질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 &lt;b&gt;클라이언트&lt;/b&gt;가 해결해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  비 일관성 해소 기법: 데이터 버저닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 측에서 &lt;b&gt;데이터의 버전 정보를 활용해 일관성이 깨진 데이터를 읽지 않도록 하는 기법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 다중화하게 되면 가용성은 높아지고 사본간 일관성은 깨질 가능성이 높아진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;버저닝(versioning)&lt;/code&gt;과 &lt;code&gt;벡터 시계(vector clock)&lt;/code&gt;는 그 문제를 해소하기 위해 등장한 기술이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;각 버전의 데이터는 변경이 불가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;데이터 일관성이 깨지는 예시&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7yVpg/dJMcagREeJw/MQ27K2v7003VBHawICkPb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7yVpg/dJMcagREeJw/MQ27K2v7003VBHawICkPb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7yVpg/dJMcagREeJw/MQ27K2v7003VBHawICkPb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7yVpg%2FdJMcagREeJw%2FMQ27K2v7003VBHawICkPb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;298&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터의 사본이 노드1, 노드2에 저장되어 있는 상황&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H4ZzM/dJMcagREeJK/YH8E8XDBPjrG2IJAm33qEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H4ZzM/dJMcagREeJK/YH8E8XDBPjrG2IJAm33qEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H4ZzM/dJMcagREeJK/YH8E8XDBPjrG2IJAm33qEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH4ZzM%2FdJMcagREeJK%2FYH8E8XDBPjrG2IJAm33qEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;261&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버1&lt;/b&gt;은 값을 &lt;b&gt;&lt;i&gt;johnSanFrancisco&lt;/i&gt;&lt;/b&gt;로 변경하고 &lt;b&gt;서버2&lt;/b&gt;는&lt;i&gt;&lt;b&gt; johnNewYork&lt;/b&gt;&lt;/i&gt;으로 변경.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 연산이 동시에 이루어진다고 한다면 충돌하는 두개의 값을 가지게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 시계는 이 문제를 해결하는데 사용되는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  벡터 시계 (vector clock)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;[서버, 버전]&lt;/code&gt;의 순서쌍을 데이터에 매단 것.&lt;/li&gt;
&lt;li&gt;데이터를 서버에 기록할 때 해당 서버의 버전을 증가&lt;/li&gt;
&lt;li&gt;이를 통해 어떤 버전이 선행 버전인지, 후행 버전인지 다른 버전과 충돌이 있는지 판별한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;code&gt;D([S1, v1], [S2, v2], &amp;hellip;., [Sn, vn])&lt;/code&gt; 같이 표현한다고 가정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;D&lt;/code&gt;: 데이터&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vi&lt;/code&gt;: 버전 카운터 값&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Si&lt;/code&gt;: 서버 번호&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCv37C/dJMcacBIQ6A/G8AzNWzIyfyPtfnwq9uTi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCv37C/dJMcacBIQ6A/G8AzNWzIyfyPtfnwq9uTi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCv37C/dJMcacBIQ6A/G8AzNWzIyfyPtfnwq9uTi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCv37C%2FdJMcacBIQ6A%2FG8AzNWzIyfyPtfnwq9uTi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;498&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 클라이언트가 데이터 &lt;code&gt;D1&lt;/code&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;code&gt;Sx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;벡터 시계: &lt;code&gt;D1([Sx, 1])&lt;/code&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;2️⃣ 다른 클라이언트가 D1을 읽고 &lt;code&gt;D2&lt;/code&gt;로 업데이트 후 기록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;D2는 D1에 대한 변경이므로 D1을 덮어쓴다&lt;/li&gt;
&lt;li&gt;쓰기 연산을 처리해주는 서버: &lt;code&gt;Sx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;벡터 시계: &lt;code&gt;D2([Sx, 2])&lt;/code&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;3️⃣ 다른 클라이언트가 D2를 읽어 &lt;code&gt;D3&lt;/code&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;code&gt;Sy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;벡터 시계: &lt;code&gt;D3([Sx, 2], [Sy, 1])&lt;/code&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;4️⃣ 또 다른 클라이언트가 D2를 읽어 &lt;code&gt;D4&lt;/code&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;code&gt;Sz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;벡터 시계: &lt;code&gt;D4([Sx, 2], [Sz, 1])&lt;/code&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;5️⃣ 어떤 클라이언트가 D3와 D4를 읽으면 데이터간 충돌이 있다는 것을 알게 된다.&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;code&gt;Sx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;벡터 시계: &lt;code&gt;D5([Sx, 2], [Sy, 1], [Sz, 1])&lt;/code&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;  벡터 시계를 사용하면 어떤 버전 X가 버전 Y의 이전 버전인지 쉽게 판단할 수 있다.&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;버전 Y에 포함된 모든 구성요소의 값이 X에 포함된 모든 구성요소 값보다 같거나 큰지만 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 벡터 시계 단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 충돌 감지 및 해소 로직이 클라이언트에게 들어가야해 클라이언트 구현이 복잡해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;code&gt;[서버:버전]&lt;/code&gt; 의 순서쌍 개수가 빠르게 늘어나게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임계치를 설정한 후 임계치 이상으로 길어지면 오래된 순서쌍을 제거&lt;/li&gt;
&lt;li&gt;이럴 경우 버전 간 선후 관계가 정확하지 않을 수 있어서 효율성이 낮아질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;다이나모 DB에 관계된 문헌에 따르면 아마존은 실제 서비스에서 그런 문제가 벌어지는 것을 발견한 적이 없다고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  장애 처리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애는 대규모 시스템에서 아주 흔하게 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 장애를 어떻게 처리하느냐는 굉장히 중요한 문제이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✔️ 장애 감지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템에서는 한 대 서버가 &amp;ldquo;서버 A가 지금 죽었습니다&amp;rdquo;라고 판단한다고 서버 A를 장애처리 하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 두 대 이상의 서버가 똑같이 서버 A의 장애를 보고해야 실제로 장애가 발생했다고 간주&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 많을 때는 비효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  분산형 장애 감지 솔루션 (decentralized failure detection)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;가십 프로토콜(gossip protocol)&lt;/code&gt; 같은 분산형 장애 감지 솔루션을 채택하는 것이 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;가십 프로토콜 동작 원리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&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;각 멤버 ID와 그 박동 카운터(heartbeat counter)쌍의 목록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;&lt;/li&gt;
&lt;li&gt;박동 카운터 목록을 받은 노드는 &lt;b&gt;멤버십 목록을 최신 값으로 갱신&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;어떤 멤버의 박동 카운터 값이 지정된 시간 동안 갱신되지 않는다면 해당 멤버는 장애 상태인 것으로 간주한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;가십 프로토콜로 장애를 감지한 시스템은 가용성을 보장하기 위해서 조치를 해야 한다.&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;b&gt;느슨한 정족수 방식&lt;/b&gt; - 장애 상태 서버를 제외하고 쓰기 연산을 할 W개의 서버와 읽기 연산을 수행할 R개의 서버를 해시 링에서 선택하여 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애 상태인 서버로 가는 요청은 다른 서버가 잠시 맡아서 처리한 후 그동안 발생한 변경사항은 해당 서버가 복구되었을 때 일괄적으로 반영하여 데이터 일관성을 보존한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 위해 임시로 쓰기 연산을 처리한 서버에는 그에 대한 &lt;b&gt;단서(hint)&lt;/b&gt;를 남겨둔다.&lt;/li&gt;
&lt;li&gt;이런 장애 처리 방안을 &lt;b&gt;단서 후 임시 위탁(hinted handoff)&lt;/b&gt; 기법이라 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  영구 장애 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일시적 장애와 달리 영구적인 서버 장애를 처리하기 위해서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;반-엔트로피(anti-entropy)&lt;/b&gt;&lt;/span&gt; 프로토콜을 구현해 사본들을 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사본들을 비교하여 최신 버전으로 갱신(동기화)&lt;/li&gt;
&lt;li&gt;사본 간의 일관성 상태를 탐지하고 전송 데이터의 양을 줄이기 위해 머클(Merkle) 트리를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;각 노드에 그 자식 노드들에 보관된 값의 해시 또는 자식 노드들의 레이블로부터 계산된 해시 값을 레이블로 붙여두는 트리&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 트리를 사용하면 대규모 자료구조의 내용을 효과적이면서도 보안상 안전한 방법으로 검증할 수 있다.&lt;/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;리프 노드(Leaf Node)&lt;/b&gt;: 실제 데이터 블록 (예: 데이터 청크, 파일 조각)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부모 노드(Parent Node)&lt;/b&gt;: 자식 노드들의 해시를 결합하여 생성한 새로운 해시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;루트 해시(Root Hash)&lt;/b&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;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 데이터 블록에 해시 함수를 적용 &amp;rarr; 리프 노드 해시 생성&lt;/li&gt;
&lt;li&gt;인접한 두 리프 해시를 합친 후 다시 해시 함수 적용 &amp;rarr; 부모 노드 해시 생성&lt;/li&gt;
&lt;li&gt;이 과정을 반복하여 최종 루트 해시에 도달&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  예시 - 상황 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3개의 복제 서버가 있다고 가정&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;서버 A (Primary)&lt;/li&gt;
&lt;li&gt;서버 B (Replica 1)&lt;/li&gt;
&lt;li&gt;서버 C (Replica 2) &amp;larr; 영구 장애 복구&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; 4개의 버킷&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;Bucket&lt;/b&gt; 1: &quot;User Data A&quot; &amp;rarr; &lt;code&gt;Hash: 2a4f&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bucket&lt;/b&gt; 2: &quot;User Data B&quot; &amp;rarr; &lt;code&gt;Hash: 5c8e&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bucket&lt;/b&gt; 3: &quot;User Data C&quot; &amp;rarr; &lt;code&gt;Hash: 7d2b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bucket&lt;/b&gt; 4: &quot;User Data D&quot; &amp;rarr; &lt;code&gt;Hash: 9f1a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  각 서버의 머클 트리 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 A(정상)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Level 2:          Root: 4x2a
                   /       \
Level 1:      Hash1: 8c9d   Hash2: 3e4f
              /    \        /    \
Level 0:   2a4f   5c8e   7d2b   9f1a
            &amp;darr;      &amp;darr;       &amp;darr;      &amp;darr;
         Bucket  Bucket  Bucket  Bucket
           1      2       3       4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 C (장애 후 복구, 데이터 손상)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Level 2:          Root: 7x3c  &amp;larr; 다름!
                   /       \
Level 1:      Hash1: 8c9d   Hash2: 2a1b  &amp;larr; 다름!
              /    \        /    \
Level 0:   2a4f   5c8e   9g3c   9f1a  &amp;larr; Block 3 손상
            &amp;darr;      &amp;darr;      &amp;darr;      &amp;darr;
          Bucket  Bucket  Bucket  Bucket
            1      2      3      4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  반-엔트로피 프로토콜 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;루트 레벨 비교&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버 A: &quot;루트 해시: 4x2a&quot;&lt;/li&gt;
&lt;li&gt;서버 C: &quot;루트 해시: 7x3c&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;: 루트가 다르다 &amp;rarr; 데이터 불일치 감지!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;문제점 (머클 트리 없는 경우)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 4개 블록의 원본 데이터를 네트워크로 전송해야 함&lt;/li&gt;
&lt;li&gt;불필요한 데이터 전송 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;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;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버 A의 &lt;code&gt;Hash1: 8c9d&lt;/code&gt; vs 서버 C의 &lt;code&gt;Hash1: 8c9d&lt;/code&gt; &amp;rarr; &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;Bucket 1, 2는 정상&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버 A의 &lt;code&gt;Hash2: 3e4f&lt;/code&gt; vs 서버 C의 &lt;code&gt;Hash2: 2a1b&lt;/code&gt; &amp;rarr; &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;Bucket&lt;/b&gt; 3, 4 중 하나 이상에 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;마지막: Level 0, 손상된 부분만 비교&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Bucket&lt;/b&gt; 3: &lt;code&gt;7d2b&lt;/code&gt; (A) vs &lt;code&gt;9g3c&lt;/code&gt; (C) &amp;rarr; &lt;b&gt;불일치&lt;/b&gt; ❌ &amp;rarr; &lt;b&gt;Bucket 3만 전송&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bucket&lt;/b&gt; 4: &lt;code&gt;9f1a&lt;/code&gt; (A) vs &lt;code&gt;9f1a&lt;/code&gt; (C) &amp;rarr; &lt;b&gt;일치!&lt;/b&gt; ✅&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☑️ 최소한의 데이터 전송&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초기 상태&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;전체 데이터: 4개 블록&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;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;✅ Bucket 1&lt;/b&gt;: 전송 불필요 (해시 일치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ Bucket 2&lt;/b&gt;: 전송 불필요 (해시 일치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;❌ Bucket 3&lt;/b&gt;: 전송 필요 (해시 불일치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ Bucket 4&lt;/b&gt;: 전송 불필요 (해시 일치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과: 총 4개 중 1개만 전송 (75% 대역폭 절감)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 데이터를 갖는 버킷을 찾아 그 버킷들만 동기화하면 된다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  데이터 센터 장애 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정전, 네트워크 장애, 자연재해 등 다양한 이유로 데이터 센터 장애가 발생할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 여러 데이터 센터에 다중화되어야 한다.&lt;/li&gt;
&lt;li&gt;하나의 센터가 망가져도 다른 데이터 센터에 보관된 데이터를 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 시스템 아키텍처 다이어그램&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GCM1c/dJMcagqzTki/V2ve6himDUqdADkKQtMRvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GCM1c/dJMcagqzTki/V2ve6himDUqdADkKQtMRvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GCM1c/dJMcagqzTki/V2ve6himDUqdADkKQtMRvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGCM1c%2FdJMcagqzTki%2FV2ve6himDUqdADkKQtMRvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;317&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  아키텍처의 주된 기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 키-값 저장소가 제공하는 &lt;b&gt;API(get, put)&lt;/b&gt;를 통해 통신&lt;/li&gt;
&lt;li&gt;중재자는 클라이언트에게 키-값 저장소에 대한 프록시 역할 수행&lt;/li&gt;
&lt;li&gt;노드는 &lt;b&gt;안정 해시의 해시 링&lt;/b&gt; 위에 분포&lt;/li&gt;
&lt;li&gt;노드는 자동으로 추가 / 삭제할 수 있도록 시스템은 완전히 분산&lt;/li&gt;
&lt;li&gt;데이터는 여러 노드에 다중화된다.&lt;/li&gt;
&lt;li&gt;모든 노드가 같은 책임을 가져 &lt;code&gt;SPOF(단일 장애점)&lt;/code&gt;가 존재하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✍️ 쓰기 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쓰기 요청&lt;/b&gt;이 특정 노드에 전달되었을 때 발생하는 일&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJSZjP/dJMcabbJSHv/9KkKFH8eu7CLaWVjTjNMH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJSZjP/dJMcabbJSHv/9KkKFH8eu7CLaWVjTjNMH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJSZjP/dJMcabbJSHv/9KkKFH8eu7CLaWVjTjNMH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJSZjP%2FdJMcabbJSHv%2F9KkKFH8eu7CLaWVjTjNMH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;340&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;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;code&gt;SSTable&lt;/code&gt;에 기록&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt; SSTable&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;Sorted-String Table&lt;/li&gt;
&lt;li&gt;(키, 값) 순서쌍을 정렬된 리스트 형태로 관리하는 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  읽기 요청&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd0C9K/dJMcaaKFprz/HEolIzaEtNp89K8kfVqmCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd0C9K/dJMcaaKFprz/HEolIzaEtNp89K8kfVqmCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd0C9K/dJMcaaKFprz/HEolIzaEtNp89K8kfVqmCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd0C9K%2FdJMcaaKFprz%2FHEolIzaEtNp89K8kfVqmCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;293&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;594&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;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;/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;code&gt;SSTable&lt;/code&gt;에 키가 보관되어 있는지 알아낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SSTable&lt;/code&gt;에서 데이터를 가져온다.&lt;/li&gt;
&lt;li&gt;데이터를 클라이언트에게 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 메모리에 없는 경우에는 디스크에서 데이터를 가져와야 하는데 어느 SSTable에 찾는 키가 있는지 알아낼 수 있는 효율적인 방법이 필요하다.&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;존재하지 않는 경우에는 100% 정확하지만 존재할 경우 오탐이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ibm.com/kr-ko/think/topics/cap-theorem&quot;&gt;https://www.ibm.com/kr-ko/think/topics/cap-theorem&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/195</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-6%EC%9E%A5-%ED%82%A4%EA%B0%92-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%84%A4%EA%B3%84#entry195comment</comments>
      <pubDate>Thu, 25 Dec 2025 14:19:47 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 5장. 안정 해시 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-5%EC%9E%A5-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%84%A4%EA%B3%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;수평적 규모 확장성을 달성하기 위해서는 요청, 데이터를 서버에 균등하게 나누는 것이 중요 ☑️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정 해시는 이런 목표를 달성하기 위해 보편적으로 사용하는 기술이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 해시 키 재배치 문제 (rehash)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;N&lt;/code&gt;개의 캐시 서버가 있다고 했을 때 이 서버에게 균등하게 부하를 나누는 보편적인 방법은 해시 함수를 이용하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;serverIndex = hash(key) % N // N은 서버의 개수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  예시 - 4대의 서버&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 155px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;키&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;해시&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;해시 % 4 (서버 인덱스)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;18358617&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key1&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;26143584&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key2&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;18131126&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key3&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;35863496&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key4&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;34085809&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key5&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;27581703&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key6&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;38164978&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;key7&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;22530351&amp;dagger;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해시 값과 서버 인덱스를 계산한 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 값이 서버에 어떻게 분산되는지 그림으로 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dL3oC0/dJMcagREelY/rqaFVrHrbxZOk4LDKArxX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dL3oC0/dJMcagREelY/rqaFVrHrbxZOk4LDKArxX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dL3oC0/dJMcagREelY/rqaFVrHrbxZOk4LDKArxX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdL3oC0%2FdJMcagREelY%2FrqaFVrHrbxZOk4LDKArxX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;199&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 서버 풀의 크기가 고정되어 있고 데이터 분포가 균등할 때는 잘 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &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;ex) 서버 하나에 장애가 발생해 동작이 중단하면 서버 풀의 크기는 3으로 변한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키에 대한 해시 값은 변하지 않지만 나머지 연산을 적용해 계산한 서버 인덱스 값은 달라진다.&lt;/li&gt;
&lt;li&gt;올바르지 않은 서버로 접근하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx7K03/dJMcadtOlcM/fIksq020BCMzNOtykqIY2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx7K03/dJMcadtOlcM/fIksq020BCMzNOtykqIY2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx7K03/dJMcadtOlcM/fIksq020BCMzNOtykqIY2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx7K03%2FdJMcadtOlcM%2FfIksq020BCMzNOtykqIY2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;196&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP8vhW/dJMcabJAvvV/RcSa0xclOnbnND8GhL7Rq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP8vhW/dJMcabJAvvV/RcSa0xclOnbnND8GhL7Rq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP8vhW/dJMcabJAvvV/RcSa0xclOnbnND8GhL7Rq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP8vhW%2FdJMcabJAvvV%2FRcSa0xclOnbnND8GhL7Rq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;176&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 장애가 발생한 서버에 보관된 키뿐 아니라 대부분의 키가 재분배되었고 캐시 클라이언트가 데이터가 없는 서버에 접속하게 되어 대규모 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;캐시미스&lt;/b&gt;&lt;/span&gt;가 발생하게 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⭐ 안정 해시&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;code&gt;k / n&lt;/code&gt; 개의 키만 재배치하는 기술&lt;/li&gt;
&lt;li&gt;&lt;b&gt;k: 키의 개수&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;n: 슬롯의 개수&lt;/b&gt;&lt;br /&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  해시 공간과 해시링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;해시 공간&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qr7eE/dJMcagc3yw2/K3cdeXhMg2RT5J1aPlUXe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qr7eE/dJMcagc3yw2/K3cdeXhMg2RT5J1aPlUXe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qr7eE/dJMcagc3yw2/K3cdeXhMg2RT5J1aPlUXe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqr7eE%2FdJMcagc3yw2%2FK3cdeXhMg2RT5J1aPlUXe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;829&quot; height=&quot;108&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수의 출력 값 범위가 &lt;i&gt;&lt;b&gt;x0, x1, x2, x3, &amp;hellip;. xn&lt;/b&gt;&lt;/i&gt; 과 같다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수로&lt;b&gt; SHA-1&lt;/b&gt; 를 사용한다고 하면 해시 공간의 범위는 0부터 까지로 알려져 있고 이 해시 공간을 그림으로 표현한 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭕ &lt;b&gt;해시 링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 링은 해시 공간의 양쪽을 구부려 잡으면 만들어진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QoOwX/dJMcadgg97O/b8pPde3aI9InKTrnWLcnC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QoOwX/dJMcadgg97O/b8pPde3aI9InKTrnWLcnC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QoOwX/dJMcadgg97O/b8pPde3aI9InKTrnWLcnC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQoOwX%2FdJMcadgg97O%2Fb8pPde3aI9InKTrnWLcnC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;363&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  해시 서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수 &lt;code&gt;f&lt;/code&gt; 를 사용하면 &lt;b&gt;서버 IP&lt;/b&gt;나 &lt;b&gt;이름&lt;/b&gt;을 해시 링의 어떤 위치에 대응시킬 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bArFyn/dJMcaaKFpaB/czoDFCXMLOmH68UU8OX6ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bArFyn/dJMcaaKFpaB/czoDFCXMLOmH68UU8OX6ik/img.png&quot; data-alt=&quot;https://karmesin924.tistory.com/88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bArFyn/dJMcaaKFpaB/czoDFCXMLOmH68UU8OX6ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbArFyn%2FdJMcaaKFpaB%2FczoDFCXMLOmH68UU8OX6ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;373&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://karmesin924.tistory.com/88&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  해시 키&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sZcpY/dJMcad1DFm0/47yaAaqW6wbFiypsRKWxRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sZcpY/dJMcad1DFm0/47yaAaqW6wbFiypsRKWxRK/img.png&quot; data-alt=&quot;https://karmesin924.tistory.com/88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sZcpY/dJMcad1DFm0/47yaAaqW6wbFiypsRKWxRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsZcpY%2FdJMcad1DFm0%2F47yaAaqW6wbFiypsRKWxRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;323&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://karmesin924.tistory.com/88&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용된 해시 함수는 &lt;code&gt;해시키 재배치 문제&lt;/code&gt;에 언급된 함수와 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;나머지 연산(%)&lt;/u&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;캐시할 키 (key0, key1, &amp;hellip;)&lt;/b&gt; 또한 해시 링의 어느 지점에 위치할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  서버 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키가 저장되는 서버는 해당 키의 위치로부터 &lt;b&gt;시계 방향&lt;/b&gt;으로 링을 탐색하다 &lt;b&gt;만나는 첫 번째 서버&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wXJet/dJMcac2Kdmb/jxukfOkdH1j7OLKwBzeijk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wXJet/dJMcac2Kdmb/jxukfOkdH1j7OLKwBzeijk/img.png&quot; data-alt=&quot;https://karmesin924.tistory.com/88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wXJet/dJMcac2Kdmb/jxukfOkdH1j7OLKwBzeijk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwXJet%2FdJMcac2Kdmb%2FjxukfOkdH1j7OLKwBzeijk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;423&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://karmesin924.tistory.com/88&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;key0은 서버 0, key1은 서버 1, key2은 서버 2, key3은 서버 3에 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  서버 추가 / 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 추가하더라도 &lt;b&gt;키 가운데 일부만 재배치&lt;/b&gt;를 하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PJSZp/dJMcafFdpcr/yVawOQYX7B9I9jqGM67kB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PJSZp/dJMcafFdpcr/yVawOQYX7B9I9jqGM67kB0/img.png&quot; data-alt=&quot;https://karmesin924.tistory.com/88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PJSZp/dJMcafFdpcr/yVawOQYX7B9I9jqGM67kB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPJSZp%2FdJMcafFdpcr%2FyVawOQYX7B9I9jqGM67kB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;439&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://karmesin924.tistory.com/88&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;서버 4가 추가된 이후에 &lt;b&gt;key0&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;key0 위치에서 시계방향으로 돌때 처음으로 만나는 서버가 서버 4이기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 서버가 제거될 때 역시 일부만 재배치된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4ejzO/dJMcadghaby/Q4Npx4piLAukVUYfM9F0H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4ejzO/dJMcadghaby/Q4Npx4piLAukVUYfM9F0H0/img.png&quot; data-alt=&quot;https://karmesin924.tistory.com/88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4ejzO/dJMcadghaby/Q4Npx4piLAukVUYfM9F0H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4ejzO%2FdJMcadghaby%2FQ4Npx4piLAukVUYfM9F0H0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;430&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://karmesin924.tistory.com/88&lt;/figcaption&gt;
&lt;/figure&gt;
&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;안정 해시 알고리즘의 절차는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;균등 분포 해시 함수를 사용해 서버와 키를 해시 링에 배치&lt;/li&gt;
&lt;li&gt;키의 위치에서 링을 시계방향으로 탐색하다 만나는 최초의 서버가 키가 저장될 서버&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법에는 2가지 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  파티션의 크기를 균등하게 유지하는 게 불가능&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 서버는 작은 해시 공간을 할당 받고 다른 서버는 큰 해시 공간을 할당 받는 상황이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  키의 균등 분포를 달성하기 어렵다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 서버는 아무 데이터를 가지고 있지 않을 수도 있는데 다른 서버에서는 대부분의 키가 저장되는 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해서 제안된 기법이 &lt;b&gt;가상 노드&lt;/b&gt; 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  가상 노드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 노드는 실제 노드 또는 서버를 가리키는 노드로서, 하나의 서버는 링 위에 여러 개의 가상 노드를 가질 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDSXG/dJMcahXll1K/glk7sqLPnVMCkaYYoZYQ9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDSXG/dJMcahXll1K/glk7sqLPnVMCkaYYoZYQ9K/img.png&quot; data-alt=&quot;https://karmesin924.tistory.com/88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDSXG/dJMcahXll1K/glk7sqLPnVMCkaYYoZYQ9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDSXG%2FdJMcahXll1K%2Fglk7sqLPnVMCkaYYoZYQ9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;257&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://karmesin924.tistory.com/88&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 0&lt;/b&gt;와 &lt;b&gt;서버 1&lt;/b&gt;은 3개의 가상 노드를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 0&lt;/b&gt;는 S0 를 하나 쓰는 대신 &lt;code&gt;S0_0&lt;/code&gt;, &lt;code&gt;S0_1&lt;/code&gt;, &lt;code&gt;S0_2&lt;/code&gt; 을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 1&lt;/b&gt;도 동일하게 3개의 가상 노드를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 각 서버는 여러 개의 파티션을 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;가상 노드의 개수를 늘리면 키의 분포는 점점 균등해진다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표준 편차가 작아져 데이터가 고르게 분포되기 때문&lt;/li&gt;
&lt;li&gt;⚠️ 하지만 가상 노드 데이터를 저장할 공간은 더 많이 필요하게 된다 (트레이드 오프)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  배운 점 정리 및 궁금한 점 정리&lt;/h2&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;/li&gt;
&lt;li&gt;키 위치에서 시계 방향으로 처음 만나는 서버에 할당한다.&lt;/li&gt;
&lt;li&gt;서버가 추가, 삭제돼도 &lt;b&gt;평균적으로 k/n개의 키만 재배치&lt;/b&gt;되므로 캐시 미스가 적어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/194</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-5%EC%9E%A5-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%84%A4%EA%B3%84#entry194comment</comments>
      <pubDate>Thu, 25 Dec 2025 13:33:13 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 4장. 처리율 제한 장치의 설계</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-4%EC%9E%A5-%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98%EC%9D%98-%EC%84%A4%EA%B3%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 시스템에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;처리율 제한 장치(rate limiter)&lt;/b&gt;&lt;/span&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;ex) 특정 기간 내에 전송되는 클라이언트 HTTP 요청 횟수를 제한&lt;/li&gt;
&lt;li&gt;ex) API 요청 횟수가 정의된 임계치를 넘어서면 추가 호출은 처리가 중단(block)&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DoS(Denial of Service) 공격에 의한 자원 고갈(resource starvation)을 방지&lt;/li&gt;
&lt;li&gt;비용을 절감. 추가 요청에 대한 처리를 제한하면 서버를 많이 두지 않아도 됨&lt;/li&gt;
&lt;li&gt;서버 과부하를 막는다. 봇(bot)에서 오는 트래픽이나 사용자의 잘못된 이용패턴으로 유발된 트래픽을 걸러내는데 처리율 제한 장치를 활용 가능&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;&lt;code&gt;클라이언트&lt;/code&gt; 측에 둘 수도 &lt;code&gt;서버&lt;/code&gt; 측에 둘 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 클라이언트 측&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 클라이언트는 처리율 제한을 안정적으로 처리할 수 있는 장소가 되지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 요청은 쉽게 위변조가 가능&lt;/li&gt;
&lt;li&gt;모든 클라이언트의 구현을 통제하는 것이 어렵기 때문&lt;/li&gt;
&lt;/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;2️⃣ 서버 측&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sFIle/dJMcabJAvc2/mVBzZv6PIpsNWsRBvIfKYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sFIle/dJMcabJAvc2/mVBzZv6PIpsNWsRBvIfKYK/img.png&quot; data-alt=&quot;서버측에 제한 장치를 두는 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sFIle/dJMcabJAvc2/mVBzZv6PIpsNWsRBvIfKYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsFIle%2FdJMcabJAvc2%2FmVBzZv6PIpsNWsRBvIfKYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;154&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버측에 제한 장치를 두는 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 서버 측에 제한장치를 두는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 방법으로 처리율 제한 장치를 API 서버에 두는 대신 &lt;code&gt;처리율 제한 미들웨어&lt;/code&gt;를 만들어 해당 미들웨어에서 API 서버로 가는 요청을 통제할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 마이크로서비스인 경우 처리율 제한 장치는 보통 &lt;code&gt;API 게이트웨이(gateway)&lt;/code&gt; 라 불리는 컴포넌트에 구현&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리율 제한, SSL 종단(termination), 사용자 인증(authentication), IP 허용 목록(whitelist) 관리 등을 지원하는 완전 위탁관리형 서비스&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;처리율 제한 알고리즘은 여러가지 (각각 다른 장단점)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰 버킷(token bucket)&lt;/li&gt;
&lt;li&gt;누출 버킷(leaky bucket)&lt;/li&gt;
&lt;li&gt;고정 윈도 카운터(fixed window counter)&lt;/li&gt;
&lt;li&gt;이동 윈도 로그(sliding window log)&lt;/li&gt;
&lt;li&gt;이동 윈도 카운터(sliding window counter)&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;  토큰 버킷 (token bucket)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 버킷은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;지정된 용량&lt;/b&gt;&lt;/span&gt;을 가진 컨테이너로 &lt;b&gt;사전 설정된 양의 토큰이 주기적으로 채워진다&lt;/b&gt;.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;꽉 찬 상태라면 더 이상 토큰이 추가되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 요청이 처리될 때마다 하나의 토큰이 사용되고 요청이 도착하면 버킷에 충분한 토큰이 있는지 검사&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충분한 토큰이 있다면 버킷에서 토큰을 하나 꺼낸 후 요청을 시스템에 전달&lt;/li&gt;
&lt;li&gt;충분한 토큰이 없다면 해당 요청은 버려진다(dropped)&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;토큰 버킷 알고리즘의 2가지 파라미터&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. 버킷 크기&lt;/td&gt;
&lt;td&gt;버킷에 담을 수 있는 토큰의 최대 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 토큰 공급률 (refill rate)&lt;/td&gt;
&lt;td&gt;초당 몇 개의 토큰이 버킷에 공급되는지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  버킷은 몇 개나 사용해야 하나?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 엔드포인트마다 별도의 버킷을 두고 사용&lt;/li&gt;
&lt;li&gt;ex) &lt;b&gt;사용자마다 하루에 한 번만 포스팅 가능&lt;/b&gt;, &lt;b&gt;친구는 150명까지 추가&lt;/b&gt;, &lt;b&gt;좋아요 버튼은 5번만 가능&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자마다 3개의 버킷이 필요!&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;&lt;/p&gt;
&lt;table style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현이 쉽다&lt;/td&gt;
&lt;td&gt;버킷 크기, 토큰 공급률 값을 적절하게 튜닝하기 어렵다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 사용 측면에서 효율적이다&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;짧은 시간에 집중되는 트래픽 (burst of traffic) 처리 가능 (남은 토큰이 있다면)&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  누출 버킷 (leaky bucket)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 버킷과 비슷하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;요청 처리율이 고정&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;보통 FIFO 큐로 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 도착하면 큐가 가득 차 있는지 확인&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈자리가 있는 경우 큐에 요청 추가&lt;/li&gt;
&lt;li&gt;큐가 가득 차 있는 경우 새 요청은 버리게 됨.&lt;/li&gt;
&lt;li&gt;지정된 시간마다 큐에서 요청을 꺼내어 처리&lt;/li&gt;
&lt;/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; &lt;b&gt;버킷 알고리즘의 2가지 파라미터&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;버킷 크기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;큐 사이즈와 같은 크기. 처리될 항목들이 큐에 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;처리율(outflow rate)&lt;/td&gt;
&lt;td&gt;지정된 시간당 몇 개의 항목을 처리할지 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;장 단점&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 사용 측면에서 효율적이다(큐의 크기가 제한)&lt;/td&gt;
&lt;td&gt;짧은 시간에 트래픽이 많이 몰릴 경우 큐에 오래된 요청들이 쌓이고 제때 처리하지 못해 최신 요청들이 버려진다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;고정된 처리율을 가져 안정적인 출력(stable outflow rate)가 필요한 경우 적합&lt;/td&gt;
&lt;td&gt;두 개의 인자를 적절히 튜닝하기 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  고정 윈도 카운터 알고리즘 (fixed window counter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임라인을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;고정된 간격의 윈도(window)&lt;/b&gt;&lt;/span&gt;로 나누고 &lt;b&gt;&lt;u&gt;각 윈도마다 카운터&lt;/u&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;요청이 올 때마다 카운터 값은 1씩 증가&lt;/li&gt;
&lt;li&gt;카운터 값이 사전에 설정된 임계치에 도달시 새로운 요청은 새 윈도가 열릴 때까지 버려짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도 경계 부근에 순간적으로 많은 트래픽이 집중되면 윈도에 할당된 양보다 더 많은 요청이 처리될 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 분당 최대 5개의 요청만 허용하는 시스템&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2:00:00 ~ 2:01:00 &amp;rarr; 5개의 요청&lt;/li&gt;
&lt;li&gt;2:01:00 ~ 2:02:00 &amp;rarr; 5개의 요청&lt;/li&gt;
&lt;li&gt;2:00:30 ~ 2:01:30 &amp;rArr; 1분동안 살표본다면 10개의 요청&amp;hellip;!? &amp;rArr; 임계치의 2배&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;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 효율이 좋다&lt;/td&gt;
&lt;td&gt;윈도 경계 부근에서 일시적으로 많은 트래픽이 몰려드는 경우&amp;nbsp;&lt;b&gt;기대했던 시스템의 처리 한도(임계치)보다 많은 양의 요청을 처리하게 됨&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이해하기 쉽다&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;윈도가 닫히는 시점에 카운터를 초기화하는 방식은 특정한 트래픽 패턴을 처리하기에 적합하다.&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  이동 윈도 로깅 알고리즘 (sliding window log)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;고정 윈도 카운터 알고리즘&lt;/code&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;1️⃣ &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;code&gt;정렬 집합(sorted set)&lt;/code&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;2️⃣ &lt;b&gt;새 요청이 오면 만료된 타임스탬프 제거&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 윈도의 시작 시점보다 오래된 타임스탬프&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 새 요청 타임스탬프를 로그에 추가&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그의 크기가 허용치보다 같거나 작으면 요청을 시스템에 전달&lt;/li&gt;
&lt;li&gt;그렇지 않으면 처리를 거부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;장 단점&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;어느 순간의 윈도를 보더라도 허용되는 요청의 개수는 시스템 처리율 한도를 넘지 않음&lt;/td&gt;
&lt;td&gt;다량의 메모리를 사용함. 거부된 요청의 타임스탬프도 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  이동 윈도 카운터 알고리즘 (sliding window counter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;고정 윈도 카운터 알고리즘&lt;/code&gt;과 &lt;code&gt;이동 윈도 로깅 알고리즘&lt;/code&gt;을 결합한 형태&lt;/p&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;현재 1분간의 요청 수&lt;/code&gt; + &lt;code&gt;직전 1분간의 요청수 x 이동 윈도와 직전 1분이 겹치는 비율&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;장 단점&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이전 시간대의 평균 처리율에 따라 현재 윈도의 상태를 계산하므로 짧은 시간에 몰리는 트래픽에도 대응&lt;/td&gt;
&lt;td&gt;직전 시간대에 도착한 요청이 균등하게 분포되어 있다고 가정한 상태에서 추정치를 계산하기 때문에 다소 느슨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 효율이 좋음&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점(문제)는 생각보다 심각하지 않음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라우드페어(Cloudfare)가 실시했던 실험에 따르면 40억 개의 요청 가운데 시스템의 실제 상태와 맞지 않게 허용되거나 버려진 요청은 0.003%&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;얼마나 많은 요청이 접수되었는지를 추적할 수 있는 카운터를 추적할 대상별로 두고 해당 카운터 값이 한계를 넘어서면 도착한 요청을 거부한다!&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;IP 주소별로 추적?&lt;/li&gt;
&lt;li&gt;API 엔드포인트 / 서비스 단위?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  카운터는 어디에 보관..?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DB&lt;/code&gt; - 디스크 접근으로 인해 느리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리상으로 동작하는 캐시가 바람직 &amp;rarr; &lt;code&gt;Redis&lt;/code&gt;&lt;/p&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;INCR&lt;/code&gt;와 &lt;code&gt;EXPIRE&lt;/code&gt; 두 가지 명령어를 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;명령어&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INCR&lt;/td&gt;
&lt;td&gt;메모리에 저장된 카운터의 값을 1 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EXPIRE&lt;/td&gt;
&lt;td&gt;카운터에 타임아웃 값 설정. 설정된 시간이 지나면 자동 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw6yx7/dJMcabQlbN3/eEg6ZwDFM8AsUPccoOmaC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw6yx7/dJMcabQlbN3/eEg6ZwDFM8AsUPccoOmaC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw6yx7/dJMcabQlbN3/eEg6ZwDFM8AsUPccoOmaC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw6yx7%2FdJMcabQlbN3%2FeEg6ZwDFM8AsUPccoOmaC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;272&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;♂️ &lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 &lt;b&gt;처리율 제한 미들웨어&lt;/b&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;한도에 도달하지 않았다면 요청은 API 서버로 전달
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미들웨어는 카운터의 값을 증가 후 레디스에 다시 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;한도에 도달했다면 요청은 거부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;♂️ 상세 설계&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcklG2/dJMcafLYxmM/9NR8XDmGJzsEjMQHHJK2B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcklG2/dJMcafLYxmM/9NR8XDmGJzsEjMQHHJK2B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcklG2/dJMcafLYxmM/9NR8XDmGJzsEjMQHHJK2B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcklG2%2FdJMcafLYxmM%2F9NR8XDmGJzsEjMQHHJK2B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;231&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 개략적인 설계만 봐서는 아래와 같은 사항들은 알 수 없다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리율 제한 규칙은 어떻게 만들어지고 어디에 저장?!?&lt;/li&gt;
&lt;li&gt;처리가 제한된 요청들은 어떻게 처리?!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  처리율 제한 규칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Lyft&lt;/b&gt; 는 처리율 제한에 오픈 소스를 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;domain: messaging
descriptors:
  - key: message_type
    value: marketing
    descriptors:
      - key: to_number
        rate_limit:
          unit: day
          requests_per_unit: 5

 --------------
domain: auth
descriptors:
    - key: auth_type
      Value: login
      rate_limit:
          unit: minute
        requests_per_unit:5&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;마케팅 메시지의 최대치를 하루 5개로 제한&lt;/li&gt;
&lt;li&gt;클라이언트가 분당 5회 이상 로그인 할 수 없도록 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Lyft&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미국 차량 공유 서비스&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☑️ 처리율 한도 초과 트래픽 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 요청이 처리율 한도 제한에 걸리면 &lt;code&gt;HTTP 429(too many requests)&lt;/code&gt;응답을 클라이언트에게 보낸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한도 제한에 걸린 요청(메시지)를 나중에 처리하기 위해 큐에 보관할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;↗️ 처리율 제한 장치가 사용하는 HTTP 헤더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 자신이 보낸 요청이 처리율 제한에 걸린 것을 어떻게 감지!?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 요청이 처리율 제한에 걸리기까지 얼마나 많은 요청을 보낼 수 있는지 알 수 있나??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;HTTP 응답 헤더&lt;/code&gt;를 통해 해결&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리율 제한 장치는 HTTP 헤더를 클라이언트에게 보내줌.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;헤더&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X-Ratelimit-Remaining&lt;/td&gt;
&lt;td&gt;윈도 내에 남은 처리 가능 요청의 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X-Ratelimit-Limit&lt;/td&gt;
&lt;td&gt;매 윈도마다 클라이언트가 전송할 수 있는 요청의 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X-Ratelimit-Retry-After&lt;/td&gt;
&lt;td&gt;한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는지 알림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 사용자가 너무 많은 요청을 보내면 429 에러를 &lt;code&gt;X-Ratelimit-Retry-After&lt;/code&gt; 헤더와 함께 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 상세 설계 도면&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o8Iap/dJMcag5b2PX/3fHhr3PXBvgmXnObmguokk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o8Iap/dJMcag5b2PX/3fHhr3PXBvgmXnObmguokk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o8Iap/dJMcag5b2PX/3fHhr3PXBvgmXnObmguokk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo8Iap%2FdJMcag5b2PX%2F3fHhr3PXBvgmXnObmguokk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;405&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;처리율 제한 규칙&lt;/b&gt; 은 디스크에 보관&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 프로세스(workers)는 수시로 규칙을 디스크에서 읽어 캐시에 저장한다.&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;2️⃣ 클라이언트가 요청을 서버에 보내면 요청은 먼저 처리율 제한 미들웨어에 도달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 처리율 제한 미들웨어는 제한 규칙을 캐시에서 가져온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카운터 및 마지막 요청의 타임스탬프를 레디스 캐시에서 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ 가져온 값들을 토대로 미들웨어가 결정을 내린다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 처리율 제한에 걸리지 않았다면 API 서버로 보낸다.&lt;/li&gt;
&lt;li&gt;제한에 걸렸다면 429 에러를 클라이언트에게 보낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청은 버릴수도 메시지 큐에 보관할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt; 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트 요청이 오면 처리율 제한 미들웨어가 규칙과 Redis 상태를 보고 허용/차단/지연(큐)에 넣는 결정을 한다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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;여러 대의 서버와 병렬 스레드를 지원하도록 시스템을 확장하는 것은 두 가지 문제를 해결해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;경쟁 조건 (race condition)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동기화 (synchronization)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ 경쟁 조건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리율 제한 조건은 아래 처럼 동작&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레디스에서 카운터 값을 읽음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count + 1&lt;/code&gt; 값이 임계치를 넘는지 확인&lt;/li&gt;
&lt;li&gt;넘지 않는다면 레디스에 보관된 카운터 값을 1만큼 증가 후 저장.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 이때, 병행성이 심한 환경에서는 경쟁 조건 이슈가 발생할 수 있음!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSKi1F/dJMcagc3ydg/RituzEKwMu750PCgHtYIy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSKi1F/dJMcagc3ydg/RituzEKwMu750PCgHtYIy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSKi1F/dJMcagc3ydg/RituzEKwMu750PCgHtYIy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSKi1F%2FdJMcagc3ydg%2FRituzEKwMu750PCgHtYIy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;388&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  경쟁 조건 문제를 해결하는 가장 널리 알려진 문제는 &lt;b&gt;락(lock)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 락은 시스템 성능을 상당히 떨어뜨리는 문제가 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;락 대신 사용할 수 있는 해결책 2가지&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%A3%A8%EC%95%84_(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%96%B8%EC%96%B4)&quot;&gt;&lt;b&gt;루아 스크립트&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redis.io/docs/latest/develop/data-types/sorted-sets/&quot;&gt;&lt;b&gt;Redis 자료구조인 정렬 집합 사용&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  동기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산환경에서 고려해야 할 중요한 요소중 하나가 동기화이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수백만 사용자를 지원하기 위해서는 한 대의 처리율 제한 장치 서버는 충분하지 않을 수 있고 여러 대를 두게 되면 동기화가 필요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/span&gt;가 제한 장치&lt;b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;A`&lt;/span&gt;&lt;/b&gt; 에 요청을 보내고 클라이언트 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;B&lt;/span&gt;&lt;/b&gt;가 제한 장치 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;B`&lt;/b&gt;&lt;/span&gt; 에 요청을 보내고 있을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 계층은 무상태로 클라이언트는 다음 요청을 다른 제한 장치로 보낼 수 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동기화를 하지 않은 상태라면 제한장치 A``은 클라이언트 B`에 대해 아무것도 모르기 때문에 처리율 제한을 제대로 수행할 수 없다.&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;을 활용해 같은 클라이언트로부터 요청은 항상 같은 처리율 제한 장치로 보내기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 가능하지도 유연하지 않은 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;레디스..!&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레디스 같은 중앙 집중형 데이터 저장소를 이용해 처리하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgqN8w/dJMcafkUbjy/DgbPRvxZo3FLBKh8ZGljak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgqN8w/dJMcafkUbjy/DgbPRvxZo3FLBKh8ZGljak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgqN8w/dJMcafkUbjy/DgbPRvxZo3FLBKh8ZGljak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgqN8w%2FdJMcafkUbjy%2FDgbPRvxZo3FLBKh8ZGljak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;400&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  성능 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설계들은 2가지 지점에서 성능 개선이 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 여러 데이터센터를 지원하는 것은 처리율 제한 장치에 중요한 문제라는 것을 상기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 트래픽을 가장 가까운 엣지서버로 전달하여 지연시간을 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 제한 장치간 데이터를 동기화할 때 최종 일관성 모델 (eventual consistency model)을 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강한 일관성&lt;/li&gt;
&lt;li&gt;약한 일관성&lt;/li&gt;
&lt;li&gt;결과적 일관성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리율 제한 장치를 설치한 후 효과적으로 동작하는지 확인하기 위해 데이터를 모을 필요가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;채택된 처리율 제한 알고리즘이 효과적인지&lt;/li&gt;
&lt;li&gt;정의한 처리율 제한 규칙이 효과적인지&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=ref-box&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#request-rate-limiter&quot;&gt;https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#request-rate-limiter&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/&quot;&gt;https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/193</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-4%EC%9E%A5-%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98%EC%9D%98-%EC%84%A4%EA%B3%84#entry193comment</comments>
      <pubDate>Thu, 25 Dec 2025 13:23:14 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 3장. 시스템 설계 면접 공략법</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-3%EC%9E%A5-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%A9%B4%EC%A0%91-%EA%B3%B5%EB%9E%B5%EB%B2%95</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  시스템 설계 면접이 있는 이유??&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모호한 문제를 풀기 위해서 해결책을 찾아내는 과정에 대한 시뮬레이션&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;정해진 결말도 정답도 없다!&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 기술을 시연하는 자리이고 설계 과정에서 내린 결정들에 대한 방어 능력을 보이는 자리이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면접관의 피드백을 건설적인 방식으로 처리할 자질이 있음을 보이는 자리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;  &lt;b&gt;면접관의 입장&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일차적 목표는 능력을 평가하는 것&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;압박이 심한 상황도 잘 헤쳐 나가는 자질이 있는지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모호한 문제를 건설적으로 해결할 능력&lt;/b&gt;이 있는지&lt;/li&gt;
&lt;li&gt;설계의 순수성에 집착하여 &lt;code&gt;트레이드 오프&lt;/code&gt;를 도외시하고 &lt;b&gt;과도한 엔지니어링&lt;/b&gt;을 하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;♂️ 효과적 면접을 위한 4단계 접근법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 문제 이해 및 설계 범위 확정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 설계 면접 과정에서는 &lt;u&gt;&lt;b&gt;생각 없이 바로 답을 내는 것은 좋지 않다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;요구사항&lt;/span&gt;을 완전히 이해하지 않고 답을 내놓는 행위는 &lt;span style=&quot;color: #ee2323;&quot;&gt;엄청난 부정적 신호&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;속도를 늦춰라&lt;/b&gt; ⭐&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깊이 생각하고 질문하여 요구사항과 가정들을 분명히 하라&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔지니어가 가져야 할 가장 중요한 기술 중 하나는 &lt;b&gt;올바른 질문을 하는 것, 적절한 가정을 하는 것, 시스템 구축에 필요한 정보를 모으는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문을 던졌을 때 면접관은 질문에 대한 답을 내놓거나 스스로 어떤 가정을 하기를 주문할 것이다.&lt;/li&gt;
&lt;li&gt;후자의 경우는 그 가정을 화이트보드나 종이에 적어두어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 정확히 이해하기 위해 해볼 수 있는 질문들&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구체적으로 어떤 기능을 만들어야 하나?&lt;/li&gt;
&lt;li&gt;제품 사용자 수는 얼마나 되나?&lt;/li&gt;
&lt;li&gt;회사의 규모는 얼마나 빨리 커질까?&lt;/li&gt;
&lt;li&gt;회사가 주로 사용하는 기술 스택은 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;이 단계에서는 개략적인 설계안을 제시하고 면접관의 동의를 얻는 것에 초점을 두어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면접관과 협력하여 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;설계안에 대한 최초 청사진을 제시하고 의견을 구하라&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면접관을 마치 팀원인 것처럼 대하라&lt;/li&gt;
&lt;li&gt;훌륭한 면접관들은 지원자들과 대화하고 설계 과정에 개입하는 것을 즐긴다.&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트, API, 웹 서버, 데이터 저장소, 캐시, CDN, 메시지 큐 등&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;zwj;  &lt;b&gt;최초 설계안이 시스템 규모에 관계된 제약사항들을 만족하는지 개략적으로 계산해 보라.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 상세 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계까지 왔다면 시스템에서 &lt;b&gt;달성해야 할 목표와 기능 범위를 확인했을 것이고 전체 설계의 개략적 청사진도 준비가 되었고 상세 설계에서 집중해야 할 영역들을 확인&lt;/b&gt;했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 면접관과 해야 할 일은 설계 대상 컴포넌트 사이의 우선순위를 정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관이 집중했으면 하는 영역을 알려주기도 시스템 병목 구간이나 자원 요구량 추정치에 초점이 맞춰진 시스템 성능 특성에 대한 질문을 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;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;code&gt;ex) 단축 URL 생성기 설계&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해시 함수의 설계를 구체적으로 설명하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ex) 채팅 시스템&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떻게 하면 지연시간을 줄이고 사용자의 온 / 오프라인 상태를 표시할 것인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⏱️ &lt;b&gt;불필요한 세부사항에 시간을 쓰지 말아라&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사소한 세부사항을 설명하느라 능력을 보일 기회를 놓칠 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마무리 단계에서는 면접관은 설계 결과물에 대해 후속 질문들 던질 수도 있고 스스로 추가 논의를 진행하도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관이 시스템 병목 구간, 혹은 더 개선 가능한 지점을 찾아내라고 할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개선할 점은 언제나 있다.&lt;/li&gt;
&lt;li&gt;설계가 완벽하다거나 개선할 부분이 없다는 답은 하지 말자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계를 다시 요약을 한 번해주는 것도 도움이 될 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면접관의 기억을 환기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해야할 것과 하지말아야 할 것&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;해야 할 것&lt;/td&gt;
&lt;td&gt;하지 말아야 할 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;질문을 통해 확인하라. 스스로 내린 가정이 옳다 믿고 진행하지마라&lt;/td&gt;
&lt;td&gt;전형적인 면접 문제들에 대비하지 않은 상태에서 면접장에 가지마라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문제의 요구사항을 이해하라&lt;/td&gt;
&lt;td&gt;요구사항이나 가정을 분명히 하지 않은 채 설계를 제시하지 마라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정답이나 최선의 답안은 없다는 것을 명시&lt;/td&gt;
&lt;td&gt;처음부터 특정 컴포넌트의 세부사항을 너무 깊게 설명하지 마라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;면접관과 소통하여 사고 흐름을 이해할 수 있도록 하라&lt;/td&gt;
&lt;td&gt;진행중 막혔다면 힌트 청하기를 주저하지 마라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여러 해법을 가능하면 함께 제시&lt;/td&gt;
&lt;td&gt;소통을 주저하지 마라. 침묵 속에 설계를 진행하지 마라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개략적 설계에 면접관이 동의하면 중요한 컴포넌트부터 세부사항을 설명하기 시작하라&lt;/td&gt;
&lt;td&gt;설계안을 내놓는 순간 면접이 끝났다고 생각하지 마라&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;면접관의 아이디어를 이끌어 내라. 같은 팀원처럼 협력&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포기하지 마라&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;그동안 여러 면접을 많이 참여해봤지만 내 입장에서만 면접을 준비하고 차마 면접관의 입장을 생각해본 경험이 많이 없는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관이 직무에서 원하는 것은 무엇이고 내가 니즈에 충족한 사람임을 보여주기 위해서 어떤 것들을 어필해야 할지 어떤 질문들이 들어올지 한 번쯤 생각해보는 연습도 해보면 좋을 것 같다는 생각을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ 면접 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관에게 쫄지말자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 팀원이라고 생각하고 계속해서 의견을 물어보고 질문하고 결정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 모르는 것이라고 포기하지 말자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포기하는 것이 더 최악이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/192</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-3%EC%9E%A5-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%A9%B4%EC%A0%91-%EA%B3%B5%EB%9E%B5%EB%B2%95#entry192comment</comments>
      <pubDate>Thu, 25 Dec 2025 13:07:20 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 2장. 개략적인 규모 추정</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-2%EC%9E%A5-%EA%B0%9C%EB%9E%B5%EC%A0%81%EC%9D%B8-%EA%B7%9C%EB%AA%A8-%EC%B6%94%EC%A0%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 설계 면접에서 시스템 용량, 성능 요구사항을 개략적으로 추정하라는 요구를 받게됨.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개략적인 규모 추정(back-of-the-envelope estimation)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보편적으로 성능 수치상에서 사고 실험을 행하여 추정치를 계산하는 행위&lt;/li&gt;
&lt;li&gt;어떤 설계가 요구사항에 부합할 것인지 보기 위함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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️⃣&amp;nbsp;2의 제곱수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 볼륨의 단위를 2의 제곱수로 표현하면 어떻게 되는지 알아야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2의 x제곱&lt;/td&gt;
&lt;td&gt;근사치&lt;/td&gt;
&lt;td&gt;이름&lt;/td&gt;
&lt;td&gt;축약형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1천&lt;/td&gt;
&lt;td&gt;1킬로바이트&lt;/td&gt;
&lt;td&gt;1KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1백만&lt;/td&gt;
&lt;td&gt;1메가바이트&lt;/td&gt;
&lt;td&gt;1MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;10억&lt;/td&gt;
&lt;td&gt;1기가바이트&lt;/td&gt;
&lt;td&gt;1GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1조&lt;/td&gt;
&lt;td&gt;1테라바이트&lt;/td&gt;
&lt;td&gt;1TB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;1000조&lt;/td&gt;
&lt;td&gt;1페타바이트&lt;/td&gt;
&lt;td&gt;1PB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;응답지연 값&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글의 제프 딘이 2010년에 통상적인 컴퓨터에서 구현된 연산들의 응답지연 값을 공개한 바가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 구글 엔지니어가 개발한 도구를 사용해 최근 기술 동향을 반영하여 2020년 기준 시각화한 수치를 분석하면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리는 빠르지만 디스크는 아직 느리다.&lt;/li&gt;
&lt;li&gt;디스크 탐색은 가능한 한 피하라.&lt;/li&gt;
&lt;li&gt;단순한 압축 알고리즘은 빠르다.&lt;/li&gt;
&lt;li&gt;데이터를 인터넷으로 전송하기 전에 가능하면 &lt;b&gt;압축&lt;/b&gt;하라.&lt;/li&gt;
&lt;li&gt;데이터 센터는 보통 여러 지역에 분산되어 있고 센터들 간에 데이터를 주고받는 데는 시간이 걸린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️&amp;nbsp;가용성에 관계된 수치들&lt;/h3&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLA&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 사업자가 보편적으로 사용하는 용어&lt;/li&gt;
&lt;li&gt;서비스 사업자와 고객 사이에 맺어진 합의를 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;예시 - 트위터 QPS와 저장소 요구량 측정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&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;월간 능동 사용자(MAU)는 3억명&lt;/li&gt;
&lt;li&gt;50%의 사용자가 트위터를 매일 사용한다.&lt;/li&gt;
&lt;li&gt;평균적으로 각 사용자는 매일 2건의 트윗을 올린다.&lt;/li&gt;
&lt;li&gt;미디어를 포함하는 트윗은 10% 정도다.&lt;/li&gt;
&lt;li&gt;데이터는 5년간 보관된다.&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;b&gt;QPS(Query Per Second) 추정치&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일간 능동 사용자(DAU) =&lt;b&gt; 3억 x 50% = 1.5억(150million)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;QPS = &lt;b&gt;1.5억 x 2트윗 / 24시간 / 3600초 = 약 3500&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;최대 QPS(Peek QPS) =&lt;b&gt; 2 X QPS = 약 7000&lt;/b&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tweet_id에 64바이트&lt;/li&gt;
&lt;li&gt;텍스트에 140바이트&lt;/li&gt;
&lt;li&gt;미디어에 1MB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;미디어 저장소 요구량 : 1.5억 x 2 x 10% x 1MB = 30TB/일&lt;/li&gt;
&lt;li&gt;5년간 미디어를 보관하기 위한 저장소 요구량 : 30TB X 365 X 5 = 약 55PB&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; &amp;nbsp;Tip&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모 추정과 관계된 면접에서 중요한 것은 &lt;u&gt;&lt;b&gt;문제를 풀어나가는 절차&lt;/b&gt;!!&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면접자가 보고 싶어하는 것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;문제 해결 능력&lt;/span&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;1️⃣&amp;nbsp;근사치를 활용한 계산을 하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 계산을 하는 것은 어렵다.&lt;/li&gt;
&lt;li&gt;적절한 근사치를 활용해 시간을 절약하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣&amp;nbsp;가정들은 적어두자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나중에 살펴 보기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;단위를 붙이기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모호함을 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/191</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-2%EC%9E%A5-%EA%B0%9C%EB%9E%B5%EC%A0%81%EC%9D%B8-%EA%B7%9C%EB%AA%A8-%EC%B6%94%EC%A0%95#entry191comment</comments>
      <pubDate>Thu, 25 Dec 2025 13:01:31 +0900</pubDate>
    </item>
    <item>
      <title>[가상면접 사례로 배우는 대규모 시스템 설계 기초] 1장. 사용자 수에 따른 규모 확장성</title>
      <link>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1%EC%9E%A5-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;단일 서버&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNNIWB/dJMcaiaRHiC/25YpoEjQOYBGjiNMhXZvJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNNIWB/dJMcaiaRHiC/25YpoEjQOYBGjiNMhXZvJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNNIWB/dJMcaiaRHiC/25YpoEjQOYBGjiNMhXZvJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNNIWB%2FdJMcaiaRHiC%2F25YpoEjQOYBGjiNMhXZvJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;444&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;668&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; &amp;nbsp;데이터베이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 늘면 서버 하나로는 충분하지 않기 때문에 여러 서버를 두어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&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;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;웹 / 모바일 트래픽 처리 서버(웹 계층)&lt;/b&gt; 와 &lt;b&gt;데이터베이스 서버(데이터 계층)&lt;/b&gt; 을 분리하면 각각을 독립적으로 확장해 나갈 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;↗️&amp;nbsp;수직적 규모 확장 vs 수평적 규모 확장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;수직적 규모 확장&lt;/b&gt; (scale up)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에 고사양 자원을 추가하는 행위&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;수평적 규모 확장&lt;/b&gt; (scale out)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 많은 서버를 추가하여 성능을 개선하는 행위&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버로 유입되는 트래픽의 양이 적을 때는 수직적 확장이 좋은 선택!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 방법의 가장 큰 장점은 단순함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️&amp;nbsp;&lt;b&gt;그러나 스케일 업에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;몇 가지 심각한 단점&lt;/span&gt;이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;한계&lt;/b&gt;: 한 대의 서버에 CPU나 메모리를 무한대로 증성할 방법은 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장애에 대한 자동복구(failover)&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;b&gt;따라서 대규모 애플리케이션을 지원하는 데는&amp;nbsp;수평적 규모 확장법이 보다 적절하다&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⏩&amp;nbsp;로드밸런서&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDUIT9/dJMcagjOcuK/rC9h78pQNgVGKYm9XDOJZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDUIT9/dJMcagjOcuK/rC9h78pQNgVGKYm9XDOJZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDUIT9/dJMcagjOcuK/rC9h78pQNgVGKYm9XDOJZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDUIT9%2FdJMcagjOcuK%2FrC9h78pQNgVGKYm9XDOJZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;523&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &lt;b&gt;로드밸런서&lt;/b&gt;의 public IP address로 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 간 통신에는 private IP address를 이용&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;private IP address&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 네트워크에 속한 서버 사이의 통신에만 쓰일 수 있는 IP 주소&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로드밸런서는 웹 서버와 통신하기 위해 이 사설 주소를 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하 분산 집합에 웹 서버를 추가하고 나면(scale out) &lt;u&gt;&lt;b&gt;장애를 자동복구하지 못하는 문제 (no failover&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;b&gt;가 해소&lt;/b&gt;&lt;/u&gt;되고 &lt;u&gt;&lt;b&gt;웹 계층의 가용성(availabilit&lt;/b&gt;&lt;b&gt;y)는 향상&lt;/b&gt;&lt;/u&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt;&amp;nbsp;failover&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;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt;&amp;nbsp;웹 계층의 가용성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 웹 서비스를 쓰고 싶을 때 웹 서버가 정상 동작 중이라서 요청을 처리해 줄 확률, 비율&lt;/li&gt;
&lt;li&gt;웹 애플리케이션이 작동하는 시간의 비율&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;데이터베이스 다중화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드밸런서를 웹 계층에 적용하여 트래픽을 분산하고 장애의 자동복구나 다중화를 지원하게 되었는데 데이터베이스 서버는 하나뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 데이터베이스 관리 시스템은 다중화를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;master-slave&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 서버 사이에 &lt;b&gt;master-slave&lt;/b&gt; 관계를 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 데이터 원본은 주 서버&lt;/b&gt;에 &lt;b&gt;사본은 부 서버&lt;/b&gt;에 저장하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기 연산은 마스터에서만 지원하고 부 데이터베이스는 주 데이터베이스로부터 그 사본을 전달받아 읽기 연산만을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 애플리케이션은 읽기 연산의 비중이 쓰기 연산보다 훨씬 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 부 데이터베이스의 수가 주 데이터베이스의 수보다 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&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;b&gt;더 나은 성능&lt;/b&gt;: 모든 데이터 변경 연산은 주 데이터베이스 서버로, 읽기 연산은 부 데이터베이스 서버들로 분산되어 병렬로 처리될 수 있는 query 수가 늘어나 성능이 좋아진다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⬅️&amp;nbsp;캐시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 시간은 &lt;b&gt;캐시&lt;/b&gt;를 붙이고 &lt;b&gt;정적 콘텐츠를 콘텐츠 전송 네트워크(Content Delivery Network, CDN)&lt;/b&gt; 로 옮기면 개선할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐시&lt;/b&gt;는 값 비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 성능은 &lt;b&gt;데이터베이스&lt;/b&gt;를 얼마나 자주 호출하느냐에 크게 좌우되는데 캐시는 그런 문제를 완화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️&amp;nbsp;캐시 계층&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 계층은 데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능이 개선될 뿐아니라 데이터베이스의 부하를 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;캐시 우선 읽기 전략&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvUBOk/dJMcaiu7FfU/ZDWVLMdAkkYQbjfqb7Mat0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvUBOk/dJMcaiu7FfU/ZDWVLMdAkkYQbjfqb7Mat0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvUBOk/dJMcaiu7FfU/ZDWVLMdAkkYQbjfqb7Mat0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvUBOk%2FdJMcaiu7FfU%2FZDWVLMdAkkYQbjfqb7Mat0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;113&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️&amp;nbsp;캐시 사용시 유의할 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;캐시는 어떤 상황에 바람직한가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 갱신은 자주 일어나지 않지만 참조는 빈번하게 일어나는 경우 고려해볼 만하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;nbsp;어떤 데이터를 캐시에 두어야 하나?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영속적으로 보관할 데이터를 캐시에 두는 것은 바람직하지 않다.&lt;/li&gt;
&lt;li&gt;중요한 데이터는 지속적 저장소에 두어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;캐시에 보관된 데이터는 어떻게 만료되는가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이에 대한 정책을 마련해 두는 것은 좋다.&lt;/li&gt;
&lt;li&gt;만료 정책이 없으면 데이터는 캐시에 계속 남게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;일관성은 어떻게 유지되는가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 저장소의 원본과 캐시 내의 사본이 같은지 여부&lt;/li&gt;
&lt;li&gt;저장소의 원본을 갱신하는 연산과 캐시를 갱신하는 연산이 단일 트랜잭션으로 처리되지 않는 경우 이 일관성은 깨질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&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;b&gt;단일 장애 지점(Single Point of Failure, SPOF)&lt;/b&gt;이 되어버릴 가능성이 있다.&lt;/li&gt;
&lt;li&gt;SPOF를 피하기 위해서는 캐시 서버를 분산시켜야 한다.&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;b&gt;캐시 메모리는 얼마나 크게 잡을 것인가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시 메모리가 너무 작으면 데이터가 너무 자주 캐시에서 밀려나 성능이 떨어진다.&lt;/li&gt;
&lt;li&gt;캐시 메모리를 과할당하면 보관될 데이터가 갑자기 늘어났을 때 생길 문제도 방지할 수 있게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;데이터 방출 정책은 무엇인가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시가 꽉 차면 추가로 캐시에 데이터를 넣어야 할 경우 기존 데이터를 내보내야 한다.&lt;/li&gt;
&lt;li&gt;가장 널리 쓰이는 것은 &lt;b&gt;LRU&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;Least Recently Used&lt;/b&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;콘텐츠 전송 네트워크 (CDN)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CDN&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;이미지, 비디오, CSS, Javascript 파일등을 캐시할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YREzR/dJMcafd82wC/V1jwR1yQ6FyxWpHqyJgzD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YREzR/dJMcafd82wC/V1jwR1yQ6FyxWpHqyJgzD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YREzR/dJMcafd82wC/V1jwR1yQ6FyxWpHqyJgzD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYREzR%2FdJMcafd82wC%2FV1jwR1yQ6FyxWpHqyJgzD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;273&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;468&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자 A가 이미지 URL을 이용해 &lt;b&gt;image.png&lt;/b&gt;에 접근한다.&lt;/li&gt;
&lt;li&gt;CDN 서버의 캐시에 해당 이미지가 없는 경우, 서버는 원본 서버에 요청하여 파일을 가져온다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 서버는 웹 서버일 수도 아마존 S3 같은 온라인 저장소일 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;원본 서버가 파일을 CDN 서버에 반환한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TTL 값을 응답 HTTP 헤더로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CDN 서버는 파일을 캐시하고 사용자 A에게 반환한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TTL에 명시된 시간이 끝날 때 까지 캐시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용자 B가 같은 이미지에 대한 요청을 CDN 서버에 전송한다.&lt;/li&gt;
&lt;li&gt;만료되지 않은 이미지에 대한 요청은 캐시를 통해 처리된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️&amp;nbsp;무상태 웹 계층(stateless)&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;상태 정보(사용자 세션 데이터 등)을 웹 계층에서 제거하고 관계형 데이터베이스나 NoSQL 같은 지속성 저장소에 보관하고 필요할 때 가져오도록 한다.&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HHSsb/dJMcaiIEYnn/OrOJ9Ksz5pg6tOrb4EEnp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HHSsb/dJMcaiIEYnn/OrOJ9Ksz5pg6tOrb4EEnp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HHSsb/dJMcaiIEYnn/OrOJ9Ksz5pg6tOrb4EEnp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHHSsb%2FdJMcaiIEYnn%2FOrOJ9Ksz5pg6tOrb4EEnp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;517&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 HTTP 요청은 어떤 웹 서버로 전달될 수 있고 웹 서버는 상태 정보가 필요할 경우 공유 저장소에서 데이터를 가져온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 정보는 웹 서버로부터 분리되어 있어 단순하고 안정적이며 규모 확장이 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;데이터 센터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내되는데 이 절차를 &lt;u&gt;&lt;b&gt;지리적 라우팅&lt;/b&gt;&lt;/u&gt;이라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZpA1Y/dJMcadUSoI5/fZu96FwUNBoFSY5C0TRWW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZpA1Y/dJMcadUSoI5/fZu96FwUNBoFSY5C0TRWW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZpA1Y/dJMcadUSoI5/fZu96FwUNBoFSY5C0TRWW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZpA1Y%2FdJMcadUSoI5%2FfZu96FwUNBoFSY5C0TRWW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;354&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 센터중에 하나에 장애가 발생하면 모든 트래픽은 장애가 없는 데이터 센터로 전송된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;다중 데이터센터 아키텍처를 위한 기술적 난제 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣&amp;nbsp;&lt;b&gt;트래픽 우회&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GeoDNS는 사용자에게서 가장 가까운 데이터센터로 트래픽을 보낼 수 있도록 해준다.&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;2️⃣&amp;nbsp;&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;데이터 센터마다 별도의 DB를 사용한다면 다른 데이터센터에서는 찾는 데이터가 없을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;테스트와 배포&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 사이트 애플리케이션을 여러 위치에서 테스트 해보는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;메시지 큐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 큐&lt;/b&gt;는 메시지의 무손실을 보장하는 메시지의 버퍼 역할을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;비동기&lt;/b&gt;&lt;/span&gt;적으로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Publish / Producer&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;Consumer&lt;/b&gt;, &lt;b&gt;Subscribe&lt;/b&gt;가 메세지를 꺼내서 동작을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메세지 큐를 이용하면 서비스 또는 서버 간 결합이 느슨해져서 규모 확장성이 보장되어야 하는 안정적 애플리케이션을 구성하기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;로그, 메트릭 그리고 자동화&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZFZ3C/dJMcaiaRHz6/qEuskXBjemr90ksLKkmjQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZFZ3C/dJMcaiaRHz6/qEuskXBjemr90ksLKkmjQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZFZ3C/dJMcaiaRHz6/qEuskXBjemr90ksLKkmjQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZFZ3C%2FdJMcaiaRHz6%2FqEuskXBjemr90ksLKkmjQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;588&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;954&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; &amp;nbsp;데이터베이스 규모 확장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장할 데이터가 많아지면 데이터베이스에 대한 부하도 증가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이서의 규모를 확장하는 2가지 접근법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수직적 규모 확장법&lt;/li&gt;
&lt;li&gt;수평적 규모 확장법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;수평적 확장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 수평적 확장은 &lt;b&gt;샤딩&lt;/b&gt; 이라 부르는데 더 많은 서버를 추가함으로 성능을 향상시킬 수 있도록 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;샤딩&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대규모 데이터베이스를 샤드 라고 부르는 작은 단위로 분할&lt;/li&gt;
&lt;li&gt;모든 샤드는 같은 스키마를 사용하지만 보관되는 데이터 사이에 중복이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐&amp;nbsp;샤딩 전략을 구현할 때 고려할 가장 중요한 것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;샤딩 키&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;/li&gt;
&lt;li&gt;데이터를 고르게 분할 할 수 있도록 하는 것이 가장 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pTp0i/dJMcabW6Swm/YDpK6R1ojmDBr3h7lN8zJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pTp0i/dJMcabW6Swm/YDpK6R1ojmDBr3h7lN8zJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pTp0i/dJMcabW6Swm/YDpK6R1ojmDBr3h7lN8zJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpTp0i%2FdJMcabW6Swm%2FYDpK6R1ojmDBr3h7lN8zJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;622&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️&amp;nbsp;샤딩에서 고려해야할 조건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣&amp;nbsp;&lt;b&gt;데이터 재 샤딩&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터가 너무 많아져 하나의 샤드로 더 이상 감당하기 어려울 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드간 데이터 분포가 균등하지 않을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 현상들이 발생하면 샤드 키를 계산하는 함수를 변경하고 데이터를 재 배치해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣&amp;nbsp;&lt;b&gt;유명인사 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 저스틴 비버, 레이디 가가 같은 유명인사가 전부 같은 샤드에 저장된 DB가 있을 때 read 연산으로 과부하&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;조인과 비정규화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 샤드에 걸친 데이터를 조인하기 어려워진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스를 비정규화하여 하나의 테이블에서 질의가 되도록 한다.&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; &amp;nbsp;배운 점 정리 및 궁금한 점 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Elastic Load Balancing 은 실제로 어떻게 동작할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드밸런스 부분을 읽으면서 실제로 많이 사용되는 &lt;b&gt;AWS&lt;/b&gt;의 Elastic Load Balancing은 어떤 식으로 동작하는지 궁금해져 정리를 해보고자 한다.&lt;/p&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️⃣&amp;nbsp;로드 밸런서는 클라이언트에서 오는 트래픽을 허용하고 하나 이상의 가용 영역에서 등록된 대상(예: EC2 인스턴스)으로 요청을 라우팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 로드 밸런서는 등록된 대상의 상태를 모니터링하고 정상 대상으로만 트래픽이 라우팅&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로드 밸런서가 비정상인 대상을 감지하면 해당 대상으로 트래픽 라우팅을 중단.&lt;/li&gt;
&lt;li&gt;이후 해당 대상이 다시 정상으로 감지되면 트래픽을 해당 대상으로 다시 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;하나 이상의 리스너&lt;/b&gt;를 지정하여 들어오는 트래픽을 허용하도록 로드 밸런서를 구성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스너는 연결 요청을 확인하는 프로세스&lt;/li&gt;
&lt;li&gt;클라이언트와 로드 밸런서 간의 연결을 위한 프로토콜 및 포트 번호로 구성&lt;/li&gt;
&lt;li&gt;로드 밸런서와 대상 간의 연결을 위한 프로토콜 및 포트 번호로 구성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbQPlW/dJMcaf6hgvW/yMFUz2YJEcI9KYQrDQKvfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbQPlW/dJMcaf6hgvW/yMFUz2YJEcI9KYQrDQKvfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbQPlW/dJMcaf6hgvW/yMFUz2YJEcI9KYQrDQKvfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbQPlW%2FdJMcaf6hgvW%2FyMFUz2YJEcI9KYQrDQKvfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;510&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;캐시 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책에서는 &lt;b&gt;캐시 우선 읽기 전략&lt;/b&gt;을 설명해주었는데 다른 전략들에 대해서도 이전에 학습을 했었는데 기억이 잘 안나 한 번 더 확인해보고 정리를 해봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣&amp;nbsp;&lt;b&gt;Cache-Aside (Lazy Loading)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 패턴&lt;/li&gt;
&lt;li&gt;필요한 데이터만 캐싱&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣&amp;nbsp;&lt;b&gt;Read-Through&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;캐시가 DB 조회를 대신 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;Write-Through&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;쓰기 시 캐시와 DB에 동시 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣&amp;nbsp;&lt;b&gt;Write-Behind (Write-Back)&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;캐시에만 쓰고 나중에 DB에 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5️⃣&amp;nbsp;&lt;b&gt;Refresh-Ahead&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만료 전 미리 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033116&quot;&gt;https://product.kyobobook.co.kr/detail/S000001033116&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/가상 면접 사례로 배우는 대규모 시스템 설계 기초</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/190</guid>
      <comments>https://beomsic.tistory.com/entry/%EA%B0%80%EC%83%81%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1%EC%9E%A5-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1#entry190comment</comments>
      <pubDate>Thu, 25 Dec 2025 12:57:57 +0900</pubDate>
    </item>
    <item>
      <title>네이버 2025 컨퍼런스 DAN 25 참여 후기</title>
      <link>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-2025-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-DAN-25-%EC%B0%B8%EC%97%AC-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnKAKW/dJMcafdQOio/p16kUqr56QTIYWZPTT1k7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnKAKW/dJMcafdQOio/p16kUqr56QTIYWZPTT1k7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnKAKW/dJMcafdQOio/p16kUqr56QTIYWZPTT1k7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnKAKW%2FdJMcafdQOio%2Fp16kUqr56QTIYWZPTT1k7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;456&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;b&gt;네이버&lt;/b&gt; 측&lt;/span&gt;에서 &lt;b&gt;&lt;span data-token-index=&quot;2&quot;&gt;부스트캠프 웹 모바일&lt;/span&gt; 캠퍼&lt;/b&gt;들이 기술적인 전문성과 인사이트를 가진 개발자로 성장하는 데 도움이 될 수 있도록 &lt;b&gt;팀네이버 컨퍼런스&lt;/b&gt;인 &lt;b&gt;DAN25&lt;/b&gt; 티켓을 제공해주셨다는 이야기를 듣고 바로 티켓 신청을 했는데 운 좋게도 이번 &lt;b&gt;DAN25 컨퍼런스&lt;/b&gt;에 참여하게 되었습니다! 감사합니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwt04v/dJMcae65cUH/VQZ17h9Cc13abdzp03UpmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwt04v/dJMcae65cUH/VQZ17h9Cc13abdzp03UpmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwt04v/dJMcae65cUH/VQZ17h9Cc13abdzp03UpmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdwt04v%2FdJMcae65cUH%2FVQZ17h9Cc13abdzp03UpmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;429&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 행사는 &lt;b&gt;NFT 티켓&lt;/b&gt;을 통해서 진행되었고 &lt;b&gt;DAN NFT 행사&lt;/b&gt;에 참여해 커피도 받았습니다 ☕&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H3akb/dJMcabvKKuB/Nb1HpvP2dWXjJcGHyFugR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H3akb/dJMcabvKKuB/Nb1HpvP2dWXjJcGHyFugR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H3akb/dJMcabvKKuB/Nb1HpvP2dWXjJcGHyFugR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH3akb%2FdJMcabvKKuB%2FNb1HpvP2dWXjJcGHyFugR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;661&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;1188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;왜 DAN 이지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 네이버의 컨퍼런스 이름에 &lt;b&gt;&amp;rsquo;네이버&amp;lsquo;&lt;/b&gt; 가 없는 이유는 뭔지 궁금했는데요..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;플랫폼(Platform)&lt;/b&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; &amp;nbsp;&lt;b&gt;Ground&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DAN25 &lt;span style=&quot;color: #ee2323;&quot;&gt;GROUND&lt;/span&gt;&lt;/b&gt;에서는 여러 체험 프로그램과 DAN25 NFT 이벤트, 그리고 한정판 콜라보 굿즈도 얻을 수 있었어요  &lt;/p&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;GROUND&lt;/b&gt;는 &lt;b&gt;Naver zone&lt;/b&gt;과 &lt;b&gt;Partner Zone&lt;/b&gt;가 있고 각 부스에서 여러가지의 활동을 할 수 있었는데요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버의 기술과 새로운 서비스들을 살펴볼 수 있었고 파트너 기업들의 기술들에 대한 설명도 듣고 이에 대한 체험을 하면서 굿즈들도 받을 수 있었습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhyAtI/dJMcac9gDVP/9Wkj20JkRlTxWltXtB8E4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhyAtI/dJMcac9gDVP/9Wkj20JkRlTxWltXtB8E4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhyAtI/dJMcac9gDVP/9Wkj20JkRlTxWltXtB8E4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhyAtI%2FdJMcac9gDVP%2F9Wkj20JkRlTxWltXtB8E4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;398&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 부스에 참여했는데 가장 기억에 남는 몇 가지만 소개해보겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;Networking Zone&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbr08M/dJMcain6TSs/fFdKh0cFvUw78Kd0M3yLP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbr08M/dJMcain6TSs/fFdKh0cFvUw78Kd0M3yLP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbr08M/dJMcain6TSs/fFdKh0cFvUw78Kd0M3yLP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbr08M%2FdJMcain6TSs%2FfFdKh0cFvUw78Kd0M3yLP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;330&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;672&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 &lt;b&gt;DAN25&lt;/b&gt;에는 평소 궁금했던 내용을 자유롭게 질문하고 이에 대한 답변이나 피드백을 받을 수 있는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Networking Zone&lt;/b&gt;&lt;/span&gt;이 운영되었어요  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 역시 질문을 남기면서 다른 분들의 질문도 살펴봤는데 질문들에 대해서 자세히 답변을 남겨주셨더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI&lt;/b&gt;에 관한 질문들도 많고 평소에 궁금했던 내용들을 많이 물어보셨던 것 같아요!@&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;네이버 지도&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버지도 부스에서는 &lt;b&gt;새로운 기술&lt;/b&gt;뿐만 아니라&lt;b&gt; 아직 출시되지 않은 다양한 기능&lt;/b&gt;들도 미리 경험해볼 수 있었어요 ✔️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 네이버 지도와 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;공간지능&lt;/b&gt;&lt;/span&gt; 기술이 결합된 점이 인상깊었어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AR 기술&lt;/b&gt;이 접목된 내비게이션 기능을 통해 복잡한 공간에서도 쉽게 길을 찾을 수 있게 해주는 서비스를 체험할 수 있었는데 이번 &lt;b&gt;DAN25&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: #006dd7;&quot;&gt;&lt;b&gt;Flying View 3D&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-origin-width=&quot;640&quot; data-origin-height=&quot;1220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nKzS9/dJMcac2uZUT/U9xl0DE0H7iaObkcOGaaW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nKzS9/dJMcac2uZUT/U9xl0DE0H7iaObkcOGaaW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nKzS9/dJMcac2uZUT/U9xl0DE0H7iaObkcOGaaW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnKzS9%2FdJMcac2uZUT%2FU9xl0DE0H7iaObkcOGaaW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;732&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;1220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 &lt;b&gt;'발견'&lt;/b&gt; 탭에서는 주변 인기 장소와 전국적인 트렌드를 한눈에 확인하고 마음에 드는 장소는 저장해둘 수 있었습니다. &lt;b&gt;&amp;lsquo;예약&amp;rsquo;&lt;/b&gt; 탭을 통해서는 음식점이나 헤어샵은 물론 놀이공원, 워터파크, 여행 상품까지 예약할 수 있었고 예약이 가능한 곳만 선별해 검색할 수 있는 기능도 곧 출시될 예정이라고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현장에서 느낀 네이버지도의 발전된 기술과 폭넓어진 사용성을 직접 체험해보면서 앞으로도 많이 활용할 것 같다는 생각이 들었어요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;네이버 로고 프로젝트&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8I05y/dJMcaj8ooNe/kPkRfK74A32ySis9J3Tbo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8I05y/dJMcaj8ooNe/kPkRfK74A32ySis9J3Tbo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8I05y/dJMcaj8ooNe/kPkRfK74A32ySis9J3Tbo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8I05y%2FdJMcaj8ooNe%2FkPkRfK74A32ySis9J3Tbo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;270&quot; height=&quot;359&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DAN 25&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;또, AR 체험으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;'나만의 로고'&lt;/b&gt;&lt;/span&gt; 도 직접 만들어보는 체험도 해볼 수 있었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciI96F/dJMcafSsm5b/USbo5l4EMS90GtOvW32vW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciI96F/dJMcafSsm5b/USbo5l4EMS90GtOvW32vW1/img.png&quot; data-alt=&quot;나만의 로고~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciI96F/dJMcafSsm5b/USbo5l4EMS90GtOvW32vW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciI96F%2FdJMcafSsm5b%2FUSbo5l4EMS90GtOvW32vW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;325&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나만의 로고~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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; &amp;nbsp;&lt;b&gt;비전스테이지&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비전스테이지&lt;/b&gt; 체험존에서는 &lt;b&gt;네이버의 미디어 프로덕션 기술&lt;/b&gt;을 활용한 다양한 체험을 해볼 수 있었어요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IC-VFX&lt;/b&gt;, &lt;b&gt;버추얼 아티스트&lt;/b&gt;, &lt;b&gt;VR,&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJHBhd/dJMb995L6Xt/nVwZHMoOeydxpYxk44n2Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJHBhd/dJMb995L6Xt/nVwZHMoOeydxpYxk44n2Nk/img.png&quot; data-alt=&quot;저는 파리의 노을 배경을 선택했어요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJHBhd/dJMb995L6Xt/nVwZHMoOeydxpYxk44n2Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJHBhd%2FdJMb995L6Xt%2FnVwZHMoOeydxpYxk44n2Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;314&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;저는 파리의 노을 배경을 선택했어요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 노을진 파리에 있는 듯한 몰입감 넘치는 영상도 촬영할 수 있었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 바게트 빵을 소품으로 제공해 주셨는데 정말 빵 냄새가 나길래 실제 빵인지 아닌지 함께 다닌 지인과 이야기하고 있었는데 실제 빵이라고 알려주셔서 웃으면서 체험을 즐길 수 있었어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진에서 보이진 않지만 실제 빵 냄새를 맡고 있습니다 ㅎ..ㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj; &amp;nbsp;&lt;b&gt;세션&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DAN25 세션&lt;/b&gt;은 팀네이버 개발자분들이 어떤 고민을 하고 그 고민을 어떤 절차와 방법으로 해결하는지 직접 들어볼 수 있는 자리라고 생각해요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 행사 세션들은 크게&amp;nbsp;&lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;CREATIVE&lt;/span&gt;&lt;/b&gt;,&amp;nbsp;&lt;b&gt;&lt;span data-token-index=&quot;3&quot;&gt;S&amp;amp;B(Service &amp;amp; Business)&lt;/span&gt;&lt;/b&gt;, 그리고&amp;nbsp;&lt;b&gt;&lt;span data-token-index=&quot;5&quot;&gt;TECH&lt;/span&gt;&lt;/b&gt;&amp;nbsp;세션으로 분류되어 있었습니다. 각각의 세션에서 네이버 서비스의 기술적 발전뿐만 아니라 비즈니스와 디자인, 사업성, 그리고 미래 비전까지 팀네이버가 앞으로 어떤 방향성을 생각하고 고민하는지 확인해볼 수 있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참가자 입장에서 현업의 고민과 해결 스토리(경험)를 직접 접할 수 있다는 점이 큰 장점으로 느껴졌어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣&amp;nbsp;&lt;b&gt;기본기가 경쟁력이다 : 네이버가 Ncloud Storage를 만든 이유&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIgPd/dJMcajgfEpz/O4biOnmnpm6B91nT6K7fH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIgPd/dJMcajgfEpz/O4biOnmnpm6B91nT6K7fH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIgPd/dJMcajgfEpz/O4biOnmnpm6B91nT6K7fH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIgPd%2FdJMcajgfEpz%2FO4biOnmnpm6B91nT6K7fH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;211&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버가 기존 Object Storage, Archive Storage 서비스를 대체하는 &lt;b&gt;Ncloud Storage&lt;/b&gt;를 개발하게 된 배경, 과정과 앞으로의 방향성을 공유해주셨어요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표에서는 네이버가 데이터 저장, 관리에서 겪어온 한계, S3 API 표준 선택 이유, 기술 적용 및 미래 계획까지 다루어주셨어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 대량 데이터 관리, 비용 최적화, 오브젝트 스토리지와 클라우드 기술 트렌드에 대해 들어보고 싶어 이 세션을 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네이버에서 스토리지 솔루션을 어떻게 관리하고 변화시켰는지 직접 들어보고 싶었어요!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 서비스를 운영하면서 실제 상황에 맞게 필요한 기능만 선택적으로 도입하려는 전략과 다양한 실운영 제약사항을 고려해 성능을 높이기 위한 &lt;b&gt;Erasure Coding, QoS 제어&lt;/b&gt; 등의 내용을 재미있게 들을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣&amp;nbsp;&lt;b&gt;Pinpoint &amp;times; Pinot : 400만 TPS 실시간 분석, 운영에서 튜닝까지&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qUlyp/dJMcakzsHKU/JruJ1rRF1I8bF8Vy59RDsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qUlyp/dJMcakzsHKU/JruJ1rRF1I8bF8Vy59RDsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qUlyp/dJMcakzsHKU/JruJ1rRF1I8bF8Vy59RDsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqUlyp%2FdJMcakzsHKU%2FJruJ1rRF1I8bF8Vy59RDsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;177&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;400만 TPS를 처리하는 &lt;b&gt;Pinpoint&lt;/b&gt;가 &lt;b&gt;Pinot&lt;/b&gt;를 통해 어떻게&amp;nbsp;개발 생산성과 대용량 데이터 처리 성능을 높였는지 공유해주셨어요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는&amp;nbsp;&lt;b&gt;대용량 실시간 데이터 처리&lt;/b&gt;와&amp;nbsp;&lt;b&gt;운영 환경에서의 성능 튜닝&lt;/b&gt;에 대한 시행착오 경험과 운영 방법들에 대해 직접 들을 수 있는 것이 기대되어 이 세션을 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 세션은 시간대도 점심을 먹고나서 바로 듣다보니 살짝 집중도 잘 안되고 내용이 정말 어려웠던 것 같아요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션이 끝나고 실제 네이버에서 일하고 있던 지인에게 물어봤는데 지인도 발표가 어려웠다고 해서 다행이였어요,, ㅎ..ㅎ (&lt;b&gt;휴&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;데이터 속 숨은 트렌드, LLM이 답하다 : 랭킹 기반 플레이스 트렌드 분석 시스템&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQGwJj/dJMcafrnJ3M/alXKgcsH3ST8p1ZL45IQdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQGwJj/dJMcafrnJ3M/alXKgcsH3ST8p1ZL45IQdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQGwJj/dJMcafrnJ3M/alXKgcsH3ST8p1ZL45IQdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQGwJj%2FdJMcafrnJ3M%2FalXKgcsH3ST8p1ZL45IQdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;328&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실시간 사용자 데이터를 분석&lt;/b&gt;하여 '지금 뜨는 장소'를 결정하는 &lt;b&gt;랭킹 기반 플레이스 트렌드 분석 시스템&lt;/b&gt;을 소개해주셨어요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 단순 인기 장소 추천이 아니라 '급등'과 '지속'의 균형을 맞춘 &lt;b&gt;랭킹 알고리즘&lt;/b&gt;을 통해 진짜 의미 있는 트렌드를 포착하는 방법, &lt;b&gt;텍스트 마이닝&lt;/b&gt;과 &lt;b&gt;LLM&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;실제 서비스에 LLM을 적용&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;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;느낀점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 DAN25 세션을 들으면서 &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;각 세션에서 단순히 기술을 소개하는 수준을 넘어 개발 및 운영 과정에서의 한계나 시스템 성능 문제 등 현실적인 고민과 과제들을 소개해주셨는데 &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;이 인상 깊었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 경험, 사례 덕분에 제가 아직 직접 경험해보지 못한 기술이나 상황도 훨씬 쉽게 이해할 수 있었고 이런 경험 공유가 제 기술적 성장에 큰 도움이 된다고 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이번 경험을 통해 앞으로의 스스로의 방향성에 대해서도 다시 생각해보게 되었어요!&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &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;2️⃣ &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;3️⃣ &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; &amp;nbsp;&lt;b&gt;후기&lt;br /&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHeQDA/dJMb99SeVK6/aJKZGjw8MsA3TKXyMw5sY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHeQDA/dJMb99SeVK6/aJKZGjw8MsA3TKXyMw5sY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHeQDA/dJMb99SeVK6/aJKZGjw8MsA3TKXyMw5sY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHeQDA%2FdJMb99SeVK6%2FaJKZGjw8MsA3TKXyMw5sY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;408&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운 좋게 네이버 &lt;b&gt;DAN25 행사 2일차&lt;/b&gt;에 참여했는데요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 부스에서 재미있는 다양한 체험을 할 수 있었고 현업 개발자 분들이 개발과 운영환경에서 어떤 고민을 하는지 그 고민을 어떻게 해결해 나가는지 생생하게 들을 수 있어 너무 알찬 시간이었습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 늦게 부스에 참여해서 일부 &lt;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;Youtube의 'PLAY NAVER' 채널&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;hellip;이왕이면 &lt;b&gt;관계자로 참여&lt;/b&gt;한다면 더 좋겠다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때까지는 더 다양한 경험을 쌓고 여러 사람들과 교류하며 지식과 인사이트를 넓혀서 다음에는 더 많은 것을 배우고 얻어가는 시간을 만들어보고 싶습니다. (&lt;b&gt;목표 &lt;span&gt; &lt;/span&gt;&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;이번 DAN25 기술 세션 자료는 DAN25 공식 페이지&lt;/b&gt;에서 확인할 수 있습니다!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;&lt;b&gt;출처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dan.naver.com/25&quot;&gt;https://dan.naver.com/25&lt;/a&gt;&lt;/p&gt;</description>
      <category>일상 </category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/189</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-2025-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-DAN-25-%EC%B0%B8%EC%97%AC-%ED%9B%84%EA%B8%B0#entry189comment</comments>
      <pubDate>Sat, 8 Nov 2025 16:43:53 +0900</pubDate>
    </item>
    <item>
      <title>React의 State 업데이트 큐</title>
      <link>https://beomsic.tistory.com/entry/React%EC%9D%98-State-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%81%90</link>
      <description>&lt;h1&gt; &amp;zwj; React의 State 업데이트 큐(Queue)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Batch&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 state 업데이트를 하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다림&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 너무 많은 &lt;code&gt;리렌더링&lt;/code&gt;이 발생하지 않고도 여러 컴포넌트에서 나온 다수의 state 변수를 업데이트 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이벤트 핸들러와 그 안에 있는 코드가 완료될 때까지 UI가 업데이트되지 않는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 React가 &lt;b&gt;여러 상태 업데이트를 단일 리렌더링으로 그룹화하여&lt;/b&gt; 성능을 향상하는 것!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 예시 코드&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{number}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}&amp;gt;+3&amp;lt;/button&amp;gt;
    &amp;lt;/&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;code&gt;setNumber(number + 1)&lt;/code&gt; 를 3번 호출하게 되니깐 결과는 3이 되겠지?!?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 값은 &lt;code&gt;1&lt;/code&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 렌더링의 state의 값은 고정되어 있어 첫 번째 렌더링의 이벤트 핸들러의 number의 값은 setNumber 를 몇 번을 호출하든지 항상 0!&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;  각 렌더링의 state의 값은 고정 ??&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링은 React가 컴포넌트(함수)를 호출한다는 의미.&lt;/p&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;JSX&lt;/b&gt;는 시간상 UI의 스냅샷과 같아 &lt;code&gt;prop&lt;/code&gt;, &lt;code&gt;이벤트 핸들러&lt;/code&gt;, &lt;code&gt;로컬 변수&lt;/code&gt;는 모두 &lt;b&gt;렌더링 당시의 state를 사용해&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;즉, state를 설정하면 다음 렌더링에 대해서만 변경이 된다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 예시를 예시로 설명해보면 다음과 같아요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 렌더링에서의 number의 값은 0&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;button onClick={() =&amp;gt; {
  setNumber(number + 1);
  setNumber(number + 1);
  setNumber(number + 1);
}}&amp;gt;+3&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;setNumber(number + 1)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;number는 0, React는 다음 렌더링에서 number를 1로 변경할 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setNumber(number + 1)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;number는 0, React는 다음 렌더링에서 number를 1로 변경할 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setNumber(number + 1)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;number는 0, React는 다음 렌더링에서 number를 1로 변경할 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 몇번을 호출하던지 해당 렌더링의 이벤트 핸들러에서의 number는 항상 0이므로 state를 1로 세 번 설정하게 됩니다.&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;다음 렌더링 전에 동일한 state 변수를 여러 번 업데이트하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 렌더링 전에 동일한 state 변수를 여러 번 업데이트 하고 싶다면 이전 큐의 state를 기반으로 다음 state를 계산하는 함수를 전달하도록 할 수 있어요!!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 state 값을 대체(변경)하는 것이 아니라 React에 state 값으로 무언가를 하라고 지시&lt;/li&gt;
&lt;/ul&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;import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{number}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; {
        setNumber(n =&amp;gt; n + 1);
        setNumber(n =&amp;gt; n + 1);
        setNumber(n =&amp;gt; n + 1);
      }}&amp;gt;+3&amp;lt;/button&amp;gt;
    &amp;lt;/&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;b&gt;n &amp;rArr; n + 1&lt;/b&gt; 는 업데이터 함수로 이 함수를 state 설정자 함수에 전달 할 때 React는 아래와 같이 동작합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;React는 이벤트 핸들러의 다른 코드가 모두 실행된 후 해당 함수가 처리되도록 큐에 넣는다.&lt;/li&gt;
&lt;li&gt;다음 렌더링 중에 React는 큐를 순회하며 최종으로 업데이트된 state를 제공&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  React가 이벤트 핸들러를 수행하는 동안 작동하는 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;setNumber(n =&amp;gt; n + 1)&lt;/code&gt; &amp;rarr; &lt;code&gt;n &amp;rArr; n + 1&lt;/code&gt; 함수를 큐에 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setNumber(n =&amp;gt; n + 1)&lt;/code&gt; &amp;rarr; &lt;code&gt;n &amp;rArr; n + 1&lt;/code&gt; 함수를 큐에 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setNumber(n =&amp;gt; n + 1)&lt;/code&gt; &amp;rarr; &lt;code&gt;n &amp;rArr; n + 1&lt;/code&gt; 함수를 큐에 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 중에 useState를 호출하면 &lt;b&gt;React는 큐를 순회&lt;/b&gt; 하고 첫 number의 값은 0이었으므로 첫 번째 업데이터 함수에 n의 인수로 0을 전달&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이후 React는 이전 업데이터 함수의 반환값을 가져와 다음 업데이터 함수의 n으로 전달하는 식으로 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 62.093%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%;&quot;&gt;queued update&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot;&gt;n&lt;/td&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%;&quot;&gt;n =&amp;gt; n + 1&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;0 + 1 = 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%;&quot;&gt;n =&amp;gt; n + 1&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;1 + 1 = 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%;&quot;&gt;n =&amp;gt; n + 1&lt;/td&gt;
&lt;td style=&quot;width: 16.3954%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;2 + 1 = 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  React는 3을 최종 결과로 저장한 후 &lt;code&gt;useState&lt;/code&gt;에서 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 업데이터 함수는 렌더링 중에 실행되기 때문에 &lt;b&gt;업데이터 함수는 순수해야 하며&lt;/b&gt; 결과만 &lt;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;업데이터 함수 내부에서 state를 변경하거나 다른 사이드 이팩트를 실행하려 하면 안된다!! ❌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ &lt;b&gt;다른 예시&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;button onClick={() =&amp;gt; {
  setNumber(number + 5);
  setNumber(n =&amp;gt; n + 1);
}}&amp;gt;

------
결과: 6&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;table style=&quot;border-collapse: collapse; width: 78.4884%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.4651%;&quot;&gt;queued update&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;n&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%;&quot;&gt;반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.4651%;&quot;&gt;5으로 수정&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.4651%;&quot;&gt;n =&amp;gt; n + 1&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%;&quot;&gt;5 + 1 = 6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 6을 최종 결과로 저장하고 &lt;b&gt;useState&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;/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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에서 상태 업데이트가 발생할 때마다 업데이트를 바로 적용하지 않고 업데이트 큐에 추가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&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;각 업데이트 객체는 &lt;code&gt;action(새 상태 값 또는 업데이터 함수)&lt;/code&gt;, &lt;code&gt;lane(우선순위)&lt;/code&gt; 등의 정보를 포함합니다.&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{number}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; {
        setNumber(number + 5);
        setNumber(n =&amp;gt; n + 1);
        setNumber(10);
      }}&amp;gt;Increase&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

--- 버튼 클릭 결과
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;  &lt;b&gt;React 동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;setNumber(number + 5)&lt;/code&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;setNumber(0 + 5)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;5로 바꾸기 를 큐에 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setNumber(n =&amp;gt; n + 1)&lt;/code&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;React는 이 함수를 큐에 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setNumber(10)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;10으로 바꾸기 를 큐에 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링하는 동안 React는 state 큐를 순회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 86.7442%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 34.7674%;&quot;&gt;queued update&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.6046%;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;n&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 28.2558%;&quot;&gt;반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 34.7674%;&quot;&gt;5로 수정&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.6046%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 28.2558%;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 34.7674%;&quot;&gt;n =&amp;gt; n + 1&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 23.6046%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 28.2558%;&quot;&gt;5 + 1 = 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 34.7674%;&quot;&gt;10으로 수정&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 23.6046%;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 28.2558%;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순회한 후 React는 10를 최종 결과로 저장하고 useState에서 반환&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;1️⃣ React는 이벤트 핸들러가 실행을 마친 후 state 업데이트를 처리 - batching&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 하나의 이벤트에서 일부 state를 여러 번 업데이트하려면 업데이터 함수를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/queueing-a-series-of-state-updates&quot;&gt;https://ko.react.dev/learn/queueing-a-series-of-state-updates&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time&quot;&gt;https://ko.react.dev/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/reactwg/react-18/discussions/21&quot;&gt;https://github.com/reactwg/react-18/discussions/21&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/React</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/188</guid>
      <comments>https://beomsic.tistory.com/entry/React%EC%9D%98-State-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%81%90#entry188comment</comments>
      <pubDate>Thu, 23 Oct 2025 14:13:33 +0900</pubDate>
    </item>
    <item>
      <title>UI 표현하기</title>
      <link>https://beomsic.tistory.com/entry/UI-%ED%91%9C%ED%98%84%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 는 사용자 인터페이스(UI)를 렌더링하기 위한 JS 라이브러리&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;React를 통해 작은 요소들을 &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;  Component&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 애플리케이션은 컴포넌트라고 불리는 독립된 UI 조각들로 이루어짐&lt;/p&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;React 컴포넌트는 마크업을 얹을 수 있는 JavaScript 함수&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;마크업, CSS, Javascript를 앱의 재사용 가능한 UI 요소인 사용자 정의 컴포넌트로 조합&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;↗️ 컴포넌트 import / export&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 가장 큰 장점은 재사용성이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트를 조합해 또 다른 컴포넌트를 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 이렇게 여러 번 중첩하면 다른 파일로 분리해야 하는 시점이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분리를 하여 파일을 더 찾기 쉽고 재사용하기 편리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;export&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;함수 컴포넌트를 export&lt;/li&gt;
&lt;li&gt;&lt;b&gt;default&lt;/b&gt; 또는 &lt;b&gt;named export&lt;/b&gt; 방식을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;function Profile() {
  return (
    &amp;lt;img
      src=&quot;test.jpg&quot;
      alt=&quot;test&quot;
    /&amp;gt;
  );
}

export default function Gallery() {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;Profile /&amp;gt;
      &amp;lt;Profile /&amp;gt;
      &amp;lt;Profile /&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}&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;default 방식으로 export&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;import&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;컴포넌트를 사용할 파일에서 import&lt;/li&gt;
&lt;li&gt;&lt;b&gt;default&lt;/b&gt; 또는 &lt;b&gt;named&lt;/b&gt; 로 import&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import Gallery from './Gallery.js';

export default function App() {
  return (
    &amp;lt;Gallery /&amp;gt;
  );
}&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;default 방식으로 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;✅ &lt;b&gt;한 파일에서 여러번 import, export 하는 방법&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;default 키워드 없이 export&lt;/li&gt;
&lt;li&gt;named export&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export function Profile() {}

----

import { Profile } from './Gallery.js';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  함수 정의&lt;/h3&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function Profile() { .. }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;주의 사항&lt;/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;React 컴포넌트는 일반적인 Javascript 함수이지만 이름은 대문자로 시작해야 한다.&lt;/li&gt;
&lt;li&gt;그렇지 않으면 작동하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마크업 추가&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;return &amp;lt;img src=&quot;test.jpg&quot; alt=&quot;test&quot; /&amp;gt;;&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;b&gt;&lt;img /&gt;&lt;/b&gt; &amp;lt;img /&amp;gt; 는 HTML 같아 보이지만 실제로는 JS이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;JSX&lt;/b&gt;&lt;/span&gt;라고 함&lt;/li&gt;
&lt;/ul&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;return (
  &amp;lt;div&amp;gt;
    &amp;lt;img src=&quot;test.jpg&quot; alt=&quot;test&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
);&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;마크업이 return 키워드와 같은 라인에 있지 않은 경우 괄호로 묶는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;b&gt;브라우저에 표시되는 내용&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;export default function Gallery() {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;Profile /&amp;gt;
      &amp;lt;Profile /&amp;gt;
      &amp;lt;Profile /&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}

----- ⬇️ 브라우저에 표시

&amp;lt;section&amp;gt;
  &amp;lt;img src=&quot;https://test.jpg&quot; alt=&quot;test&quot; /&amp;gt;
  &amp;lt;img src=&quot;https://test.jpg&quot; alt=&quot;test&quot; /&amp;gt;
  &amp;lt;img src=&quot;https://test.jpg&quot; alt=&quot;test&quot; /&amp;gt;
&amp;lt;/section&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;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;section&gt;&lt;/section&gt;
&lt;b&gt;&amp;lt;section&amp;gt;&lt;/b&gt; 은 소문자이므로 React는 HTML태그를 가리킨다고 이해&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;lt;Profile /&amp;gt; &lt;/b&gt;은 대문자 P로 시작하므로 React는 Profile이라는 컴포넌트를 사용하고자 한다고 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  JSX 로 마크업 작성하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSX와 HTML의 차이점&lt;/li&gt;
&lt;li&gt;JSX로 정보를 보여주는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 Javascript를 확장한 문법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JS 파일을 HTML과 비슷하게 작성할 수 있게 해준다.&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;  React에서 마크업과 렌더링 로직을 같이 사용하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 HTML에 내용, CSS에 디자인, JS로 로직을 작성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분리된 파일로 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹이 더욱 인터랙티브해지면서 로직이 내용을 결정하는 경우가 많아져 JavaScript가 HTML을 담당하게 됨&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React에서 렌더링 로직과 마크업이 같은 위치에 있게 된 이유&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;React 컴포넌트&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;React 가 브라우저에 마크업을 렌더링할 수 있는 JS 함수&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;JSX는 HTML과 비슷해보이지만 더 엄격하고 동적으로 표현할 수 있도록 해준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;↪️ HTML 을 JSX로 표현해보기&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Todos&amp;lt;/h1&amp;gt;
&amp;lt;img
  src=&quot;test.jpg&quot;
  class=&quot;photo&quot;
&amp;gt;
&amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;li1
    &amp;lt;li&amp;gt;li2
    &amp;lt;li&amp;gt;li3
&amp;lt;/ul&amp;gt;&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;위 코드를 구현하기 위해 알아야할 JSX 규칙이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  규칙 1 - 하나의 루트 엘리먼트로 반환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 컴포넌트에서 여러 엘리먼트를 반환하기 위해서는 하나의 부모 태그로 감싸야 한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Test() {
  return (
      &amp;lt;h1&amp;gt; hello &amp;lt;/h1&amp;gt;
      &amp;lt;img src=&quot;&quot; alt=&quot;&quot; className=&quot;photo&quot; /&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
  );
}
-----

❌ JSX expressions must have one parent element.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;function Test() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt; hello &amp;lt;/h1&amp;gt;
      &amp;lt;img src=&quot;&quot; alt=&quot;&quot; className=&quot;photo&quot; /&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&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;b&gt;Fragment : &amp;lt;&amp;gt; &amp;lt;/&amp;gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;왜 여러 JSX 태그를 하나로 감싸줘야 할까??&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;  JSX는 내부적으로 일반 JS 객체로 변환되기 때문에 하나의 배열로 감싸지 않은 하나의 함수에서는 두 개의 객체를 반환할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다른 태그나 Fragment로 감싸지 않으면 두 개의 JSX 태그를 반환할 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;실제 Babel 을 통해서 변환된 모습을 확인해보자&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from &quot;react/jsx-runtime&quot;;
function Test() {
  return /*#__PURE__*/_jsxs(_Fragment, {
    children: [/*#__PURE__*/_jsx(&quot;h1&quot;, {
      children: &quot; Hedy &quot;
    }), /*#__PURE__*/_jsx(&quot;img&quot;, {
      src: &quot;&quot;,
      alt: &quot;&quot;,
      className: &quot;photo&quot;
    }), /*#__PURE__*/_jsxs(&quot;ul&quot;, {
      children: [/*#__PURE__*/_jsx(&quot;li&quot;, {}), /*#__PURE__*/_jsx(&quot;li&quot;, {}), /*#__PURE__*/_jsx(&quot;li&quot;, {})]
    })]
  });
}&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;b&gt;여러 JSX를 children 배열로 감싸 Fragment로 반환&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  규칙 2 - 모든 태그는 닫아주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX에서는 태그를 명시적으로 닫아야한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;처럼 자체적으로 닫아주는 태그는 반드시 &lt;code&gt;&amp;lt;img /&amp;gt;&lt;/code&gt; 형태로 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  규칙 3 - camel Case&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 JS로 바뀌고 JSX에 작성된 어트리뷰트(속성)는 JS 객체의 key가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에 어트리뷰틀르 변수로 읽고 싶을 경우 제한이 존재&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JS 변수명에 대시를 포함하거나 class 와 같은 예약어를 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  따라서 React에서 HTML, SVG 어트리뷰트 대부분이 캐멀 케이스로 작성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stroke-width &amp;rArr; strokeWidth&lt;/li&gt;
&lt;li&gt;class &amp;rArr; className&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;문자열 어트리뷰트 &lt;code&gt;&amp;ldquo;&amp;rdquo;&lt;/code&gt; 로 전달 / 동적 - {}&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lsquo;&amp;rsquo;&lt;/code&gt; 나 &lt;code&gt;&amp;ldquo;&amp;rdquo;&lt;/code&gt; 로 문자열 어트리뷰트를 JSX에 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;ddd&quot; /&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;b&gt;{}&lt;/b&gt; 를 사용&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const image = &quot;&quot;;

&amp;lt;img src={image} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  {} 로 Javascript 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중괄호 사이에 JS를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default function TodoList() {
  const name = 'beomsic';
  return (
    &amp;lt;h1&amp;gt;{name}'s To Do List&amp;lt;/h1&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;↖️ {{ }} 으로 객체를 전달&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function TodoList() {
  return (
    &amp;lt;ul style={{
      backgroundColor: 'black',
      color: 'pink'
    }}&amp;gt;
      &amp;lt;li&amp;gt;Improve the videophone&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
}&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  컴포넌트에 props 전달하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트는 props를 이용해 서로 통신&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 컴포넌트는 props를 통해 정보를 자식 컴포넌트에게 전달&lt;/li&gt;
&lt;li&gt;객체, 배열, 함수를 포함한 모든 JS 값을 전달할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 자식 컴포넌트에 props 전달&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default function Profile() {
  return (
    &amp;lt;Avatar
      person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
      size={100}
    /&amp;gt;
  );
}&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;person 객체, size (숫자)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗ Avatar 컴포넌트 내에서 props를 읽을 수 있다.&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️⃣ 자식 컴포넌트 내부에서 props 읽기&lt;/h3&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;function Avatar({ person, size }) { // 구조 분해 할당
  // 전달된 props 사용 가능
}

--- 
function Avatar(props) {
   let person = props.person;
   let size = props.size; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;props에 기본값을 지정할 수도 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function Avatar({ person, size = 10 }) {
  // 전달된 props 사용 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  JSX spread 문법으로 props 전달&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 컴포넌트에서 자식 컴포넌트로 모든 props를 전달해야 할 경우&lt;/li&gt;
&lt;li&gt;props를 하나하나 설정을 해주게 된다면 반복되는 코드가 발생하게 될 것이다.&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;spread를 통해 해결&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;function Profile({ person, size, ....}) {
  return (
      &amp;lt;div className=&quot;card&quot;&amp;gt;
          &amp;lt;Avatar
            person={person}
            size={size}
            ...
          /&amp;gt;
        &amp;lt;/div&amp;gt;
    ) 
}

---- spread 사용
function Profile(props) {
  return (
      &amp;lt;div className=&quot;card&quot;&amp;gt;
          &amp;lt;Avatar {...props} /&amp;gt;
        &amp;lt;/div&amp;gt;
    ) 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 이런 spread 가 많이 사용된다는 것은 문제가 있는 것!!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트를 분리해야 함을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⬆️ 자식을 부모에게 JSX로 전달&lt;/h3&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;Card&amp;gt;
  &amp;lt;Avator /&amp;gt;
&amp;lt;/Card&amp;gt;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX 태그내에서 콘텐츠를 중첩하게 되면 부모 컴포넌트는 해당 콘텐츠를 children이라는 props로 받게된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    &amp;lt;div className=&quot;card&quot;&amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  );
}

export default function Profile() {
  return (
    &amp;lt;Card&amp;gt;
      &amp;lt;Avatar
        size={100}
        person={{
          name: 'beomsic',
        }}
      /&amp;gt;
    &amp;lt;/Card&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Card 컴포넌트는 Avatar 컴포넌트로 설정된 children prop를 받아서 이를 div 에 렌더링한다.&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;code&gt;if&lt;/code&gt;, &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;: 와 같은 자바스크립트 문법을 사용해 &lt;b&gt;조건부로&lt;/b&gt; JSX를 렌더링&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;조건에 따라 다른 JSX를 반환하는 방법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prop 값을 통해 다른 JSX를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;function Item({isPacked, name}) {
  if (isPacked) return &amp;lt;li&amp;gt; ✅ &amp;lt;/li&amp;gt;;
  return &amp;lt;li&amp;gt; ❌ &amp;lt;/li&amp;gt;;
}

export default function PackingList() {
  return (
      &amp;lt;&amp;gt;
          &amp;lt;Item isPacked={true} name=&quot;item1&quot; /&amp;gt;
          &amp;lt;Item isPacked={false} name=&quot;item2&quot; /&amp;gt;
      &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 삼항 조건 연산자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 조건 연산자를 사용&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;if (isPacked) {
  return &amp;lt;li className=&quot;item&quot;&amp;gt;{name} ✅&amp;lt;/li&amp;gt;;
}
return &amp;lt;li className=&quot;item&quot;&amp;gt;{name}&amp;lt;/li&amp;gt;;

--- 위 코드를 삼항 조건 연산자를 이용해서 간단히 수정
return (
  &amp;lt;li className=&quot;item&quot;&amp;gt; 
    {isPacked ? name + ' ✅' : name}
  &amp;lt;/li&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  논리 AND 연산자 (&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트에서는 조건이 참일 때 일부 &lt;b&gt;JSX를 렌더링 하거나 그렇지 않으면 아무것도 렌더링하지 않을 때&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;&amp;amp;&amp;amp; 을 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;return (
  &amp;lt;li className=&quot;item&quot;&amp;gt; 
    {name} {isPacked &amp;amp;&amp;amp; '✅'}
  &amp;lt;/li&amp;gt;
)&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;isPacked 가 true라면 체크 표시를 렌더링하고 그렇지 않으면 아무것도 렌더링을 하지 않는다는 의미&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;amp;&amp;amp; 왼쪽에는 boolean 값을 주어야 한다.&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JS 배열 메서드를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  배열을 데이터로 렌더링&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
  &amp;lt;li&amp;gt;Creola Katherine Johnson: mathematician&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Mario Jos&amp;eacute; Molina-Pasquel Henr&amp;iacute;quez: chemist&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Mohammad Abdus Salam: physicist&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Percy Lavon Julian: chemist&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Subrahmanyan Chandrasekhar: astrophysicist&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  다른 데이터들을 통해서 동일한 컴포넌트의 여러 인스턴스를 표시해야 한다면??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  자바스크립트 객체, 배열에 저장하고 map() filter() 메서드를 사용해 컴포넌트 리스트를 렌더링&lt;/p&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️⃣ &lt;b&gt;데이터를 배열로 표시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;sml&quot;&gt;&lt;code&gt;const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario Jos&amp;eacute; Molina-Pasquel Henr&amp;iacute;quez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];&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;2️⃣ &lt;b&gt;people 배열을 map()을 이용해서 JSX 노드 배열로 매핑 (listItems)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const listItems = people.map(person =&amp;gt; &amp;lt;li&amp;gt;{person}&amp;lt;/li&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;3️⃣ &lt;b&gt;ul 로 매핑한 컴포넌트의 listItems 반환&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;return &amp;lt;ul&amp;gt;{listItems}&amp;lt;/ul&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;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario Jos&amp;eacute; Molina-Pasquel Henr&amp;iacute;quez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

export default function List() {
  const listItems = people.map(person =&amp;gt;
    &amp;lt;li&amp;gt;{person}&amp;lt;/li&amp;gt;
  );
  return &amp;lt;ul&amp;gt;{listItems}&amp;lt;/ul&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;b&gt;Warning: Each child in a list should have a unique &amp;ldquo;key&amp;rdquo; prop.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 위와 같은 에러가 발생!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  필터링 하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;filter()&lt;/b&gt; 메서드를 이용&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const people = [{
  id: 0,
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
}, {
  id: 1,
  name: 'Mario Jos&amp;eacute; Molina-Pasquel Henr&amp;iacute;quez',
  profession: 'chemist',
}, {
  id: 2,
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
}, {
  id: 3,
  name: 'Percy Lavon Julian',
  profession: 'chemist',
}, {
  id: 4,
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
}];

const chemists = people.filter(person =&amp;gt;
  person.profession === 'chemist'
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Key를 사용해서 리스트 순서대로 유지&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ Warning: Each child in a list should have a unique &amp;ldquo;key&amp;rdquo; prop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 발생했던 에러를 해결해보자.&lt;/p&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: #ee2323;&quot;&gt;&lt;b&gt;key&lt;/b&gt;&lt;/span&gt;로 지정을 해주어야 한다! ⭐&lt;/p&gt;
&lt;aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt;map() 호출 내부 JSX Element에는 항상 key 값이 필요&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key를 통해 각 컴포넌트가 어떤 배열 항목에 해당하는지 React에게 알려주어 나중에 일치하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열이 정렬등 이동하거나 삽입되거나 삭제될 경우 중요&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key를 잘 사용하면 React가 어떤 일이 일어났는지 추론하고 DOM 트리를 업데이트하는데 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =&amp;gt;
    &amp;lt;li key={person.id}&amp;gt;
      ...
    &amp;lt;/li&amp;gt;
  );
  return &amp;lt;ul&amp;gt;{listItems}&amp;lt;/ul&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Key 값&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 항목마다 다른 key 값을 제공해주어야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 데이터 (데이터베이스의 key, ID)&lt;/li&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;/li&gt;
&lt;li&gt;crypto.randomUUID()&lt;/li&gt;
&lt;li&gt;uuid 패키지&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;h3 data-ke-size=&quot;size23&quot;&gt;☑️ Key 규칙&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;key는 형제간에 고유해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 배열의 JSX 노드에는 동일한 key로 해도 상관없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;key는 변경되어서는 안된다.
&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;렌더링중 key를 생성하면 안된다!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;컴포넌트를 엄격하게 순수함수로 작성하면 코드가 점차 커지더라도 예상밖의 동작이나 버그를 피할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; 순수 함수&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수가 호출되기전 존재했던 어떤 객체나 변수가 변경되지 않음&lt;/li&gt;
&lt;li&gt;같은 입력이 주어지면 같은 결과를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;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;React 컴포넌트에 같은 입력이 주어지면 같은 JSX를 반환한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 사이드 이펙트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 렌더링 과정은 순수해야하고 컴포넌트는 JSX만 반환해야 했으며 렌더링 이전에 존재했던 객체나 변수를 변경해서는 안된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;let guest = 0;

function Cup() {
  // 나쁜 지점: 이미 존재했던 변수를 변경하고 있습니다!
  guest = guest + 1;
  return &amp;lt;h2&amp;gt;Tea cup for guest #{guest}&amp;lt;/h2&amp;gt;;
}

export default function TeaSet() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Cup /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&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;컴포넌트의 밖에 선언된 Guest라는 변수를 읽고 수정&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;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Cup({ guest }) {
  return &amp;lt;h2&amp;gt;Tea cup for guest #{guest}&amp;lt;/h2&amp;gt;;
}

export default function TeaSet() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Cup guest={1} /&amp;gt;
      &amp;lt;Cup guest={3} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는 순수해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 렌더링 전에 존재했던 객체나 변수를 변경하지 않고 자신이 맡은 일을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 같은 입력이 들어온 경우 항상 같은 JSX 결과를 반환해야 한다.&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;  트리로 UI 이해하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React&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;React&lt;/b&gt;는 어떻게 앱의 컴포넌트 구조를 추적하나?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  트리로서의 UI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UI를 트리로 모델링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리는 component와 UI 사이의 관계 모델로 UI는 종종 트리 구조를 사용해 표현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/myam5/dJMb9Qk0bZt/Uoe0pguvNVIYE52geT3XN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/myam5/dJMb9Qk0bZt/Uoe0pguvNVIYE52geT3XN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/myam5/dJMb9Qk0bZt/Uoe0pguvNVIYE52geT3XN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmyam5%2FdJMb9Qk0bZt%2FUoe0pguvNVIYE52geT3XN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;192&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex: 브라우저는 HTML (DOM)과 CSS(CSSOM)를 모델링하기 위해 트리구조를 사용&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;React도 역시 컴포넌트 간의 관계를 관리하고 모델링하기 위해 트리 구조를 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ 렌더 트리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 주요 특징은 다른 컴포넌트의 컴포넌트를 구성하는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 중첩&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 중첩시 부모 컴포넌트와 자식 컴포넌트의 개념이 생기고 각 부모 컴포넌트는 다른 컴포넌트의 자식이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 관계를 React 앱을 렌더링 할 때 렌더 트리라고 알려진 트리로 모델링할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;FancyText title text=&quot;Get Inspired App&quot; /&amp;gt;
      &amp;lt;InspirationGenerator&amp;gt;
        &amp;lt;Copyright year={2004} /&amp;gt;
      &amp;lt;/InspirationGenerator&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s5x76/dJMb9Lxeiwc/2Qn9zGSKDkKOdkFSbGJd20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s5x76/dJMb9Lxeiwc/2Qn9zGSKDkKOdkFSbGJd20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s5x76/dJMb9Lxeiwc/2Qn9zGSKDkKOdkFSbGJd20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs5x76%2FdJMb9Lxeiwc%2F2Qn9zGSKDkKOdkFSbGJd20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;361&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;532&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;React 앱에서 최상위 컴포넌트와 리프 컴포넌트가 무엇인지 식별하는데 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  조건부 렌더링을 하면 Prop 값으로 인해 컴포넌트가 다른 자식 컴포넌트를 렌더링할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  모듈 의존성 트리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리로 모델링 할 수 있는 다른 관계는 앱의 모듈 의존성이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트를 분리한 후 로직을 별도의 파일로 분리하면 컴포넌트, 함수, 상수를 내보내는 JS 모듈을 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/describing-the-ui&quot;&gt;https://ko.react.dev/learn/describing-the-ui&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/React</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/187</guid>
      <comments>https://beomsic.tistory.com/entry/UI-%ED%91%9C%ED%98%84%ED%95%98%EA%B8%B0#entry187comment</comments>
      <pubDate>Thu, 23 Oct 2025 14:07:40 +0900</pubDate>
    </item>
    <item>
      <title>React 개요</title>
      <link>https://beomsic.tistory.com/entry/React-%EA%B0%9C%EC%9A%94</link>
      <description>&lt;h1&gt; &amp;zwj;  React는 무엇인가&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Javascript 라이브러리로 Github 10만개가 넘는 별점을 가진 인기 있는 라이브러리중 하나&lt;/li&gt;
&lt;li&gt;React는 프레임워크가 아니다 ❌&lt;/li&gt;
&lt;li&gt;Facebook이 만든 오픈소스 프로젝트&lt;/li&gt;
&lt;li&gt;프런트엔드에서 사용자 인터페이스(UI)를 구축하는데 사용&lt;/li&gt;
&lt;li&gt;MVC 애플리케이션(Model-View-Controller)의 &lt;b&gt;뷰 계층&lt;/b&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;React에서 가장 중요한 측면&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;사용자 정의 및 재사용 가능한&lt;/b&gt; &lt;b&gt;HTML 요소인 컴포넌트&lt;/b&gt;를 만들 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;state&lt;/code&gt; 와 &lt;code&gt;props&lt;/code&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;/p&gt;
&lt;h1&gt;⚙️ 설정 및 설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  React를 설정하는 2가지 방법&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;정적 HTML&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React 앱 만들기&lt;/b&gt;&lt;/li&gt;
&lt;/ol&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️⃣ 정적 HTML&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;jQuery&lt;/b&gt;와 같은 라이브러리를 사용한적 있다면 익숙하고 이해하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Webpack&lt;/b&gt;, &lt;b&gt;Babel&lt;/b&gt;, &lt;b&gt;Node.js&lt;/b&gt;에 익숙하지 않다면 시작하기 가장 쉬운 방법&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;기본 파일 - index.html&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;

    &amp;lt;title&amp;gt;Hello React!&amp;lt;/title&amp;gt;

    &amp;lt;script src=&quot;https://unpkg.com/react@^16/umd/react.production.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://unpkg.com/react-dom@16.13.0/umd/react-dom.production.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://unpkg.com/babel-standalone@6.26.0/babel.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;

  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script type=&quot;text/babel&quot;&amp;gt;
      // React code will go here
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;React, React DOM, Babel 세가지 &lt;b&gt;CDN&lt;/b&gt;을 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 72px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;React&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;React 최상위 API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;React DOM&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;DOM 관련 메서드 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Babel&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;오래된 브라우저에서 ES6+를 사용할 수 있게 해주는 Javascript 컴파일러&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;앱의 시작점은 root div&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;Babel을 사용하려면 필수적인 &lt;code&gt;text/babel&lt;/code&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;index.html&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class App extends React.Component {
  render() {
      return &amp;lt;h1&amp;gt;Hello world!&amp;lt;/h1&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;b&gt;render()&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;클래스 구성 요소에 필요한 유일한 메서드인 DOM 노드를 렌더링하는데 사용되는 메서드를 추가&lt;/li&gt;
&lt;li&gt;return 안에 간단한 HTML 요소처럼 보이는 것을 넣는다
&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;span style=&quot;color: #ee2323;&quot;&gt;JSX&lt;/span&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;  React DOM 메서드를 사용해 만든 APP 클래스를 HTML의 &lt;b&gt;root div&lt;/b&gt;에 &lt;b&gt;render()&lt;/b&gt;을 통해 렌더링&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById('root'))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✋ &lt;b&gt;최종 index.html&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;

    &amp;lt;title&amp;gt;Hello React!&amp;lt;/title&amp;gt;

    &amp;lt;script src=&quot;https://unpkg.com/react@16/umd/react.development.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://unpkg.com/react-dom@16/umd/react-dom.development.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;https://unpkg.com/babel-standalone@6.26.0/babel.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;

  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script type=&quot;text/babel&quot;&amp;gt;
      class App extends React.Component {
        render() {
          return &amp;lt;h1&amp;gt;Hello world!&amp;lt;/h1&amp;gt;
        }
      }

      ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById('root'))
    &amp;lt;/script&amp;gt;
  &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; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rQslJ/dJMb9VT39ur/uRrJrDONBRQWViOLSpRDu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rQslJ/dJMb9VT39ur/uRrJrDONBRQWViOLSpRDu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rQslJ/dJMb9VT39ur/uRrJrDONBRQWViOLSpRDu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrQslJ%2FdJMb9VT39ur%2FuRrJrDONBRQWViOLSpRDu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;274&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;470&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;2️⃣ React 앱 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법에서는 Javascript 라이브러리를 정적 HTML 페이지에 로드하고 React와 Babel을 바로 렌더링하는 것은 비효율적이고 유지관리가 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Facebook은 React 앱을 구축하기 위해 필요한 모든 것을 미리 구성한 환경인 &lt;code&gt;Create React App&lt;/code&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;웹팩을 이용해 React, JSX, ES6를 자동으로 컴파일한다.&lt;/li&gt;
&lt;li&gt;CSS 파일을 자동으로 프리픽스하고 ESLint를 사용해 코드의 오류를 테스트하고 경고한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;즉, 개발자가 코드를 바꿀 때마다 새로고침하지 않아도 되고 실시간으로 결과 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚙️ create-react-app 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 실행될 위치에서 하나의 디렉토리 위에 있는 터미널에서 다음 코드를 실행&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;&amp;amp; npx create-react-app react-tutorial

------
beomsic@-ui-MacBookPro react % npx create-react-app react-tutorial
Need to install the following packages:
create-react-app@5.1.0
Ok to proceed? (y) y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 새로 생성되는 디렉토리로 이동하여 프로젝트 시작&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;&amp;amp; cd react-tutorial &amp;amp;&amp;amp; npm start&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;새 React 앱과 함께 localhost:3000에 새 창이 나타남.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗ Create React App 은 초보자가 대규모 엔터프라이즈 애플리케이션을 시작하기에 매우 좋지만, 모든 워크 플로우에 완벽한 것은 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액트용 웹팩 설정을 직접 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/aside&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;react-tutorial/
├── node_modules/
├── public/
│   ├── index.html
│   └── manifest.json
├── src/
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   └── index.js
├── .gitignore
├── package-lock.json
├── package.json
└── README.md&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;/public&lt;/b&gt; 에서 중요한 파일은 &lt;b&gt;index.html&lt;/b&gt; 이다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;React App&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;noscript&amp;gt;You need to enable JavaScript to run this app.&amp;lt;/noscript&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;이전의 static의 index.html 파일과 유사
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 이번에는 라이브러리나 스크립트가 로드되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여기서 &lt;b&gt;&amp;lt;div id=&quot;root&quot;&amp;gt; &amp;lt;/div&amp;gt;&amp;nbsp;&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;ReactDOM&lt;/b&gt;이 이 div를 찾아서 우리가 만든 컴포넌트(App 등)를 여기에 그려줌&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;/src&lt;/b&gt; 디렉토리에는 모든 &lt;b&gt;REACT&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;우리가 작성하는 대부분의 자바스크립트, 컴포넌트, CSS 파일들이 여기에 존재&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;index.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { Component } from &quot;react&quot;;
import { createRoot } from &quot;react-dom/client&quot;;
// import ReactDOM from &quot;react-dom&quot;;
import &quot;./index.css&quot;;

class App extends Component {
  render() {
    return (
      &amp;lt;div className=&quot;App&quot;&amp;gt;
        &amp;lt;h1&amp;gt;Hello, React!&amp;lt;/h1&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

const container = document.getElementById(&quot;root&quot;);

if (container) {
  const root = createRoot(container);
  root.render(&amp;lt;App /&amp;gt;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;aside&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;ReactDOM.render is no longer supported in React 18&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;ReactDOM.render는 React 18에서 더 이상 지원되지 않는다.&lt;/li&gt;
&lt;li&gt;대신 createRoot를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/aside&gt;
&lt;h1&gt;  React Developer Tools&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi&quot;&gt;React 개발자 도구라&lt;/a&gt;는 확장 프로그램이 있어 React로 작업할 때 훨씬 유용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 DevTools를 열면 React 탭이 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  JSX: Javascript + XML&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  리액트 코드에서 HTML처럼 보이는 것을 사용하고 있지만 이건 HTML이 아닌 JavaScript XML의 약자인 &lt;b&gt;JSX&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX를 사용하면 HTML처럼 보이는 태그를 작성할 수 있고 XML과 유사한 태그를 직접 생성하여 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;JSX&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const heading = &amp;lt;h1 className=&quot;site-heading&quot;&amp;gt;Hello, React&amp;lt;/h1&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;React를 작성할 때 &lt;b&gt;JSX&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;태그, 속성을 포함하는 객체, 구성 요소의 자식 등을 가져와 렌더링하는 createElement 을 사용할 수 있다.&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;JSX를 사용하지 않은 경우&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;아래 코드는 위의 JSX와 동일한 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const heading = React.createElement('h1', { className: 'site-heading' }, 'Hello, React!')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  JSX 특징&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 HTML이 아닌 Javascript에 더 가깝기 때문에 아래 차이점을 주목해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ class 는 &lt;b&gt;Javascript에서 예약된 키워드&lt;/b&gt;이기 때문에 CSS 클래스를 추가할 때 class 대신 className이 사용된다.&lt;/p&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;2️⃣ JSX의 프로퍼티와 메서드는 camelCase 이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex: HTML의 onclick은 JSX에서 onClick&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;3️⃣ &lt;b&gt;자체 닫는 태그(Self-closing tags)&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;ex: &lt;img /&gt;&lt;/li&gt;
&lt;li&gt;HTML에서는 &lt;br /&gt;이나 &lt;img /&gt;가 허용되지만 JSX에서는 반드시 &lt;br /&gt;또는 &lt;img /&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;4️⃣ JavaScript 표현식은 중괄호({}) 안에 포함&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSX 내부에 JavaScript &lt;b&gt;변수, 함수 호출, 객체 속성&lt;/b&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;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const name = 'Tania'
const heading = &amp;lt;h1&amp;gt;Hello, {name}&amp;lt;/h1&amp;gt;&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;JSX는 정적인 마크업과 동적인 JavaScript 로직을 분리하는 경계를 제공&lt;/li&gt;
&lt;li&gt;중괄호는 &quot;지금부터는 JavaScript 코드를 실행해야 한다&quot;는 것을 React에게 알려주는 역할&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;  JSX는 바닐라 JavaScript에서 많은 요소를 생성하고 추가하는 것보다 작성하고 이해하기 쉽다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람들이 React를 매우 좋아하는 이유 중 하나&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Components&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 거의 모든 Component는 &lt;b&gt;클래스&lt;/b&gt; Component일 수도 있고 &lt;b&gt;간단한&lt;/b&gt; Component일 수도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 React 앱에는 작은 구성 component들이 많고 모든 것이 메인 App component에 로드된다.&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;index.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { Component } from &quot;react&quot;;
import { createRoot } from &quot;react-dom/client&quot;;
import App from &quot;./App&quot;;
import &quot;./index.css&quot;;

const container = document.getElementById(&quot;root&quot;);

if (container) {
  const root = createRoot(container);
  root.render(&amp;lt;App /&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;b&gt;App.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import React, { Component } from 'react'

class App extends Component {
  render() {
    return (
      &amp;lt;div className=&quot;App&quot;&amp;gt;
        &amp;lt;h1&amp;gt;Hello, React!&amp;lt;/h1&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;App component를 export 하여 내보내고 index.js에 로드&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;component를 따로 파일로 분리하는 것은 필수는 아니지만 애플리케이션을 유지보수하고 다루기 쉽기하기 위해서 분리하는게 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Class Components&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;src/Table.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { Component } from 'react'

class Table extends Component {
  render() {
    return (
      &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Job&amp;lt;/th&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;Charlie&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;Janitor&amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;Mac&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;Bouncer&amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;Dee&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;Aspiring actress&amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;Dennis&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;Bartender&amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
    )
  }
}

export default Table&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;커스텀 클래스 컴포넌트를 대문자로 사용해 일반 HTML 요소와 차별화&lt;/li&gt;
&lt;li&gt;App.js 에서 이 Table을 가져와 로드할 수 있다.&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;src/App.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import React, { Component } from 'react'
import Table from './Table'

class App extends Component {
  render() {
    return (
      &amp;lt;div className=&quot;container&quot;&amp;gt;
        &amp;lt;Table /&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sx8Ym/dJMb9c2AP4K/sFaXwE1qMwN2fwNJfO6lw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sx8Ym/dJMb9c2AP4K/sFaXwE1qMwN2fwNJfO6lw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sx8Ym/dJMb9c2AP4K/sFaXwE1qMwN2fwNJfO6lw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsx8Ym%2FdJMb9c2AP4K%2FsFaXwE1qMwN2fwNJfO6lw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;410&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Simple Components&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 다른 유형의 구성요소는 &lt;b&gt;함수&lt;/b&gt;와 같은 simple component 이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 컴포넌트는 class 키워드를 사용하지 않는다.&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;src/Table.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Component } from &quot;react&quot;;

const TableHeader = () =&amp;gt; {
  return (
    &amp;lt;thead&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Job&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
  );
};

const TableBody = () =&amp;gt; {
  return (
    &amp;lt;tbody&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;Charlie&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Janitor&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;Mac&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Bouncer&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;Dee&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Aspiring actress&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;Dennis&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;Bartender&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
  );
};

class Table extends Component {
  render() {
    return (
      &amp;lt;table&amp;gt;
        &amp;lt;TableHeader /&amp;gt;
        &amp;lt;TableBody /&amp;gt;
      &amp;lt;/table&amp;gt;
    );
  }
}

export default Table;&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;(&amp;lt;TableHeader /&amp;gt;, &amp;lt;TableBody /&amp;gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;함수 컴포넌트와 클래스 컴포넌트를 섞어서 쓸 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  simple(함수) 컴포넌트 vs 클래스 컴포넌트&lt;/h2&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;문법이 간단하고 읽기 쉬움.&lt;/li&gt;
&lt;/ul&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;const SimpleComponent = () =&amp;gt; {
  return &amp;lt;div&amp;gt;Example&amp;lt;/div&amp;gt;;
}

// or
const SimpleComponent = () =&amp;gt; &amp;lt;div&amp;gt;Example&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;클래스 컴포넌트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Component를 상속하고&lt;/li&gt;
&lt;li&gt;render() 메서드를 반드시 가짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import React, { Component } from 'react';

class ClassComponent extends Component {
  render() {
    return &amp;lt;div&amp;gt;Example&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;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;클래스는 render() 필요, 라이프사이클 메서드 사용 가능&lt;/li&gt;
&lt;li&gt;함수 컴포넌트는 간결, React Hooks(useState, useEffect)를 쓰면 상태/라이프사이클도 다룰 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Props&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Table 컴포넌트를 보면 데이터가 하드코딩이 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React의 가장 큰 장점중 하나는 데이터를 처리하는 방식이다.&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;props&lt;/li&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 props 을 통해서 데이터를 다루는 방법에 대해서 확인을 해보자.&lt;/p&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;src/App.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class App extends Component {
  render() {
    const characters = [
      {
        name: &quot;Charlie&quot;,
        job: &quot;Janitor&quot;,
      },
      {
        name: &quot;Mac&quot;,
        job: &quot;Bouncer&quot;,
      },
      {
        name: &quot;Dee&quot;,
        job: &quot;Aspring actress&quot;,
      },
      {
        name: &quot;Dennis&quot;,
        job: &quot;Bartender&quot;,
      },
    ];

    return (
      &amp;lt;div className=&quot;container&quot;&amp;gt;
        &amp;lt;Table characterData={characters} /&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;src/Table.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Component } from &quot;react&quot;;

const TableHeader = () =&amp;gt; {
  return (
    &amp;lt;thead&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Job&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
  );
};

const TableBody = (props) =&amp;gt; {
  const rows = props.characterData.map((row, index) =&amp;gt; {
    return (
      &amp;lt;tr key={index}&amp;gt;
        &amp;lt;td&amp;gt;{row.name}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{row.job}&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    );
  });

  return &amp;lt;tbody&amp;gt;{rows}&amp;lt;/tbody&amp;gt;;
};

class Table extends Component {
  render() {
    const { characterData } = this.props;

    return (
      &amp;lt;table&amp;gt;
        &amp;lt;TableHeader /&amp;gt;
        &amp;lt;TableBody characterData={characterData} /&amp;gt;
      &amp;lt;/table&amp;gt;
    );
  }
}

export default Table;
&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;li&gt;전달 시 JSX에서 속성처럼 사용&lt;/li&gt;
&lt;li&gt;자식은 this.props(클래스) 또는 함수 파라미터 props(함수 컴포넌트)로 접근&lt;/li&gt;
&lt;li&gt;&lt;b&gt;읽기 전용(read-only)&lt;/b&gt;: 자식은 전달받은 props를 직접 수정하면 안 됨. (데이터를 바꾸려면 부모가 함수 형태로 조작하거나 컴포넌트의 state 사용)&lt;/li&gt;
&lt;li&gt;테이블 행에 key 인덱스를 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React에서 목록을 만들 때에는 항상 key를 사용해야 한다.&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;h3 data-ke-size=&quot;size23&quot;&gt;  가상 DOM(Virtual DOM)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React는 실제 DOM과 별개로 가볍고 효율적인 &lt;b&gt;메모리 상의 트리(가상 DOM)&lt;/b&gt; 를 유지.&lt;/li&gt;
&lt;li&gt;상태나 props 변경 시 가상 DOM에서 변경된 부분만 계산(diff)하고 실제 DOM에 최소한의 업데이트만 적용 &amp;rarr; 성능 향상.&lt;/li&gt;
&lt;li&gt;React DevTools로 컴포넌트 트리와 &lt;b&gt;props/state&lt;/b&gt;를 확인할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;✅ State&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 문자 data를 배열에 저장하여 props로 전달해주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 배열에서 아이템을 삭제하려면 어떻게 해야할까 ❓&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt;props&lt;/b&gt; 를 사용하면 &lt;b&gt;one way data flow&lt;/b&gt;가 가능하지만 &lt;b&gt;state&lt;/b&gt;를 사용하면 component의 private한 데이터를 업데이트할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex: 구매를 확인하기 전에 장바구니에서 항목을 추가하거나 제거하는 등 데이터베이스에 추가하지 않고도 저장하고 수정해야 하는 모든 데이터를 상태라고 생각할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  상태 객체 만들기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체에는 상태에 저장하려는 모든 항목에 대한 속성이 포함&lt;/li&gt;
&lt;/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;src/App.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class App extends Component {
  state = {
    characters: [
      {
        name: &quot;Charlie&quot;,
        job: &quot;Janitor&quot;,
      },
      {
        name: &quot;Mac&quot;,
        job: &quot;Bouncer&quot;,
      },
      {
        name: &quot;Dee&quot;,
        job: &quot;Aspring actress&quot;,
      },
      {
        name: &quot;Dennis&quot;,
        job: &quot;Bartender&quot;,
      },
    ]
  }
}&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;이전에 만들었던 캐릭터 배열을 state.characters 로 이동&lt;/li&gt;
&lt;li&gt;데이터는 이제 state에 포함&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;❌ 테이블에서 character 를 제거하는 것을 원하기 때문에 App class에 character를 제거하는 함수를 만들어서 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이때, 상태를 검색하기 위해서는 ES6 메서드 방식을 이용하여 state.characters 를 가져온다.&lt;/p&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;this.setState()&lt;/b&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;removeCharacter = (index) =&amp;gt; {
  const { characters } = this.state

  this.setState({
    characters: characters.filter((character, i) =&amp;gt; {
      return i !== index
    }),
  })
}&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;b&gt;filter&lt;/b&gt; 는 변경하지 않고 새로운 배열을 만드는 방법으로 &lt;b&gt;javascript&lt;/b&gt;에서 배열을 수정하는데 선호된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열의 모든 인덱스를 비교해 테스트하고 통과된 것을 제외한 모든 것들을 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/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;removeCharacter 함수를 component에 전달&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;prop 로 Table에 전달&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;src/App.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { Component } from &quot;react&quot;;
import Table from &quot;./Table&quot;;

class App extends Component {
  state = {
    characters: [
      {
        name: &quot;Charlie&quot;,
        job: &quot;Janitor&quot;,
      },
      {
        name: &quot;Mac&quot;,
        job: &quot;Bouncer&quot;,
      },
      {
        name: &quot;Dee&quot;,
        job: &quot;Aspring actress&quot;,
      },
      {
        name: &quot;Dennis&quot;,
        job: &quot;Bartender&quot;,
      },
    ],
  };

  removeCharacter = (index) =&amp;gt; {
    const { characters } = this.state;

    this.setState({
      characters: characters.filter((character, i) =&amp;gt; {
        return i !== index;
      }),
    });
  };

  render() {
    const { characters } = this.state;

    return (
      &amp;lt;div className=&quot;container&quot;&amp;gt;
        &amp;lt;Table
          characterData={characters}
          removeCharacter={this.removeCharacter}
        /&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;src/Table.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const TableHeader = () =&amp;gt; {
  return (
    &amp;lt;thead&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Job&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
  );
};

const TableBody = (props) =&amp;gt; {
  const rows = props.characterData.map((row, index) =&amp;gt; {
    return (
      &amp;lt;tr key={index}&amp;gt;
        &amp;lt;td&amp;gt;{row.name}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{row.job}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; props.removeCharacter(index)}&amp;gt;Delete&amp;lt;/button&amp;gt;
        &amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    );
  });

  return &amp;lt;tbody&amp;gt;{rows}&amp;lt;/tbody&amp;gt;;
};

const Table = (props) =&amp;gt; {
  const { characterData, removeCharacter } = props;

  return (
    &amp;lt;table&amp;gt;
      &amp;lt;TableHeader /&amp;gt;
      &amp;lt;TableBody
        characterData={characterData}
        removeCharacter={removeCharacter}
      /&amp;gt;
    &amp;lt;/table&amp;gt;
  );
};

export default Table;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mcrOn/dJMb9PfjZZ7/Bh8BklkUZ6JGQmQU35RkgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mcrOn/dJMb9PfjZZ7/Bh8BklkUZ6JGQmQU35RkgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mcrOn/dJMb9PfjZZ7/Bh8BklkUZ6JGQmQU35RkgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmcrOn%2FdJMb9PfjZZ7%2FBh8BklkUZ6JGQmQU35RkgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;377&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;698&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;  props와 state의 차이&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;props&lt;/td&gt;
&lt;td&gt;state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정의&lt;/td&gt;
&lt;td&gt;부모 &amp;rarr; 자식에게 전달되는 &lt;b&gt;읽기 전용 데이터&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컴포넌트 내부에서 관리하는 &lt;b&gt;변경 가능한 데이터&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;변경 가능 여부&lt;/td&gt;
&lt;td&gt;❌ (읽기 전용, 변경 불가)&lt;/td&gt;
&lt;td&gt;✅ this.setState() 또는 useState()로 변경 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;누가 관리?&lt;/td&gt;
&lt;td&gt;부모 컴포넌트&lt;/td&gt;
&lt;td&gt;자신(해당 컴포넌트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;외부에서 전달된 값 표시&lt;/td&gt;
&lt;td&gt;내부 동작이나 사용자 입력 결과 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;↗️ &lt;b&gt;Submitting Form Data&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 state 에 저장해놓은 상태에서 이제 해당 데이터들을 제거할 수 있게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 데이터를 state에 추가하기 위해서는 어떻게 해야할까?&lt;/p&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;state.characters&lt;/b&gt; 에 하드코딩으로 저장한 데이터를 삭제한 후 Form 을 활용해서 데이터를 추가해보자.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class App extends Component {
  state = {
    characters: [],
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Form 컴포넌트 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;src/Form.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import React, { Component } from 'react'

class Form extends Component {
  initialState = {
    name: '',
    job: '',
  }

  state = this.initialState
}&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;Form의 초기 상태를 빈 속성을 가진 객체로 설정한 후 해당 객체를 this.state 으로 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이전에는 React class 컴포넌트에 &lt;b&gt;생성자(constructor())&lt;/b&gt; 가 필수였지만 지금은 더 이상 필요하지 않다.&lt;/p&gt;
&lt;/aside&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;해당 Form의 목표&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;form에서 필드가 변경될 때마다 form의 상태를 업데이트&lt;/li&gt;
&lt;li&gt;form 이 제출이되면 데이터가 App의 state로 전달되어 Table이 업데이트&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;1️⃣ 입력에서 변경이 생길때 실행되는 함수&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트가 전달되어 입력의 이름(key)와 값을 통해 Form 상태을 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;handleChange = (event) =&amp;gt; {
  const { name, value } = event.target

  this.setState({
    [name]: value,
  })
}&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;pre class=&quot;dust&quot;&gt;&lt;code&gt;render() {
  const { name, job } = this.state;

  return (
    &amp;lt;form&amp;gt;
      &amp;lt;label htmlFor=&quot;name&quot;&amp;gt;Name&amp;lt;/label&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        name=&quot;name&quot;
        id=&quot;name&quot;
        value={name}
        onChange={this.handleChange} /&amp;gt;
      &amp;lt;label htmlFor=&quot;job&quot;&amp;gt;Job&amp;lt;/label&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        name=&quot;job&quot;
        id=&quot;job&quot;
        value={job}
        onChange={this.handleChange} /&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default Form;&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;state에서 필요한 두 속성(name, job)을 가져와 각 속성에 맞는 값으로 할당하도록 한다.&lt;/li&gt;
&lt;li&gt;input의 onChange에 handleChange 메서드를 추가해 값이 변경될 때 값으로 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9B6ia/dJMb9MXcwMx/eBuWHxT1Akj4wV231injt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9B6ia/dJMb9MXcwMx/eBuWHxT1Akj4wV231injt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9B6ia/dJMb9MXcwMx/eBuWHxT1Akj4wV231injt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9B6ia%2FdJMb9MXcwMx%2FeBuWHxT1Akj4wV231injt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;504&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력한 값으로 state가 변경되는 것을 확인해볼 수 있다.&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;2️⃣ Form을 제출하고 Table 데이터 업데이트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt; src/App.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;handleSubmit = (character) =&amp;gt; {
  this.setState({ characters: [...this.state.characters, character] })
}&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;App의 this.state.characters 를 가져와 새로운 character 를 추가해 state를 업데이트 하는 함수 추가&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;위에서 만든 제출 메서드를 Form의 매개변수로 전달&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;Form handleSubmit={this.handleSubmit} /&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;b&gt;src/Form.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;submitForm = () =&amp;gt; {
  this.props.handleSubmit(this.state)
  this.setState(this.initialState)
}&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;Form에서 전달받은 메서드를 호출하는 메서드를 또 만들고 handleSubmit의 character 매개변수로 form의 state를 전달&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;src/Form.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;button&quot; value=&quot;Submit&quot; onClick={this.submitForm} /&amp;gt;&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;제출 버튼을 만들어 Form을 제출&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Pulling in API Data&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 일반적인 사용법중 하나는 API를 통해 데이터를 가져오는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;위키피디아 API를 이용해서 데이터 가져오는 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import React, { Component } from 'react'

class App extends Component {
  state = {
    data: [],
  }

  componentDidMount() {
    const url =
      'https://en.wikipedia.org/w/api.php?action=opensearch&amp;amp;search=Seona+Dancing&amp;amp;format=json&amp;amp;origin=*'

    fetch(url)
      .then((result) =&amp;gt; result.json())
      .then((result) =&amp;gt; {
        this.setState({
          data: result,
        })
      })
  }

  render() {
    const { data } = this.state

    const result = data.map((entry, index) =&amp;gt; {
      return &amp;lt;li key={index}&amp;gt;{entry}&amp;lt;/li&amp;gt;
    })

    return &amp;lt;ul&amp;gt;{result}&amp;lt;/ul&amp;gt;
  }
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;componentDidMount&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;React 생명주기 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  리액트 생명주기(lifecycle)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생명주기(Lifecycle) &amp;ldquo;컴포넌트가 생성 - 화면에 렌더링 - 업데이트 - 사라질 때까지의 과정&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 이 생명주기의 각 시점마다 &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;b&gt;라이프사이클 메서드(Lifecycle Methods)&lt;/b&gt; 라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  컴포넌트의 생명주기 단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 클래스형 컴포넌트는 보통 세 가지 주요 단계로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 147px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;단계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;대표 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;&lt;b&gt;Mounting&lt;/b&gt; (마운트)&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;컴포넌트가 &lt;b&gt;처음 생성되어 DOM에 추가&lt;/b&gt;되는 시점&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;constructor() &amp;rarr; render() &amp;rarr; componentDidMount()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;Updating&lt;/b&gt; (업데이트)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;props나 state가 변경되어 &lt;b&gt;화면이 다시 렌더링&lt;/b&gt;되는 시점&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;render() &amp;rarr; componentDidUpdate()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;Unmounting&lt;/b&gt; (언마운트)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;컴포넌트가 &lt;b&gt;DOM에서 제거&lt;/b&gt;되는 시점&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;componentWillUnmount()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  componentDidMount()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;componentDidMount()는 컴포넌트가 &lt;b&gt;화면에 처음 렌더링된 직후&lt;/b&gt;에 자동으로 호출되는 메서드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;컴포넌트가 실제로 DOM에 붙은 다음 실행되는 메서드&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;중요한 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 요청이나 외부 데이터 fetch, 타이머나 이벤트 등록&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;DOM이 실제로 존재해야 가능&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;따라서 React에서는 &lt;b&gt;데이터를 불러올 때는 무조건 componentDidMount() 안에서 처리&lt;/b&gt;하는 것이 원칙&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.taniarascia.com/getting-started-with-react/&quot;&gt;https://www.taniarascia.com/getting-started-with-react/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/docs/state-and-lifecycle.html&quot;&gt;https://ko.legacy.reactjs.org/docs/state-and-lifecycle.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/React</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/186</guid>
      <comments>https://beomsic.tistory.com/entry/React-%EA%B0%9C%EC%9A%94#entry186comment</comments>
      <pubDate>Thu, 23 Oct 2025 13:53:37 +0900</pubDate>
    </item>
    <item>
      <title>Cookie 와 Session 그리고 토큰</title>
      <link>https://beomsic.tistory.com/entry/Cookie-%EC%99%80-Session-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%86%A0%ED%81%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  HTTP 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 &lt;b&gt;무상태(Stateless)&lt;/b&gt; 프로토콜입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 요청이 독립적으로 처리되며 서버가 클라이언트의 이전 상태를 기억하지 않는다는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 로그인 상태와 같이 사용자의 연속적인 상태 정보를 유지하기 위해 쿠키와 세션 같은 기술이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 Auth 기능을 구현할 때 쿠키, 세션, 토큰, JWT 등의 용어가 있는데 이를 예전에 학습을 했지만 다시 정리를 해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  쿠키&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키를 이용하면 서버는 브라우저에 데이터를 넣을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 서버에 요청을 보내고 서버에서는 브라우저에게 응답을 보냅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 이 응답에 브라우저에 저장하고자 하는 쿠키가 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  브라우저에 쿠키를 저장하면 웹사이트에 접속을 할 때마다 브라우저는 해당 쿠키도 요청과 함께 보내게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;도메인 제한&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 도메인에서 발급한 쿠키는 그 도메인에만 전송됩니다.&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;youtube.com&lt;/code&gt; 이 발급한 쿠키는 &lt;code&gt;youtube.com&lt;/code&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;2️⃣ &lt;b&gt;유효기간이 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에서 쿠키의 만료 시간을 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;만료 기간이 지나면 브라우저가 자동으로 쿠키를 삭제합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &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;ex) 웹사이트 언어 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 쿠키를 주고 선택한 언어를 저장&lt;/li&gt;
&lt;li&gt;브라우저가 다시 웹사이트에 방문할 경우 쿠키는 요청과 함께 서버로 보내져 쿠키가 저장하고 있는 언어 설정의 페이지를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/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; &amp;zwj;♂️ 동작 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저 &amp;rarr; 서버 &lt;b&gt;요청&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;서버 &amp;rarr; 브라우저 &lt;b&gt;응답 (+ Set-Cookie 헤더)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;브라우저가 쿠키를 로컬에 저장&lt;/li&gt;
&lt;li&gt;이후 같은 도메인에 요청 시 자동으로 쿠키를 포함하여 서버로 전송&lt;/li&gt;
&lt;/ol&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 편의를 위하고 가로채이거나 지워지더라도 큰 문제가 없을 정보를 브라우저에 저장&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;로그인창의 아이디를 자동 완성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;웹 페이지의 공식 메시지 하루 안 보기 선택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;로그인하지 않은 상태로 장바구니에 물건을 담기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  세션&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 기본적으로 &lt;code&gt;Stateless&lt;/code&gt; 방식으로 동작합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 끝나면 서버는 요청을 보낸 주체를 잊게 됩니다.&lt;/li&gt;
&lt;li&gt;따라서 요청을 할 때마다 우리가 누군지 알려주어야 합니다.&lt;/li&gt;
&lt;li&gt;이를 처리하는 방법중 하나가 세션!!&lt;/li&gt;
&lt;/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; &amp;zwj;♂️ 동작 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yCL5I/btsQQXD3dQc/greS3vCqdWOObhGmvMXJe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yCL5I/btsQQXD3dQc/greS3vCqdWOObhGmvMXJe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yCL5I/btsQQXD3dQc/greS3vCqdWOObhGmvMXJe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyCL5I%2FbtsQQXD3dQc%2FgreS3vCqdWOObhGmvMXJe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;485&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 (이메일, 패스워드)를 통해 서버에 로그인 요청&lt;/li&gt;
&lt;li&gt;비밀번호가 맞다면 서버는 세션 DB에 해당 유저를 생성&lt;/li&gt;
&lt;li&gt;해당 세션에는 별도의 ID가 있어 해당 세션 ID는 쿠키를 통해 브라우저로 돌아와 저장&lt;/li&gt;
&lt;li&gt;브라우저는 세션 ID를 가지고 있는 쿠키를 서버에게 보냅니다&lt;/li&gt;
&lt;li&gt;서버는 쿠키를 보고 세션 ID를 가지고 세션 DB를 확인하여 유저의 정보를 가져옵니다.&lt;/li&gt;
&lt;/ol&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 단순히 &lt;b&gt;세션 ID&lt;/b&gt;만 가지고 있음&lt;/li&gt;
&lt;li&gt;실제 사용자 정보는 서버의 세션 저장소(DB, Redis 등)에 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키는 단순히 세션 ID를 전달하기 위한 매개체일 뿐!&lt;/p&gt;
&lt;/blockquote&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;세션은 현재 로그인한 유저들의 모든 세션 ID를 DB에 저장&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;모든 요청이 들어올때마다 서버는 쿠키를 받아 세션 ID를 보고 이를 통해 세션 ID와 일치하는 유저를 찾고 그 다음 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;즉, 유저가 늘어남에 따라 DB의 리소스가 많이 필요&lt;/b&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;&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;Redis 같은 외부 세션 저장소 필요&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;세션의 특징인 서버가 로그인 된 유저의 모든 정보를 저장하는 것을 이용하면 아래와 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;특정 유저를 강제로 로그아웃 시키고 싶을 때&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그냥 해당 유저의 세션을 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;인스타그램&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 로그인된 모든 디바이스를 확인&lt;/li&gt;
&lt;li&gt;원하지 않는 디바이스 로그아웃 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;넷플릭스 계정 공유 숫자 제한&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 로그인한 유저의 수를 확인할 수 있고 이를 제한하는 기능을 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  토큰 (Token)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 단순히 식별을 위한 긴 문자열입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션과 비슷하지만 브라우저에 쿠키 대신 &lt;b&gt;토큰 자체를 저장&lt;/b&gt;하고 요청 시 헤더에 넣어 보냅니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Authorization: Bearer &amp;lt;token-value&amp;gt;&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;네이티브 앱(iOS, Android)은 쿠키 대신 토큰을 사용&lt;/li&gt;
&lt;li&gt;브라우저 외의 환경에서도 동일한 인증 방식을 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  JWT&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT를 이용한다면 세션 DB를 사용할 필요도 없고 서버는 유저를 인증한다고 많은 처리를 할 필요도 없습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ 동작 방식&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdHyFH/btsQM79O1pT/NSti4PbCENTLVxfZJv6Nw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdHyFH/btsQM79O1pT/NSti4PbCENTLVxfZJv6Nw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdHyFH/btsQM79O1pT/NSti4PbCENTLVxfZJv6Nw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdHyFH%2FbtsQM79O1pT%2FNSti4PbCENTLVxfZJv6Nw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;511&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 (이메일, 패스워드)를 통해 서버에 로그인 요청&lt;/li&gt;
&lt;li&gt;비밀번호가 맞다면 서버는 유저의 ID(예시)를 가져다가 사인 알고리즘을 이용해 사인&lt;/li&gt;
&lt;li&gt;토큰(사인된 정보) 를 string 형태로 반환&lt;/li&gt;
&lt;li&gt;서버에 요청을 보낼 때 토큰를 서버에 보내줍니다.&lt;/li&gt;
&lt;li&gt;서버는 토큰을 받으면 &lt;b&gt;사인이 유효한지 체크&lt;/b&gt;하고 유효하다면 서버는 우리를 유저로 인증합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;1️⃣ &lt;b&gt;서버는 DB에 무언가를 저장하지 않습니다.&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;원하는 정보(예: userId)를 사인하고 전달하는 것이 전부&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;2️⃣ &lt;b&gt;JWT는 세션 ID보다 훨씬 긴 문자열입니다.&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;쿠키는 길이에 제약이 있지만 JWT는 제약이 없어 엄청 길어도 가능!&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;3️⃣ &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;세션의 경우 세션 DB에 저장&lt;/li&gt;
&lt;li&gt;따라서 JWT을 사용하면 서버는 DB를 거칠 필요 없이 토큰이 유효한지만 검증&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;  JWT는 암호화되지 않았습니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  만약 암호화가 되었다면?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아무도 읽을 수 없고 이해할 수 없게 됩니다.&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;JWT&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;를 JWT안에 두면 안된다!! ❌&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;  JWT 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 &lt;code&gt;.&lt;/code&gt; 을 기준으로 세 파트로 나뉩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FQ5wL/btsQQh4cl3Q/WsmXmACTHxuiOwub9sKf7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FQ5wL/btsQQh4cl3Q/WsmXmACTHxuiOwub9sKf7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FQ5wL/btsQQh4cl3Q/WsmXmACTHxuiOwub9sKf7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFQ5wL%2FbtsQQh4cl3Q%2FWsmXmACTHxuiOwub9sKf7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;376&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Payload&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Claim&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;ex) 토큰이 언제까지 유효한지?, 사용자의 닉네임, 관리자 여부 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이런 정보에 접근하고 조작하면 어떻게 되지?!?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효기간을 증가시키는 등의 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이런 문제를 해결하기 위해 헤더와 서명 부분이 존재!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Header&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
     &quot;alg&quot;: &quot;서명 시 사용하는 알고리즘&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}&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;type 값으로는 항상 JWT가 들어있습니다.&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;alg (알고리즘)&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;HS256 등 여러 암호화 방식중 하나를 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;헤더&lt;/b&gt;와 &lt;b&gt;페이로드&lt;/b&gt; 그리고 서버에서 사용하는 &lt;code&gt;secret key&lt;/code&gt; 이 3가지를 이용해 암호화 알고리즘에 넣고 처리하면 &lt;b&gt;서명 값&lt;/b&gt;이 나오게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Signature&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서는 요청이 들어오면 토큰의 헤더, 페이로드, &lt;code&gt;secret key&lt;/code&gt; 를 이용해서 계산된 결과 값이 Signature 값과 같은지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서명 값이 계산된 값과 일치하고 유효기간도 지나지 않았다면 그 요청은 인가처리가 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;따라서 토큰을 탈취해도 서버의 &lt;code&gt;secret key&lt;/code&gt; 를 찾아낼 수 없고 페이로드의 값이 하나라도 변경되더라도 서명값이 달라지기 때문에 조작할 수 없습니다.&lt;/b&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;  그럼 JWT가 짱 인건가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세션과 비교했을 때 JWT의 큰 단점이 존재&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;세션은 stateful 하기 때문에 모든 사용자들의 상태를 기억하고 있기 때문에 구현하기 어렵고 관리하기 어렵지만 이런 상태를 언제든지 제어를 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 여러 사용자가 같은 아이디로 로그인(같은 토큰)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT를 사용하는 서버에서는 항상 허용(토큰이 유효하기 때문)&lt;/li&gt;
&lt;li&gt;이와 달리 세션의 경우는 여러 아이디로 접속을 할 경우 이를 막는 등의 작업을 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 강제 로그아웃 불가능&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;/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;  Refresh Token 전략&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT의 문제를 보완하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만료시간을 매우 짧게 설정하여 토큰의 수명을 짧게 하는 것!&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만료시간을 짧게 하면 해당 만료시간이 끝나면 다시 로그인을 해주어야 합니다.&lt;/p&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;access token&lt;/code&gt; 과 함께 &lt;code&gt;refresh token&lt;/code&gt; 을 제공해줍니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;access token&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;&lt;span&gt;매번 인가를 받을 때 사용하는 토큰, 수명이 짧음&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;refresh token&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;엑세스 토큰을 재 발급받을 때 사용하는 토큰, 수명이 김&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  사용 방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로그인시 access token 과 refresh token을 사용자에게 반환&lt;/li&gt;
&lt;li&gt;refresh token은 데이터베이스에 저장&lt;/li&gt;
&lt;li&gt;클라이언트는 access token이 만료되었을 때 refresh token을 서버로 보내주고&lt;/li&gt;
&lt;li&gt;서버는 데이터베이스에 저장된 값과 비교해 맞다면 새로운 access token을 발급&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☁️ 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;refresh token만 잘 관리해준다면 access token이 만료될 때마다 다시 로그인을 할 필요가 없이 새로 발급을 받을 수 있습니다.&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;따라서, 중간에 엑세스 토큰이 탈취당하더라도 refresh token을 데이터베이스에서 삭제해 오래 사용하지 못하도록 보완&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token이 살아있는 동안은 여전히 즉시 차단 불가&lt;/li&gt;
&lt;li&gt;Refresh Token 관리가 복잡해짐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탈취당하지 않도록 보안 강화 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=tosLBcAX1vk&quot;&gt;https://www.youtube.com/watch?v=tosLBcAX1vk&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=gA1KsJ2ak10&quot;&gt;https://www.youtube.com/watch?v=gA1KsJ2ak10&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;</description>
      <category>  computer science/  network</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/185</guid>
      <comments>https://beomsic.tistory.com/entry/Cookie-%EC%99%80-Session-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%86%A0%ED%81%B0#entry185comment</comments>
      <pubDate>Fri, 26 Sep 2025 01:27:10 +0900</pubDate>
    </item>
    <item>
      <title>HTTP Redirect</title>
      <link>https://beomsic.tistory.com/entry/HTTP-Redirect</link>
      <description>&lt;h1&gt;⭐ Redirect HTTP 상태 코드&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP Redirect는 &lt;b&gt;3xx&lt;/b&gt; 상태 코드를 지닌 응답&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 170px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;상태 코드&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;300&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Multiple Choices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;301&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Moved Permanently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;302&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;303&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;See Other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;304&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Not Modified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;305&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Use Proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;307&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Temporary Redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;308&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Permanent Redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Location 헤더&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Location 응답 헤더는 리다이렉트 할 페이지의 URL을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 헤더는 &lt;code&gt;3xx&lt;/code&gt; (redirection) 또는 &lt;code&gt;201&lt;/code&gt; (created) 응답 상태와 함께 제공됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;리다이렉션의 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;303&lt;/code&gt; (See Other) 응답 코드는 항상 GET 메서드를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;307&lt;/code&gt; (Temporary Redirect), &lt;code&gt;308&lt;/code&gt; (Permanent Redirect)은 원본 요청에서 사용한 메서드를 변경하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;301&lt;/code&gt; (Moved Permanently), &lt;code&gt;302&lt;/code&gt; (Found)는 대부분 메서드를 변경하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 코드 중 하나가 포함된 모든 응답은 Location 헤더를 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리소스 생성의 경우 새로 만들어진 리소스의 URL을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Location: /index.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Content-Location&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;code&gt;Location&lt;/code&gt;과 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Headers/Content-Location&quot;&gt;Content-Location&lt;/a&gt;는 서로 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Location&lt;/code&gt;은 리다이렉션의 대상이나 새로 만들어진 리소스의 URL을 나타냅니다.&lt;/p&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;Content-Location&lt;/code&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;콘텐츠 협상(content negotiation)&lt;/b&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;사용자가 example.com/page를 요청했는데 서버는 한국어(page.ko.html)와 영어(page.en.html) 두 가지 버전의 페이지를 가지고 있다고 가정해 봅시다.&lt;/li&gt;
&lt;li&gt;서버는 클라이언트의 언어 설정을 확인하고 한국어 페이지를 응답으로 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;: 이때 서버는 응답 헤더에 Content-Location: /page.ko.html을 포함시킵니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이는 &quot;/page를 요청했지만 지금 전달하는 콘텐츠의 원본은 사실 /page.ko.html에 있습니다&quot;라고 알려주는 것입니다.&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;code&gt;Content-Location&lt;/code&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;↪️ Redirect&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redirect 응답을 수신한 브라우저는 제공된 새로운 URL을 사용하며 그것을 즉시 로드합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 경우 리다이렉션은 사용자에게는 보이지 않는데다가 적은 성능 저하를 일으킵니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3가지 카테고리의 리다이렉트 유형이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpvncN/btsQOEkv4IZ/ujvcTYwY4qP4qXdPxWT3d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpvncN/btsQOEkv4IZ/ujvcTYwY4qP4qXdPxWT3d1/img.png&quot; data-alt=&quot;https://inpa.tistory.com/entry/HTTP- -3XX-Redirection-상태-코드-제대로-알아보기#303_see_other&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpvncN/btsQOEkv4IZ/ujvcTYwY4qP4qXdPxWT3d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpvncN%2FbtsQOEkv4IZ%2FujvcTYwY4qP4qXdPxWT3d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;368&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://inpa.tistory.com/entry/HTTP- -3XX-Redirection-상태-코드-제대로-알아보기#303_see_other&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 영속적인 리다이렉션 (Permanent)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Redirection은 영원히 지속됨을 의미&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 리소스의 URL이 영구적으로 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 이 응답을 받으면 새 URL을 캐시에 저장하여 다음부터는 이전 URL로 요청하지 않고 바로 새 URL로 이동&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 53px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;301 Moved Permanently&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;GET 메서드는 변경되지 않고 다른 메서드들은 GET으로 변하거나 변하지 않을 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;308 Permanent Redirect&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;301과 유사하지만 메서드를 보존합니다. (301이 POST 요청을 GET으로 바꾸는 문제를 해결)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;301 Moved Permanently&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP &lt;code&gt;301 Moved Permanently&lt;/code&gt; 리디렉션 상태 응답 코드는 요청한 리소스가 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Headers/Location&quot;&gt;Location&lt;/a&gt; 헤더에 주어진 URL로 완전히 옮겨졌다는 것을 나타냅니다.&lt;/p&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;zwj;♂️ &lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GVylz/btsQL46aLSi/1ksV8Tnf9W46USjxpSsKu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GVylz/btsQL46aLSi/1ksV8Tnf9W46USjxpSsKu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GVylz/btsQL46aLSi/1ksV8Tnf9W46USjxpSsKu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGVylz%2FbtsQL46aLSi%2F1ksV8Tnf9W46USjxpSsKu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;422&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;856&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;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;code&gt;https://site.com/redirect&lt;/code&gt; URL을 전송해 클릭&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요청:&lt;/b&gt; 서버에 GET 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;301 응답:&lt;/b&gt; 서버는 301 Moved Permanently와 Location: &lt;code&gt;https://site.com/page&lt;/code&gt; 헤더로 응답합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동 리디렉션:&lt;/b&gt; 브라우저는 301 상태와 Location 헤더를 봅니다.
&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; Location 헤더의 URL로 &lt;b&gt;새로운 요청&lt;/b&gt;을 보냅니다.&lt;/li&gt;
&lt;li&gt;이 과정은 사용자에게 보이지 않게 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 목적지:&lt;/b&gt; 서버는 새 요청에 200 OK와 페이지의 콘텐츠로 응답합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;브라우저 주소 표시줄 업데이트:&lt;/b&gt; 브라우저의 주소 표시줄이 새 URL인 &lt;code&gt;https://site.com/page&lt;/code&gt; 로 업데이트&lt;/li&gt;
&lt;/ol&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;POST 요청에서의 문제&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfmO0e/btsQOj1YhrL/It1ySgGtJIjBggfzgJUh71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfmO0e/btsQOj1YhrL/It1ySgGtJIjBggfzgJUh71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfmO0e/btsQOj1YhrL/It1ySgGtJIjBggfzgJUh71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfmO0e%2FbtsQOj1YhrL%2FIt1ySgGtJIjBggfzgJUh71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;424&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;732&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트에서 /redirect 로 param1, param2 바디 정보를 담은 POST 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;이때, 서버는 301 상태코드와 Location 필드를 통해 /page 로 페이지 주소가 영구적으로 변경되었다는 사실을 알려주고 리다이렉트를 지시합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 해당 location으로 요청을 하는데 메서드가 GET으로 변경되면서 요청을 하고 바디가 제거되어집니다.&lt;/li&gt;
&lt;li&gt;따라서 클라이언트의 원래 의도와 다르게 흘러가 원하지 않는 문제가 발생할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  POST 요청은 308 Permenant Redirect 를 사용하기.&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;  308 Permanent Redirect&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;301과 기능은 동일하지만 리다이렉트시 요청 메서드와 본문을 유지합니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 처음에 POST 메서드, 본문에 데이터에 담아 전송하면 새로운 URL로 리다이렉트하더라도 그 메서드와 본문내용이 유지&lt;/li&gt;
&lt;li&gt;첫 요청에 POST가 사용되었다면 두번째 요청도 반드시 POST를 사용&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;2️⃣ 일시 리다이렉션 (Temporary)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 리소스의 URL이 일시적으로 이동&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;b&gt;302 Found&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%;&quot;&gt;브라우저는 새 URL로 이동하지만 캐시에 저장하지 않습니다. 따라서 다음 요청 시에는 이전 URL로 다시 요청합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;b&gt;307 Temporary Redirect&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%;&quot;&gt;302와 유사하지만 &lt;b&gt;메서드(GET, POST 등)를 변경하지 않도록&lt;/b&gt; 보장합니다. 302는 브라우저에 따라 POST 요청을 GET으로 바꾸는 경우가 있어 안전한 리다이렉트를 위해 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;302 Found&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 URL에서 리소스를 찾습니다 (일시 리다이렉션)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청된 리소스가 Location 헤더가 지정한 URL로 일시적으로 이동되었음을 나타냅니다.&lt;/li&gt;
&lt;li&gt;301와 같이 요청 메서드가 &lt;b&gt;GET으로 변하고 본문이 제거&lt;/b&gt;될 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무조건적으로 변경하지는 않습니다!! (불확실성)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/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;  왜 301, 302는 리다이렉션 후 메서드를 GET으로 바꿀까?? - PRG(Post - Redirect - Get) 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;예시 상황&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트에서 POST를 통해 요청하고 브라우저를 새로고침을 한다면 이전의 요청을 재전송 하기 때문에 POST 요청을 다시하게 되고 따라서 &lt;b&gt;중복된 POST 요청이 서버에게 전달&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;PRG 를 사용하지 않는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUvSBX/btsQMQ0BLNJ/EETIGTDqKIBB2kJla0lQ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUvSBX/btsQMQ0BLNJ/EETIGTDqKIBB2kJla0lQ61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUvSBX/btsQMQ0BLNJ/EETIGTDqKIBB2kJla0lQ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUvSBX%2FbtsQMQ0BLNJ%2FEETIGTDqKIBB2kJla0lQ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;437&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 출금 요청을 POST 메서드로 서버에 전송&lt;/li&gt;
&lt;li&gt;서버에서는 출금 처리를 하고 데이터베이스를 갱신 후 200 OK 반환&lt;/li&gt;
&lt;li&gt;사용자가 브라우저를 새로고침&lt;/li&gt;
&lt;li&gt;이때, 이전의 POST 요청이 그대로 재전송되고 같은 출금 요청이 중복되어 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 PRG 패턴은 위처럼 일시적인 리다이렉션에서 중복 요청의 전송을 방지하기 위해 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;PRG 를 사용하는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJt0NR/btsQOGikezZ/VM5AMeWok7q5LnCXAKuKM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJt0NR/btsQOGikezZ/VM5AMeWok7q5LnCXAKuKM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJt0NR/btsQOGikezZ/VM5AMeWok7q5LnCXAKuKM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJt0NR%2FbtsQOGikezZ%2FVM5AMeWok7q5LnCXAKuKM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;392&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;303 See Other&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 URL에서 리소스를 찾습니다 (일시 리다이렉션)&lt;/p&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;302 Found&lt;/b&gt;와 동일하게 일시 리다이렉트 시 요청 메서드가 GET, 본문을 제거합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 요청 메서드의 변경과 본문 제거 행위를 무조건적으로 보장해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;303&lt;/b&gt;은 302 리다이렉트의 오용을 막기 위해서 &lt;b&gt;HTTP 1.1&lt;/b&gt;에서 추가되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;폼 제출 요청에 응답시에는 이 메서드를 사용..!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  307 Temporary Redirect&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;302 Found 과 기능은 동일하지만 일시 리다이렉트 시 요청 메서드와 본문을 유지합니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 처음에 POST 메서드와 본문에 데이터를 담아 전송하였다면 새로운 URL로 리다이렉트하더라도 그 메서드와 Body 내용이 유지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 요청에 POST가 사용되었다면 두번째 요청도 반드시 POST를 사용&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;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ 특수 리다이렌션 (Special)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시를 활용할 것인지의 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;304 Not Modified&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스가 수정 되지 않아 최신 상태이므로 캐시를 이용하라는 특수 리다이렉션&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 리소스 요청시 서버로부터 304 응답이 오면 클라이언트가 요청한 리소스가 최신 상태이므로 캐시에 가지고 있는 리소스를 그대로 사용해도 된다는 의미&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GET과 HEAD 메소드에만 동작&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;304&lt;/b&gt; 응답 메시지는 &lt;b&gt;Body&lt;/b&gt; 본문에 어떠한 데이터도 포함되면 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Redirections&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Redirections&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Status/301&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Status/301&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Headers/Location&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Headers/Location&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_302&quot;&gt;https://en.wikipedia.org/wiki/HTTP_302&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/HTTP-&quot;&gt;https://inpa.tistory.com/entry/HTTP-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/HTTP-%F0%9F%8C%90-3XX-Redirection-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0#303_see_other&quot;&gt; -3XX-Redirection-상태-코드-제대로-알아보기#303_see_other&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  computer science/  network</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/184</guid>
      <comments>https://beomsic.tistory.com/entry/HTTP-Redirect#entry184comment</comments>
      <pubDate>Wed, 24 Sep 2025 23:31:07 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 빌드, 번들러</title>
      <link>https://beomsic.tistory.com/entry/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%B9%8C%EB%93%9C-%EB%B2%88%EB%93%A4%EB%9F%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  빌드&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드란 개발자가 작성한 소스코드를 브라우저가 실행가능한 형태로 변환하고 여러 파일을 하나 또는 소수의 파일로 묶어 최적화하는 과정입니다.&lt;/p&gt;
&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;브라우저 호환성 문제 해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저마다 지원하는 자바스크립트 문법이 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;const&lt;/code&gt;, &lt;code&gt;async/await&lt;/code&gt;, &lt;code&gt;Promise&lt;/code&gt; 같은 최신 문법(ES6+)은 구형 브라우저에서 동작하지 않기 때문에 실행 가능한 문법으로 변환을 해주어야 합니다.&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;트랜스파일링(Transpiling)&lt;/b&gt; 을 통해 최신 코드를 구버전 문법으로 변환해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ &lt;b&gt;모듈 번들링 (Module Bundling)&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성이 있는 여러개의 파일들을 하나의 파일로 합쳐주는 역할&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 작성하는 코드는 보통 여러 개의 파일(모듈)로 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 기본적으로 ES Module을 지원하지만 구형 브라우저에서는 지원하지 않음&lt;/li&gt;
&lt;li&gt;초기 페이지를 로딩하기 위해서 여러 HTTP 요청이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;index.js
utils.js
components/
├── header.js
├── footer.js
└── sidebar.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;  &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;여러 개의 JS, CSS, 이미지 파일을 하나(혹은 몇 개)로 묶어 브라우저에 전달&lt;/li&gt;
&lt;li&gt;네트워크 요청 수 감소 &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;3️⃣ &lt;b&gt;성능 최적화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 과정에서 다양한 성능 최적화 기법을 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 93px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;코드 압축&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;공백, 주석, 불필요한 문자를 제거&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;변수명을 짧게 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;난독화&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;코드 가독성을 낮춰 보안 강화&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;변수명, 함수명을 의미없는 문자로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;tree shaking&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;사용하지 않는 코드를 제거하여 번들 크기 최적화&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;code splitting&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;필요한 코드만 분리하여 초기 로딩 속도 향상&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;캐싱 최적화&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;파일명에 해시값 추가로 브라우저 캐싱 활용&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;변경되지 않은 파일은 캐시 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ &lt;b&gt;개발 편의성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 도구는 개발자의 생산성을 높여주는 다양한 기능을 제공&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hot Module Replacement (HMR)&lt;/b&gt;: 코드 수정 시 페이지 전체 리로드 없이 즉시 반영&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화된 테스트와 코드 린팅&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 서버 제공&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러 추적(소스맵 제공)&lt;/b&gt;&lt;/li&gt;
&lt;li&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;☁️ 기능&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;↪️ Transpiling&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dR8f08/btsQJEMnlAa/ljyTp74YeryAF6rYkqxdo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dR8f08/btsQJEMnlAa/ljyTp74YeryAF6rYkqxdo0/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=D-fqNyTV3b8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dR8f08/btsQJEMnlAa/ljyTp74YeryAF6rYkqxdo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdR8f08%2FbtsQJEMnlAa%2FljyTp74YeryAF6rYkqxdo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;214&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=D-fqNyTV3b8&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성된 소스코드를 브라우저가 해석할 수 있는 형태(언어)로 변환&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시: ES6+ &amp;rarr; ES5&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;*// Before (ES6+)*
const greet = (name) =&amp;gt; {
  console.log(`Hello, ${name}`);
};

*// After (ES5 - 구형 브라우저 호환)*
&quot;use strict&quot;;
var greet = function greet(name) {
  console.log(&quot;Hello, &quot; + name);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시: TypeScript &amp;rarr; JavaScript&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;*// Before (TypeScript)*
interface User {
  name: string;
  age: number;
}

const user: User = { name: &quot;John&quot;, age: 25 };

*// After (JavaScript)*
const user = { name: &quot;John&quot;, age: 25 };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Bundling&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈들을 하나(혹은 여러 개)로 묶는 과정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tree shaking&lt;/b&gt;과 함께 작동하여 사용되지 않는 코드 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;모듈들을 하나(혹은 여러 개)로 묶는 과정
  src/
├── index.js
├── utils.js
├── api/
│   ├── auth.js
│   └── user.js
└── components/
    ├── Header.js
    └── Footer.js

&amp;darr; 번들링 후

  dist/
├── bundle.js
├── modules.js
└── styles.css&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;코드 압축(minify)&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function calculateTotal(number1, number2) {
  const result = number1 + (number1 * number2);
  return result;
}

// After
function a(b,c){return b+b*c}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Obfuscation (난독화)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안을 위해 코드를 읽기 어렵게 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Before
function getUserInfo(userId) {
  const apiUrl = '&amp;lt;https://api.example.com&amp;gt;';
  return fetch(`${apiUrl}/users/${userId}`);
}

// After (난독화)
function _0x1a2b(a){const b=&quot;&amp;lt;https://api.example.com&amp;gt;&quot;;return fetch(b+&quot;/users/&quot;+a)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Tree shaking&lt;/h3&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;ES Module(ESM)의 &lt;code&gt;import/export&lt;/code&gt; 기반에서만 제대로 작동 (정적 분석)&lt;/li&gt;
&lt;li&gt;CommonJS(&lt;code&gt;require/module.exports&lt;/code&gt;)에서는 동적 분석의 한계로 완전한 Tree Shaking이 어려움&lt;/li&gt;
&lt;/ul&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;// utils.js
export function used() {
  console.log(&quot;I'm used!&quot;);
}
export function unused() {
  console.log(&quot;I'm never used...&quot;);
}

// index.js
import { used } from './utils.js';
used();

// 최종 번들에는 used() 함수만 포함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  환경 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발(Development)과 배포(Production) 모드에서 다른 설정 적용&lt;/p&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;zwj;  &lt;b&gt;개발 모드 (Development)&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;HMR 활성화&lt;/li&gt;
&lt;li&gt;상세한 에러 메시지&lt;/li&gt;
&lt;li&gt;Source Map 생성 (디버깅 용이)&lt;/li&gt;
&lt;li&gt;압축하지 않은 코드 (가독성 우선)&lt;/li&gt;
&lt;li&gt;빠른 빌드 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;배포 모드 (Production)&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;Tree Shaking 적용&lt;/li&gt;
&lt;li&gt;최적화된 번들 크기&lt;/li&gt;
&lt;li&gt;해시값을 포함한 파일명 (캐싱)&lt;/li&gt;
&lt;li&gt;에러 리포팅 도구 연동 (Sentry)&lt;/li&gt;
&lt;/ul&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;*// webpack.config.js 예시*
module.exports = (env, argv) =&amp;gt; {
  const isProduction = argv.mode === 'production';

  return {
    mode: isProduction ? 'production' : 'development',
    devtool: isProduction ? false : 'eval-source-map',
    optimization: {
      minimize: isProduction,
      splitChunks: isProduction ? {...} : false
    }
  };
};

// vite.config.js 예시
import { defineConfig } from 'vite';

export default viteConfig(({ command, mode }) =&amp;gt; {
  const isProduction = mode === 'production';
  return {
    base: './',
    build: {
      minify: isProduction, // 프로덕션 모드일 때만 압축
      sourcemap: !isProduction, // 개발 모드에서만 소스맵
      rollupOptions: {
        //...
      },
    },
    server: {
      open: true, // 개발 서버 실행 시 브라우저 자동 열기
    },
  };
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;HMR (Hot Module Replacement)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 중 특정 코드 변경 시 페이지 새로고침 없이 해당 부분만 리로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 상태 유지&lt;/li&gt;
&lt;li&gt;빠른 개발 피드백&lt;/li&gt;
&lt;li&gt;개발 생산성 향상&lt;/li&gt;
&lt;/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;zwj;♂️ &lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파일 변경 감지&lt;/li&gt;
&lt;li&gt;변경된 모듈만 다시 컴파일&lt;/li&gt;
&lt;li&gt;브라우저에 변경사항 전송&lt;/li&gt;
&lt;li&gt;런타임에서 모듈 교체&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Code Splitting&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 여러 개의 청크(chunk)로 나눠 &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;예시 - React.lazy&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { Suspense } from &quot;react&quot;;

const LazyComponent = React.lazy(() =&amp;gt; import(&quot;./MyComponent&quot;));

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;}&amp;gt;
        &amp;lt;LazyComponent /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&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;code&gt;React.lazy()&lt;/code&gt;로 컴포넌트를 필요할 때 로딩하도록 하고 &lt;code&gt;Suspense&lt;/code&gt;를 함께 사용하여 로딩 중일 때 보여줄 UI를 설정&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;동적 Import&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 버튼 클릭 시에만 test 모듈 로드
button.addEventListener(&quot;click&quot;, async () =&amp;gt; {
  const { Test } = await import(&quot;./test-module&quot;);
  const test = new Test();
  test.render();
});

// 라우트별 코드 분할
const routes = {
  '/dashboard': () =&amp;gt; import('./pages/Dashboard'),
  '/profile': () =&amp;gt; import('./pages/Profile'),
  '/settings': () =&amp;gt; import('./pages/Settings')
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  빌드 도구&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 가장 널리 사용되는 빌드 도구로는 Webpack, Rollup, Vite 등이 있습니다.&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️⃣ Webpack&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/01OEQ/btsQK95RIdG/qeRo6shDKqkspEG4QFz8jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/01OEQ/btsQK95RIdG/qeRo6shDKqkspEG4QFz8jk/img.png&quot; data-alt=&quot;https://webpack.kr/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/01OEQ/btsQK95RIdG/qeRo6shDKqkspEG4QFz8jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F01OEQ%2FbtsQK95RIdG%2FqeRo6shDKqkspEG4QFz8jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;314&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://webpack.kr/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 널리 사용되는 자바스크립트 &lt;b&gt;모듈 번들러&lt;/b&gt;중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(JavaScript, CSS, 이미지 등)을 모듈로 관리할 수 있고 이를 하나의 파일로 번들링하는 기능을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강력한 로더와 플러그인 시스템을 통해 다양한 파일 형식과의 호환성을 제공하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Webpack의 설정이 복잡하고 초기 구성이 어려울 수 있다는 단점이 있습니다.&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;Webpack은 모든 모듈을 분석하고 의존성을 파악하는 과정에서 시간이 소요되기 때문입니다.&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;그럼에도 Webpack은 강력한 기능과 넓은 사용자 기반을 가지고 있어 많은 프로젝트에서 선호되고 있습니다.&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️⃣ Rollup&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rollup은 ES6 모듈을 중심으로 설계된 모듈 번들러입니다. Webpack과 비교했을 때 Rollup은 더 작은 번들 사이즈를 생성하는 것으로 알려져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Rollup은 트리 쉐이킹(tree-shaking) 기능을 통해 사용되지 않는 코드를 제거하기 때문입니다.&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;Rollup은 라이브러리와 프레임워크 개발에 적합한 도구로 평가받고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Rollup이 ES6 모듈의 장점을 최대한 활용하기 때문&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;하지만 Rollup은 Webpack만큼 다양한 파일 형식과의 호환성을 제공하지 않아 생태계가 상대적으로 작다는 단점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주로 자바스크립트 모듈에 초점을 맞추고 있기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ Vite&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite(프랑스어로 &quot;빠르다(Quick)&quot;를 의미하며 발음은 &quot;veet&quot;와 비슷한 /vit/&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바이트라고 읽는 줄 알았는데 아니였다..!&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite는 최신 프론트엔드 개발 도구로, 빠른 빌드 속도와 즉각적인 모듈 업데이트를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite는 ES 모듈을 기반으로 동작해 개발 중에는 서버 사이드에서 모듈을 직접 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vite는 브라우저가 ES 모듈을 직접 해석할 수 있도록 하여 전통적인 번들링 과정을 생략하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 개발 환경에서의 빌드 속도가 매우 빨라져 변경 사항이 즉시 반영됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Vite는 설정이 간단하고 사용하기 쉽다는 장점이 있습니다.&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; &amp;zwj;  실제로 빌드 도구 사용해보기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;typescript&lt;/code&gt;와 &lt;code&gt;vite&lt;/code&gt;를 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 빌드 도구 세팅&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;npm create vite@latest my-project
cd my-project
npm install&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;code&gt;vanilla-ts&lt;/code&gt; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 개발 환경 실행&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;vite&quot;,       // 개발 서버 실행
    &quot;build&quot;: &quot;vite build&quot;, // 배포용 빌드
    &quot;preview&quot;: &quot;vite preview&quot; // 빌드 결과 프리뷰
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run dev&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;Vite의 개발 서버가 실행되고 HMR 자동 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6gBrp/btsQI8NJj2z/9yJXGK6ykI2FVy1VEAuxk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6gBrp/btsQI8NJj2z/9yJXGK6ykI2FVy1VEAuxk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6gBrp/btsQI8NJj2z/9yJXGK6ykI2FVy1VEAuxk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6gBrp%2FbtsQI8NJj2z%2F9yJXGK6ykI2FVy1VEAuxk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;614&quot; height=&quot;524&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 빌드 실행&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run build&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;code&gt;dist/&lt;/code&gt; 폴더 생성&lt;/li&gt;
&lt;li&gt;코드 압축 및 최적화 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://themeselection.com/javascript-build-tools/&quot;&gt;https://themeselection.com/javascript-build-tools/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.vite.dev/guide/&quot;&gt;https://ko.vite.dev/guide/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.kr/concepts/&quot;&gt;https://webpack.kr/concepts/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=xLziDinqBj0&quot;&gt;https://www.youtube.com/watch?v=xLziDinqBj0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Code_splitting&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/Code_splitting&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/17710/&quot;&gt;https://techblog.woowahan.com/17710/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://f-lab.kr/insight/web-development-build-tools-comparison&quot;&gt;https://f-lab.kr/insight/web-development-build-tools-comparison&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/183</guid>
      <comments>https://beomsic.tistory.com/entry/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%B9%8C%EB%93%9C-%EB%B2%88%EB%93%A4%EB%9F%AC#entry183comment</comments>
      <pubDate>Mon, 22 Sep 2025 22:49:54 +0900</pubDate>
    </item>
    <item>
      <title>실행 컨텍스트</title>
      <link>https://beomsic.tistory.com/entry/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실행 컨텍스트&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  코드가 실행되는 환경을 구성&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  역할&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;/li&gt;
&lt;/ul&gt;
&lt;/li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행컨텍스트는 언제 생성되는지 알기 위해서는 소스 코드의 종류를 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  4가지 타입의 소스코드와 이에 따라 생성되는 실행 컨텍스트&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;전역 코드&lt;/td&gt;
&lt;td&gt;전역 실행 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;함수 코드&lt;/td&gt;
&lt;td&gt;함수 실행 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eval 코드&lt;/td&gt;
&lt;td&gt;eval 실행 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모듈 코드&lt;/td&gt;
&lt;td&gt;모듈 실행 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실행 컨텍스트 생성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRVC7H/btsQIChlaX5/g8tqU2DHwI5BAp2gfcJnC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRVC7H/btsQIChlaX5/g8tqU2DHwI5BAp2gfcJnC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRVC7H/btsQIChlaX5/g8tqU2DHwI5BAp2gfcJnC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRVC7H%2FbtsQIChlaX5%2Fg8tqU2DHwI5BAp2gfcJnC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;231&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;작성한 소스코드는 자바스크립트 엔진의 생성 과정을 통해 실행 컨텍스트를 생성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수 선언, 함수 선언등을 위한 메모리 공간이 해당 컨텍스트 내에서 설정&lt;/li&gt;
&lt;li&gt;생성단계에서 호이스팅 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;자바스크립트 엔진의 실행 과정을 통해서 결과를 도출&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 컨텍스트가 호출 스택에 있고 코드가 실제로 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ozC3/btsQIBQkk6w/CHRpIeXfTKWuG9ArLEurKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ozC3/btsQIBQkk6w/CHRpIeXfTKWuG9ArLEurKK/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=EWfujNzSUmw&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ozC3/btsQIBQkk6w/CHRpIeXfTKWuG9ArLEurKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ozC3%2FbtsQIBQkk6w%2FCHRpIeXfTKWuG9ArLEurKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;336&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=EWfujNzSUmw&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.9535%;&quot;&gt;Variable Environment&lt;/td&gt;
&lt;td style=&quot;width: 30.814%;&quot;&gt;선언된 변수들을 포함한 환경을 나타내는 객체&lt;/td&gt;
&lt;td style=&quot;width: 50.1163%;&quot;&gt;자신의 외부 환경에 대한 참조를 가지고 있고 호이스팅된 변수들의 정보가 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.9535%;&quot;&gt;Lexical Environment&lt;/td&gt;
&lt;td style=&quot;width: 30.814%;&quot;&gt;변수, 함수등의 정보를 담은 환경을 나타내는 객체&lt;/td&gt;
&lt;td style=&quot;width: 50.1163%;&quot;&gt;자신의 외부 환경에 대한 참조를 가지고 있으며 &lt;b&gt;스코프 체인&lt;/b&gt; 정보가 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.9535%;&quot;&gt;This Binding&lt;/td&gt;
&lt;td style=&quot;width: 30.814%;&quot;&gt;&lt;b&gt;this 키워드&lt;/b&gt;가 바인딩되는 객체&lt;/td&gt;
&lt;td style=&quot;width: 50.1163%;&quot;&gt;&lt;b&gt;호출 방식&lt;/b&gt;에 따라 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;☑️ 평가 단계&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;console.log(beomsic);

var beomsic = 1;
var hi = 2;

console.log(beomsic);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 소스코드가 있을 때 자바스크립트 엔진은 실행하기 전 식별자들을 수집하여 &lt;b&gt;실행 컨텍스트&lt;/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;code&gt;var&lt;/code&gt; 키워드로 선언된 beomsic 과 hi 라는 식별자를 먼저 발견&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 Environment Record 안에서 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 전역 실행 컨텍스트 - Global Execution Context
{
  // 렉시컬 환경 (LexicalEnvironment) - 변수와 함수 선언이 저장되는 곳
  &quot;LexicalEnvironment&quot;: {
    // 환경 레코드 (Environment Record) - 실제 변수들이 저장되는 공간
    &quot;EnvironmentRecord&quot;: {
      // 객체 환경 레코드 (Object Environment Record) - var 변수, 함수 선언 등이 저장
      &quot;beomsic&quot;: undefined,
      &quot;hi&quot;: undefined
    },
    // 외부 환경 참조 (Outer LexicalEnvironment) - 현재 스코프 밖의 렉시컬 환경을 가리킴. 
    // 전역은 null
    &quot;outerEnvironmentReference&quot;: null
  },

  // this 바인딩 (ThisBinding) - 현재 컨텍스트에서 this가 가리키는 값. 
  // 전역에서는 window 또는 global
  &quot;ThisBinding&quot;: &quot;global&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Environment Record (환경 레코드)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별자와 식별자에 바인딩된 값을 기록&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Outer Environment Reference (외부 환경 참조)&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;Lexical Environment&lt;/b&gt;를 가리킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드를 보면&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;var beomsic = 1;
var hi = 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 값이 실제로 할당되어 있는 것 같지만 코드가 실행이 되기전 평가단계를 통해 식별자만 수집한 객체를 가지고 있어서 &lt;code&gt;undefined&lt;/code&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;code&gt;undefined&lt;/code&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujyZ7/btsQJEsc8yZ/aS6sXONKTaQTku3m0kIEs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujyZ7/btsQJEsc8yZ/aS6sXONKTaQTku3m0kIEs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujyZ7/btsQJEsc8yZ/aS6sXONKTaQTku3m0kIEs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujyZ7%2FbtsQJEsc8yZ%2FaS6sXONKTaQTku3m0kIEs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;777&quot; height=&quot;300&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EX69m/btsQJ2sQ48A/whKRD3mxtReXrehqTv41J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EX69m/btsQJ2sQ48A/whKRD3mxtReXrehqTv41J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EX69m/btsQJ2sQ48A/whKRD3mxtReXrehqTv41J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEX69m%2FbtsQJ2sQ48A%2FwhKRD3mxtReXrehqTv41J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;230&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;undefined
1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  변수 호이스팅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평가 과정에서 식별된 식별자들에 한해서 미리 메모리를 할당해놓는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbHTwZ/btsQJ532dcc/yGkQGIUKituoFCFgFIW2p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbHTwZ/btsQJ532dcc/yGkQGIUKituoFCFgFIW2p0/img.png&quot; data-alt=&quot;https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-variable-statement&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbHTwZ/btsQJ532dcc/yGkQGIUKituoFCFgFIW2p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbHTwZ%2FbtsQJ532dcc%2FyGkQGIUKituoFCFgFIW2p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;733&quot; height=&quot;266&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-variable-statement&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;var&lt;/code&gt; 키워드는 실행중인 실행 컨텍스트의 Variable Enviroment 에 범위가 지정된 변수를 선언&lt;/p&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;var&lt;/code&gt; 변수는 Environment Record가 인스턴스화 될 때 생성되어 생성될 때 &lt;code&gt;undefined&lt;/code&gt;로 초기화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 호이스팅 관점에서 이해를 해보면 아래와 같을 것입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;var beomsic; // undefined
var hi; // undefined
console.log(beomsic);

beomsic = 1;
hi = 2;

console.log(beomsic);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  var 와 let, const&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ES6에서 추가된 let, const 키워드로 변수를 선언하면??&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;console.log(beomsic);

const beomsic = 1;

console.log(beomsic);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에는 자바스크립트 엔진은 beomsic이라는 식별자를 기록을 해두긴 하지만 값을 초기화 하지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 위 코드에서는 첫 번째 줄에서 Reference Error 가 발생합니다. (let도 동일)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;code&gt;let&lt;/code&gt; 또는 &lt;code&gt;const&lt;/code&gt; 키워드로 선언했을 때 선언 이전에 식별자를 참조할 수 없는 구역&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PU3I9/btsQH88FJtn/W8AbqAi20pElNjKCZDvRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PU3I9/btsQH88FJtn/W8AbqAi20pElNjKCZDvRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PU3I9/btsQH88FJtn/W8AbqAi20pElNjKCZDvRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPU3I9%2FbtsQH88FJtn%2FW8AbqAi20pElNjKCZDvRk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;396&quot; height=&quot;242&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;키워드&lt;/td&gt;
&lt;td&gt;선언&lt;/td&gt;
&lt;td&gt;초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;var&lt;/td&gt;
&lt;td&gt;메모리 공간을 확보하고 식별자와 연결&lt;/td&gt;
&lt;td&gt;식별자에 암묵적으로 undefined 값 바인딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;let / const&lt;/td&gt;
&lt;td&gt;메모리 공간을 확보하고 식별자와 연결&lt;/td&gt;
&lt;td&gt;초기화하지 않아 할당문 전까지는 아무런 값이 담기지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  함수 호이스팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;함수 표현식 - 화살표 함수로 함수를 변수로 담는 경우&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;fun1();

var fun1 = () =&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var 키워드에 화살표 함수를 담아 선언문 이전에 실행을 하고자 한다면 &lt;b&gt;Environment Record&lt;/b&gt;에 있는 fun1 의 값은 &lt;code&gt;undefined&lt;/code&gt;로, &lt;code&gt;undefined&lt;/code&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;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;fun1();

const fun1 = () =&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 코드를 const로 사용하게 되면 &lt;b&gt;Environment Record&lt;/b&gt;에 기록된 값이 없어 &lt;b&gt;Reference Error&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;2️⃣ &lt;b&gt;함수 선언문 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;fun1();

function fun1() {}&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;fun1&lt;/code&gt; 함수를 선언함과 동시에 완성된 함수 객체를 생성해서 &lt;b&gt;Environment Record&lt;/b&gt; 에 기록합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이처럼 변수에 담지 않고 함수 선언문 방식을 사용한 경우에는 함수 선언과 동시에 함수가 생성되어 선언전에도 함수를 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 실행 컨텍스트 스택&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rndl2/btsQKaxyWpV/tQRDL3U2qAq7SRE8H7VH4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rndl2/btsQKaxyWpV/tQRDL3U2qAq7SRE8H7VH4K/img.png&quot; data-alt=&quot;https://tc39.es/ecma262/#sec-executable-code-and-execution-contexts&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rndl2/btsQKaxyWpV/tQRDL3U2qAq7SRE8H7VH4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frndl2%2FbtsQKaxyWpV%2FtQRDL3U2qAq7SRE8H7VH4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;336&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://tc39.es/ecma262/#sec-executable-code-and-execution-contexts&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;1️⃣ 실행 컨텍스트 스택은 실행 컨텍스트를 추적(track)하기 위해 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 현재 실행중인 실행 컨텍스트는 항상 스택의 최상위 요소(top)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 현재 실행중인 실행 컨텍스트와 관련된 실행 가능한 코드에서 연관되지 않은 실행 가능한 코드로 실행 흐름이 전환될 때마다 새로운 실행 컨텍스트가 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ 새로 생성된 실행 컨텍스트는 스택에 push되어 현재 실행 중인 실행 컨텍스트가 되어 이후에 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  예시 코드&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;var a = 1;
function outer() {
  console.log(a);
  var a = 2;
  function inner() {
    var b = 3;
    console.log(a + b);
  }
  inner();
}

outer();
console.log(a);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;undefined
5
1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  왜 위와 같은 결과가 나왔는지 확인해보기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 먼저 &lt;b&gt;전역&lt;/b&gt; 실행 컨텍스트가 판단을 통해 생성&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// global execution context
{
  &quot;LexicalEnvironment&quot;: {
    &quot;EnvironmentRecord&quot;: {
      &quot;a&quot;: undefined,
      &quot;outer&quot;: &amp;lt;function&amp;gt;
    },
    &quot;outerEnvironmentReference&quot;: null 
  },
}&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;2️⃣ &lt;b&gt;outer&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;새로운 실행 포인터가 생기고 outer 함수 내부로 이동&lt;/li&gt;
&lt;li&gt;평가 과정을 통해 실행 컨텍스트 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;monkey&quot;&gt;&lt;code&gt;// global execution context ...

outerExecutionContext = {
    {
      &quot;LexicalEnvironment&quot;: {
        &quot;EnvironmentRecord&quot;: {
          &quot;a&quot;: undefined, 
          &quot;inner&quot;: &amp;lt;function&amp;gt;
        },
        &quot;outerEnvironmentReference&quot;: globalExecutionContext.lexicalEnviroment 
      },
    }
}&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;console.log()&lt;/code&gt;를 통해 &lt;code&gt;undefined&lt;/code&gt; 출력 이후 2 값을 할당&lt;/p&gt;
&lt;pre class=&quot;monkey&quot;&gt;&lt;code&gt;// global execution context ...

outerExecutionContext = {
    {
      &quot;LexicalEnvironment&quot;: {
        &quot;EnvironmentRecord&quot;: {
          &quot;a&quot;: 2, 
          &quot;inner&quot;: &amp;lt;function&amp;gt;
        },
        &quot;outerEnvironmentReference&quot;: globalExecutionContext.lexicalEnviroment 
      },
    }
}&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;3️⃣ &lt;b&gt;inner&lt;/b&gt; 함수&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마찬가지로 새로운 포인터와 함께 다른 실행컨텍스트 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;// global execution context ...

// outerExecutionContext...

innerExecutionContext = {
    {
      &quot;LexicalEnvironment&quot;: {
        &quot;EnvironmentRecord&quot;: {
          &quot;b&quot;: undefined, 
        },
        &quot;outerEnvironmentReference&quot;: outerExecutionContext.lexicalEnviroment 
      },
    }
}&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;console.log(a + b);&lt;/code&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;a&lt;/b&gt; 를 현재 실행컨텍스트에서 찾습니다.&lt;/li&gt;
&lt;li&gt;식별자 &lt;b&gt;a&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;outerExecutionContext의 식별자 a의 할당된 값을 사용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;outer 실행 컨텍스트와 전역 실행 컨텍스트 둘다 같은 a 식별자를 선언하고 있지만 outer 실행 컨텍스트의 할당된 값을 사용 - 변수 섀도잉&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;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;스코프 체인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별자를 결정할 때 활용되는 스코프들의 연결리스트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Outer Environment Reference&lt;/b&gt; 를 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;계속 없다면 &amp;rarr; 전역 실행 컨텍스트.. 없다면? &amp;rarr; 전역 객체.. 없다면? &amp;rarr; 레퍼런스 에러&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 inner 실행컨텍스트는 실행 컨텍스트 스택에서 &lt;code&gt;pop&lt;/code&gt; 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다시 이전 포인터로 이동하여 해당 실행 과정을 다시 시작 (outer 실행 컨텍스트)&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;4️⃣ &lt;b&gt;global 실행 컨텍스트&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;code&gt;console.log(a);&lt;/code&gt; 를 실행하는데 global 실행 컨텍스트에서 식별자 &lt;b&gt;a&lt;/b&gt;를 관리하고 있기 때문에 할당된 값을 바로 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 컨텍스트는 식별자를 등록하고 관리하는 &lt;b&gt;스코프&lt;/b&gt;와 &lt;b&gt;코드 순서 관리&lt;/b&gt;를 구현한 내부 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 코드는 실행 컨텍스트를 통해 실행되고 관리됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tc39.es/ecma262/#sec-executable-code-and-execution-contexts&quot;&gt;https://tc39.es/ecma262/#sec-executable-code-and-execution-contexts&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zdGfo6I1yrA&quot;&gt;https://www.youtube.com/watch?v=zdGfo6I1yrA&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=EWfujNzSUmw&quot;&gt;https://www.youtube.com/watch?v=EWfujNzSUmw&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  자바스크립트</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/182</guid>
      <comments>https://beomsic.tistory.com/entry/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8#entry182comment</comments>
      <pubDate>Mon, 22 Sep 2025 22:29:58 +0900</pubDate>
    </item>
    <item>
      <title>웹 서버와 WAS</title>
      <link>https://beomsic.tistory.com/entry/%EC%9B%B9-%EC%84%9C%EB%B2%84%EC%99%80-WAS</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정적 웹 페이지와 동적 웹 페이지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정적 웹 페이지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버에 이미 저장된 정적 자원(HTML, CSS, Javascript, 이미지 파일 등)을 클라이언트에게 전송&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 웹 페이지 요청을 받으면 추가 과정없이 응답을 전송&lt;/li&gt;
&lt;li&gt;저장된 데이터를 변경할 때까지 페이지가 동일하게 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEmE8r/btsQDJftEBF/O2ywtQYXjdrO3SMOxmyJkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEmE8r/btsQDJftEBF/O2ywtQYXjdrO3SMOxmyJkk/img.png&quot; data-alt=&quot;https://share.google/images/TwOCqlQIMcRwpVb6Y&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEmE8r/btsQDJftEBF/O2ywtQYXjdrO3SMOxmyJkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEmE8r%2FbtsQDJftEBF%2FO2ywtQYXjdrO3SMOxmyJkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;753&quot; height=&quot;212&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://share.google/images/TwOCqlQIMcRwpVb6Y&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  동적 웹 페이지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 저장된 HTML 파일이 그대로 브라우저에 나오는 것이 아닌 동적으로 만들어지는 웹 페이지&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;클라이언트에서 요청을 보내면 웹 애플리케이션 서버에서 데이터베이스를 조회하거나 외부 서버에서 데이터를 읽어 HTML을 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jx1fQ/btsQElye8QE/RyY2J5JNb1YiI1SXT2pv61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jx1fQ/btsQElye8QE/RyY2J5JNb1YiI1SXT2pv61/img.png&quot; data-alt=&quot;https://share.google/images/TwOCqlQIMcRwpVb6Y&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jx1fQ/btsQElye8QE/RyY2J5JNb1YiI1SXT2pv61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJx1fQ%2FbtsQElye8QE%2FRyY2J5JNb1YiI1SXT2pv61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;203&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://share.google/images/TwOCqlQIMcRwpVb6Y&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정리&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;정적 웹 (Static Web)&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;동적 웹 (Dynamic Web)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;미리 완성된 HTML, CSS, JS, 이미지 등 그대로 제공&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;서버에서 요청 시마다 새로운 콘텐츠 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;HTML 파일, 정적 이미지, JS/CSS 파일&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;게시판, 로그인, 검색 결과 페이지 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;비유&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;음식점에서 &lt;b&gt;콜라(캔)&lt;/b&gt; 바로 제공&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;요리사가 주문에 따라 &lt;b&gt;음식 조리&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹 서버(Web Server)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 프로토콜을 기반으로 클라이언트 요청에 알맞은 정적 콘텐츠를 제공&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청에 따라 두 가지 기능중 선택하여 수행합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동적인 콘텐츠 제공을 위해 WAS에 요청 전달
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동적으로 데이터나 콘텐츠를 생성하는 요청은 처리하기 어렵기에 해당 요청을 웹 애플리케이션 서버에게 전달합니다.&lt;/li&gt;
&lt;li&gt;웹 애플리케이션 서버로부터 만들어진 동적 데이터나 콘텐츠를 받아 클라이언트에게 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예로는 &lt;code&gt;Apache&lt;/code&gt;, &lt;code&gt;Nginx&lt;/code&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;  WAS (Web Application Server)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;WAS&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;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS는 사용자의 요청에 대해 다양한 비즈니스 로직과 데이터를 처리하여 동적 콘텐츠를 생성하고 제공하는 서버입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적인 자원(HTML, 이미지, CSS 등)을 제공하는 웹 서버와 달리 WAS는 사용자 맞춤형 페이지 생성, 데이터베이스 연동, 복잡한 로직 수행 등을 담당합니다.&lt;/p&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;Tomcat&lt;/code&gt;, &lt;code&gt;Jetty&lt;/code&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;  왜 WAS 앞에 웹 서버를 둘까? - 웹 서버의 핵심 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 단순한 정적 파일 제공 외에도 중요한 기능들을 수행하며 이 때문에 WAS 앞에 두는 것이 일반적입니다.&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️⃣&amp;nbsp;&lt;b&gt;리버스 프록시(Reverse Proxy)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 클라이언트의 요청을 받아 WAS로 전달하고 응답을 다시 클라이언트에게 보내주는 &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;이 과정에서 WAS의 &lt;b&gt;실제 주소나 포트 번호 같은 내부 정보를 외부에 노출시키지 않아 보안을 강화&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 여러 개의 WAS가 있더라도 클라이언트는 웹 서버 하나만 알면 되므로 시스템 구조가 단순해집니다.&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️⃣&amp;nbsp;&lt;b&gt;로드 밸런싱(Load Balancing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 요청이 많아질 때 웹 서버는 여러 대의 WAS에 요청을 &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;줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 한 WAS가 유지보수나 업데이트로 인해 일시적으로 작동을 멈춰도 웹 서버가 다른 WAS로 요청을 돌려주므로 사용자 입장에서는 서비스가 끊기는 것을 느끼지 못합니다.&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️⃣&amp;nbsp;&lt;b&gt;캐싱(Caching) - 리버스 프록시 캐싱&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 자주 요청되는 정적 파일(이미지, CSS, 자바스크립트 등)을 &lt;b&gt;자체적으로 저장&lt;/b&gt;해둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 저장된 파일에 대한 요청이 다시 들어오면, WAS까지 가지 않고 &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;⚡️ 이는 WAS의 &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;h2 data-ke-size=&quot;size26&quot;&gt;  Apache vs Nginx&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐ 작동방식에 있어 차이가 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아파치는 멀티 프로세스, Nginx는 이벤트로 일을 처리한다는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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️⃣ 아파치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아파치&lt;/b&gt;는 &lt;b&gt;MPM&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;MPM 종류&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;처리 모델&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 21.8605%;&quot;&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;prefork&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;요청마다 &lt;b&gt;새로운 프로세스&lt;/b&gt; 생성&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;요청이 올 때마다 독립된 프로세스로 처리&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;안정적, 각 요청이 격리되어 안전&lt;/td&gt;
&lt;td style=&quot;width: 21.8605%;&quot;&gt;메모리 사용량 많음, 속도 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;worker&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;요청마다 &lt;b&gt;스레드 생성&lt;/b&gt; (한 프로세스 안에서 여러 스레드로 처리)&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;하나의 프로세스가 여러 요청을 동시에 처리&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;메모리 효율적, 성능 좋음&lt;/td&gt;
&lt;td style=&quot;width: 21.8605%;&quot;&gt;스레드 간 충돌 가능성, 스레드 안정성 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10%;&quot;&gt;&lt;b&gt;event&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;&lt;b&gt;worker 기반 + keep-alive 최적화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;keep-alive 연결을 전담 스레드가 관리&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;높은 성능, 동시 연결 최적화&lt;/td&gt;
&lt;td style=&quot;width: 21.8605%;&quot;&gt;복잡한 구조, 최신 버전에서만 완전 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;prefork&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청 1개 = 프로세스 1개&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 요청을 보내면 아파치는 새로운 프로세스를 만들어 그 요청을 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 끝나면 프로세스는 반환되거나 종료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;요청 간 완전한 격리가 보장 &amp;rarr; 안정성 높음.&lt;/li&gt;
&lt;li&gt;CGI 스크립트처럼 멀티스레드 안전성이 없는 코드와 호환성 좋음.&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;요청 수가 많아지면 프로세스가 폭증&lt;/b&gt; &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;  &lt;b&gt;worker&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청 1개 = 스레드 1개 (단, 프로세스는 유지)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 아파치가 여러 프로세스를 띄워 놓고 각 프로세스가 여러 스레드를 생성하여 요청을 병렬 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&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;/li&gt;
&lt;li&gt;많은 요청을 동시에 처리 가능 &amp;rarr; 성능 우수.&lt;/li&gt;
&lt;li&gt;단, 스레드 간 데이터 충돌 가능성이 있어 코드가 스레드 안전해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;event&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;code&gt;worker&lt;/code&gt; 방식의 개선판으로 &lt;b&gt;keep-alive 연결&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;일반 HTTP는 요청이 끝나도 TCP 연결을 유지(keep-alive)하지만 유지되는 동안 스레드가 점유되어 낭비가 발생할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;event&lt;/code&gt; 방식에서는 keep-alive 연결을 별도 스레드가 관리하여 &lt;b&gt;메인 스레드가 낭비되지 않도록 최적화&lt;/b&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;&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;최신 아파치(version 2.4)에서 기본 MPM으로 채택&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;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;아파치가 시작되면 지정한 서버 프로세스 값만큼 초기 프로세스를 실행.&lt;/li&gt;
&lt;li&gt;클라이언트 요청 발생 시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;prefork&lt;/b&gt;: 새로운 프로세스를 생성하여 요청 처리.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;worker&lt;/b&gt;: 기존 프로세스 내에서 새로운 스레드로 요청 처리.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;event&lt;/b&gt;: 요청 처리는 worker와 동일하지만 keep-alive 연결은 별도 관리 스레드에서 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청이 끝나면 프로세스/스레드를 반환하거나 종료.&lt;/li&gt;
&lt;/ol&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️⃣ Nginx&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Nginx의 event driven 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;비동기, 논블로킹 I/O&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;요청 처리가 완료될 때까지 기다리지 않고 다른 요청을 계속 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;소수의 프로세스만 사용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;기본적으로 master process + 여러 개의 worker process로 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이벤트 루프(Event Loop)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;각 worker가 이벤트 루프를 돌면서 수많은 요청을 동시에 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;높은 확장성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;적은 리소스로도 수만 개의 동시 연결 처리 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Master Process 시작&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nginx가 실행되면 &lt;code&gt;master process&lt;/code&gt;가 시작되어 설정 파일을 읽고 초기화.&lt;/li&gt;
&lt;li&gt;이후 여러 개의 &lt;code&gt;worker process&lt;/code&gt;를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Worker Process에서 이벤트 루프 실행&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 worker는 이벤트 루프를 통해 수많은 요청을 &lt;b&gt;비동기적으로 동시에 처리&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;요청이 들어오면 OS 커널의 &lt;b&gt;epoll&lt;/b&gt;이나 &lt;b&gt;kqueue&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;요청이 들어오면 그 작업(예: 파일 읽기, DB 호출)을 비동기적으로 요청하고 그동안 다른 요청을 처리.&lt;/li&gt;
&lt;li&gt;작업이 완료되면 이벤트가 발생하고, 그 시점에 다시 처리 재개.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 하나의 worker가 요청 - 대기 - 응답 을 병렬적으로 동시에 여러 개 관리할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;♂️ &lt;b&gt;Nginx 기본 프로세스 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;+---------------------+
|     Master Process  |  &amp;larr; 설정 관리, Worker 관리
+----------+----------+
           |
+----------+----------+
| Worker Process #1   | &amp;larr; 이벤트 루프, 실제 요청 처리
| Worker Process #2   |
| Worker Process #3   |
| ...                 |
+---------------------+&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;b&gt;CPU 코어 수만큼 worker&lt;/b&gt;를 띄웁니다.&lt;/li&gt;
&lt;li&gt;각 worker는 &lt;b&gt;단일 스레드&lt;/b&gt;로 동작하여 이벤트 루프 기반으로 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  이벤트 기반 비유 (아파치와 비교)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아파치&lt;/b&gt;는 손님(요청)이 올 때마다 새로운 테이블이나 자리를 마련하는 구조라면 &lt;b&gt;Nginx는 작은 데스크 하나를 두고 줄을 세워 효율적으로 처리&lt;/b&gt;하는 방식입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  예시&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;손님이 한 줄로 쭉 서서 기다림.&lt;/li&gt;
&lt;li&gt;직원(A)이 질문 &amp;rarr; &amp;ldquo;어떤 일로 오셨나요?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;손님이 서류 작성하는 동안 직원은 바로 다음 손님을 응대.&lt;/li&gt;
&lt;li&gt;서류가 준비되면 해당 손님을 다시 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  왜 Nginx가 성능이 좋은가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &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;스레드/프로세스 급증 없이 하나의 worker가 수많은 요청 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache는 요청 수에 따라 스레드 수가 늘어가고 스레드 하나당 메모리 스택 공간이 필요하고 스케줄링 시 컨텍스트 스위칭 비용 발생!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;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;I/O 작업(파일, DB, 네트워크)이 끝날 때까지 기다리지 않고 다른 요청을 처리.&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;3️⃣ &lt;b&gt;Keep-Alive 효율적 관리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결을 유지하는 동안 프로세스를 점유하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ &lt;b&gt;리버스 프록시, 로드 밸런싱에 강함&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대규모 서버 트래픽을 효율적으로 분산 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/computer-networks/difference-between-static-and-dynamic-web-pages/&quot;&gt;https://www.geeksforgeeks.org/computer-networks/difference-between-static-and-dynamic-web-pages/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://engineering.linecorp.com/ko/blog/how-to-quickly-develop-static-pages-in-line&quot;&gt;https://engineering.linecorp.com/ko/blog/how-to-quickly-develop-static-pages-in-line&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yalco.kr/09_static_dynamic_web/&quot;&gt;https://www.yalco.kr/09_static_dynamic_web/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Howto/Web_mechanics/What_is_a_web_server&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Howto/Web_mechanics/What_is_a_web_server&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://httpd.apache.org/docs/current/mpm.html&quot;&gt;https://httpd.apache.org/docs/current/mpm.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://httpd.apache.org/docs/current/en/mod/event.html&quot;&gt;https://httpd.apache.org/docs/current/en/mod/event.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nginx.org/en/docs/dev/development_guide.html#event_loop&quot;&gt;https://nginx.org/en/docs/dev/development_guide.html#event_loop&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  computer science/  network</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/181</guid>
      <comments>https://beomsic.tistory.com/entry/%EC%9B%B9-%EC%84%9C%EB%B2%84%EC%99%80-WAS#entry181comment</comments>
      <pubDate>Wed, 17 Sep 2025 16:29:47 +0900</pubDate>
    </item>
    <item>
      <title> &amp;zwj;  구글 엔지니어는 이렇게 일한다 (3) - 지식공유</title>
      <link>https://beomsic.tistory.com/entry/%F0%9F%A7%91%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-%EA%B5%AC%EA%B8%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%8A%94-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9D%BC%ED%95%9C%EB%8B%A4-3-%EC%A7%80%EC%8B%9D%EA%B3%B5%EC%9C%A0</link>
      <description>&lt;h1&gt;  3. 지식공유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 사실은 조직에 배움의 문화가 자리 잡혀야 한다는 것이고 그러려면 사람들에게 모르는 걸 인정할 수 있도록 돕는 심리적 안전을 제공해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3.1 배움을 가로막는 장애물&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;1️⃣ &lt;b&gt;심리적 안전 부족&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불이익이 두려워서 스스로 위험을 감수하거나 실수를 드러내기 꺼리는 환경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 현상은 두려움이 팽배한 문화 혹은 꼭꼭 숨기려는 경향으로 나타나곤 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;정보 섬&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직의 각 부서가 서로 소통하거나 자원을 공유하지 않아서 지식이 파편화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 환경에서는 일하는 방식을 각각의 부서가 제각기 만들어나가서 여러 현상이 나타남&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정보 파편화&lt;/li&gt;
&lt;li&gt;정보 중복&lt;/li&gt;
&lt;li&gt;정보 왜곡&lt;/li&gt;
&lt;/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;3️⃣ &lt;b&gt;단일 장애점 (SPOF)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 정보를 한 사람이 독접하면 병목이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단기 효율은 높여주는 대신 장기 확장성을 희생하는 꼴입니다.&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;4번으로 이어집니다.&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;4️⃣ &lt;b&gt;전부 아니면 전무 전문성&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직 구성원이 &lt;code&gt;모든 것을 아는 사람&lt;/code&gt;과 &lt;code&gt;아무것도 모르는 초심자&lt;/code&gt;로 나뉨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문가들은 항상 모든 일을 자신들이 처리하게 되고 새로운 전문가를 키우기 위한 여력이 줄어들음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지식과 책임은 계속 이미 전문가가 된 사람들에게 집중되고 새로운 팀원이나 초심자들은 그들만의 울타리에 갇혀 느리게 성장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5️⃣ &lt;b&gt;앵무새처럼 흉내내기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하지 못한 상태로 흉내만 내는 것을 말함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적을 이해하지 못하고 무의식적으로 기존 패턴이나 코드를 따라함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6️⃣ &lt;b&gt;유령의 묘지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무언가 잘못될 게 두려워서 아무도 손대지 않는 영역(주로 코드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두려움과 비합리적인 의심 때문에 사람들이 손대기를 기피하는 영역임&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3.2 철학&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기록된 지식은 확장성이 좋지만 사람이 해주는 맞춤형 도움도 장점이 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문가는 특정 개인에게 딱 맞는 정보가 무엇인지 가늠하여 문서에 적힌 내용이 적절한지 그리고 그 내용을 어디에서나 찾을 수 있는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현장 지식과 문서화된 지식은 서로를 보완해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 형태의 배움에 최고인 유일무이한 지식 공유법은 존재하지 않으며 어떻게 조합하는 게 최선일지는 조직에 따라 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3.3 판 깔아주기: 심리적 안전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리적 안전은 학습 환경을 조성하는 데 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 자신이 이해하지 못한 게 있음을 인정해야 무언가를 배울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 우리 모두는 타인의 무지를 탁하지 말고 그 솔직함을 반겨야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배움에는 &lt;code&gt;무언가를 시도하다가 실패해도 안전하다&lt;/code&gt;는 인식이 엄청나게 중요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;건강한 환경에서라면 사람들은 질문을 던지고, 틀리고, 새로운 지식을 얻는 걸 편안하게 생각합니다.&lt;/li&gt;
&lt;li&gt;심리적 안전이 효과적인 팀을 이루는 데 가장 중요한 요인이었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3.4 내 지식 키우기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3.4.1 질문하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초심자가 저지르는 가장 큰 실수는 무언가 막혔을 때 질문하지 않는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혼자서 극복해내고 싶다거나 너무 &lt;code&gt;기초적인&lt;/code&gt; 질문이란 소리를 듣는게 두려워서일 수 있습니다.&lt;/li&gt;
&lt;li&gt;혹은 &lt;code&gt;도움을 청하기 전에 최대한 노력해봐야 해&lt;/code&gt;라고 생각할지 모릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분의 동료가 가장 휼륭한 정보 소스일 경우가 많습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 귀중한 자원을 충분히 활용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서 몇 년을 일한 엔지니어라도 어떻게 해야 할지 모르는 영역이 존재하며 전혀 문제 될 일이 아닙니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;이게 뭔지 모르겠는데, 설명 좀 해주시겠어요?&lt;/code&gt;라고 말하는 걸 두려워하지 마세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는 분야가 나오면 두려워하지 말고 성장하는 기회로 받아들이세요  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 무언가 배울 게 있는 환경에서 살아야 합니다. 그렇지 않으면 더 이상 성장하지 못할 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 많이 알면 알수록 모르는 것이 더 많음을 깨닫게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공개적으로 묻고 모르면 모른다고 인정한다면 다른 사람들도 점점 그렇게 변해갈 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기의 망설임을 극복하면 분위기를 빠르게 조성할 수 있으니 질문을 하도록 권장하고 &lt;b&gt;&amp;ldquo;사소한&amp;rdquo;&lt;/b&gt; 질문이라도 답을 얻을 수 있도록 힘써주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3.4.2 맥락 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 것을 이해하는 일뿐 아니라 기존 설계와 구현을 뒷받침하는 결정 사항들을 더 깊이 이해하는 일도 배움에 포함됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특히 정상이 아니라고 보이는 결정에 대해서는 먼저 맥락을 찾아 이해해야 합니다.&lt;/li&gt;
&lt;li&gt;우선 코드의 목적과 맥락을 이해하고 그런 다음 변경하려는 방향이 여전히 더 나은지 고민해야 합니다.&lt;/li&gt;
&lt;li&gt;더 낫다고 판단되면 고치고 그렇지 않다면 미래에 다시 그 코드를 살펴볼 후임들을 위해 여러분이 생각한 근거를 적어두세요.&lt;/li&gt;
&lt;/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;  3.6 지식 확장하기: 누구나 가르칠 게 있다.&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3.6.3 문서자료&lt;/h3&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;문서자료의 실수나 빠진 부분을 발견한다면 곧바로 고치세요!&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스스로 새로운 개발 흐름을 만들어냈다면 그 단계들을 설명하는 문서자료를 작성하세요.&lt;/li&gt;
&lt;li&gt;다른 사람에게 공유하여 여러분이 걸어간 길을 쉽게 따라오도록 하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문서화 촉진하기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔지니어들에게 각자가 한 일을 문서화하도록 장려하기가 쉽지는 않을 것입니다.&lt;/li&gt;
&lt;li&gt;문서자료를 작성하려면 시간과 노력이 드는데 코딩할 시간에서 뺏어와야 하기 때문&lt;/li&gt;
&lt;li&gt;문서자료로 정리하려면 시간을 좀 투자해야 하지만 한 번만 해두면 다음부터는 시간을 절약할 수 있습니다.&lt;/li&gt;
&lt;li&gt;문서화는 팀과 조직의 규모를 키우는 데도 보탬이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3.7 조직의 지식 확장하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3.7.1 지식 공유 문화 일구기&lt;/h3&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;기술 업계에서는 &amp;ldquo;뛰어난 괴짜&amp;rdquo;를 용인하는(심지어 숭배하는) 경향이 있지만, 해로운 현상입니다. 상냥한 전문가도 얼마든지 가능하기 때문이죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글의 소프트웨어 엔지니어링 직무 사다리에서 리더십 항목을 찾아보면 다음 내용을 명확히 밝히고 있습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;높은 수줄의 기술 리더십을 요구하지만 모든 리더십이 기술 문제와 관련된 것은 아닙니다. 
리더는 주변 사람들을 성장시키고, 팀의 심리적 안전을 개선하고, 팀워크와 협업 문화를 조성하고, 
팀 내 긴장을 해소하고, 구글 문화의 가치를 설정하며, 구글을 더 활기차고 신나는 일터로 가꿔야 합니다. 괴짜는 좋은 리더가 아닙니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보상과 인정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동료의 업적을 인정해주는 공식적이고 손쉬운 제도는 직원들에게 계속해서 이타적이고 멋진 일들을 하도록 강한 동기를 부여합니다.&lt;/li&gt;
&lt;li&gt;상여금보다는 동료를 인정해준다는 점이 더 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3.8 가독성 제도: 코드 리뷰를 통한 표준 멘토 제도&lt;/h2&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;프로그래밍 언어 모범 사례를 전파하기 위한 구글 전사 차원의 &amp;ldquo;표준 멘토링 프로세스&amp;rdquo;를 지칭하죠.&lt;/li&gt;
&lt;li&gt;오늘날에는 구글 엔지니어의 약 20%가 리뷰어 혹은 코드 작성자가 되어 가독성 인증 프로세스에 참여하고 있습니다.&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;  3.8.1 가독성 인증 프로세스란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서 코드 리뷰는 필수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 변경 목록은 가독성 승인을 얻어야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가독성 승인이란 해당 언어의 가독성 자격증이 있는 누군가가 해당 CL을 승인했다는 표시입니다.&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;  3.8.2 가독성 인증 프로세스를 두는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 들이는 비용에 비해 얻어지는 이익이 더 많은가입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가독성 제도는 코드 리뷰가 길어진다는 단기적인 비용 과 코드 품질 개선, 리포지터리 차원의 코드 일관성 향상, 엔지니어 전문성 향상에서 절약하는 장기적인 비용을 의식적으로 맞바꾸는 제도입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혜택의 시간 척도를 길게 잡을수록 코드의 기대 수명이 길다는 뜻이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  3장을 읽고나서,,,&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 질문하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 장에서 인상깊었는 내용 중 하나는 아래 내용이였어요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초심자가 저지르는 가장 큰 실수는 무언가 막혔을 때 질문하지 않는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자서 극복해내고 싶다거나 너무 기초적인 질문이란 소리를 듣는게 두려워서일 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 구글 엔지니어들조차 모르는 게 많아 서로 사소한 것도 질문한다는 점을 보고 내가 질문하지 않는 것은 오히려 더 비효율적이고 바람직하지 않다는 것을 알게 되었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;질문 자체가 학습의 출발점&lt;/b&gt;이며 질문하지 않으면 더 크게 성장의 기회를 놓칠 수도 있을 것이라 생각이 들었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 앞으로는 막혔을 때 충분히 고민한 뒤 주저하지 않고 공유하고 질문을 던져 그것을 더 큰 학습의 기회로 삼아야겠다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 문서자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 학습을 한 것, 구현과정에서 발생한 작업들을 문서화를 하느라 구현 시간이 줄어드는 것 같아 고민이 있었지만 그것 또한 개발자로서의 중요한 과정이라는 것을 다시 느낄 수 있었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 문서화와 구현 사이에서 균형을 유지하면서 핵심 내용을 먼저 기록하는 방식으로 개선해야겠다고 생각했어요..!&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000061352347&quot;&gt;https://product.kyobobook.co.kr/detail/S000061352347&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/ &amp;zwj;  구글 엔지니어는 이렇게 일한다</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/180</guid>
      <comments>https://beomsic.tistory.com/entry/%F0%9F%A7%91%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-%EA%B5%AC%EA%B8%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%8A%94-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9D%BC%ED%95%9C%EB%8B%A4-3-%EC%A7%80%EC%8B%9D%EA%B3%B5%EC%9C%A0#entry180comment</comments>
      <pubDate>Wed, 17 Sep 2025 00:30:42 +0900</pubDate>
    </item>
    <item>
      <title>Logging Library</title>
      <link>https://beomsic.tistory.com/entry/Logging-Library</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;Logging의 이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣&amp;nbsp;&lt;b&gt;디버깅 및 문제해결에 도움&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그는 애플리케이션의 상태, 동작에 대한 자세한 정보를 제공해 문제를 더 쉽게 식별하고 해결하는데 도움을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣&amp;nbsp;&lt;b&gt;성능 모니터링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답시간, 리소스 활용도 등의 성능 측정 항목을 추적하는데 도움을 주어 개발자가 애플리케이션을 최적화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;보안 모니터링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 사용해 잘못된 요청이나 데이터에 무단으로 엑세스 시도하는 보안관련 이벤트를 모니터링할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣&amp;nbsp;&lt;b&gt;운영시 정보 수집&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 행동 및 시스템 상태 등에 대한 정보를 수집해서 이용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pageview 등&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;✅&amp;nbsp;Node.js 로깅의 Best Practices&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js에서 로깅을 할 때 따르면 좋을 best practices를 알아보려고 합니다.&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️⃣&amp;nbsp;안정된 로깅 프레임워크 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔로 로그를 작성하는 것이 아닌 Node.js 애플리케이션에 로깅 기능을 제공하는 안정된 로깅 프레임워크를 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Winston :다양한 전송 방식(예: 콘솔, 파일, HTTP)을 지원하고 널리 사용되는 로깅 라이브러리입니다.&lt;/li&gt;
&lt;li&gt;Bunyan&lt;b&gt;:&lt;/b&gt; 로그 분석을 위한 내장 CLI 도구가 포함된 빠르고 간단한 JSON 로깅 라이브러리입니다.&lt;/li&gt;
&lt;li&gt;Pino&lt;b&gt;:&lt;/b&gt; 프로덕션 환경에 적합한 고성능 로깅 라이브러리입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣&amp;nbsp;구조화된 로깅 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화되지 않은 로그(사람이 읽을 수 있는 문자열)는 우리에게는 직관적일 수 있지만 기계가 처리하는데는 어려움이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;image 'file.jpg' was uploaded successfully
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 로그가 계속 발생한다면 파싱을 하는데 매우 복잡한 로직이 필요하고 자동화를 하는데 있어서 일관성이 부족할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 구조화된 형식을 사용하면 로그 데이터를 정리하는데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;level&quot;: &quot;info&quot;,
  &quot;timestamp&quot;: &quot;2023-10-25T07:12:46.743Z&quot;,
  &quot;filename&quot;: &quot;file.jpg&quot;,
  &quot;msg&quot;: &quot;image 'file.jpg' was uploaded successfully&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 JSON이 널리 사용되는 구조화된 형식입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LogFmt 와 같은 대안도 존재&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;b&gt;구조화된 로그의 단점 - 가독성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발중에 쉽게 사람이 더 읽기 쉬운 형식으로 출력하도록 프레임워크를 구성하고 운영환경에서는 구조화된 출력을 보존하는 방법을 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pino-prettey 같은 추가 도구를 사용해 로그를 더 이쁘게 꾸밀 수도 있습니다.&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;3️⃣&amp;nbsp;로그 Level을 올바르게 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 level은 로그의 중요도와 긴급도에 따라 분류합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;디버깅을 위한 자세한 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;td&gt;애플리케이션 이벤트에 대한 일반 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warn&lt;/td&gt;
&lt;td&gt;당장은 중요하지 않은 잠재적인 문제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;주의가 필요한 오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fatal&lt;/td&gt;
&lt;td&gt;조기 종료를 발생시키는 심각한 오류&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 level을 사용하면 로그 메시지를 필터링하고 우선순위를 지정하는 데 도움이 되므로 중요한 문제에 집중하기가 더 쉽습니다.&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;4️⃣&amp;nbsp;자세한 로그 메시지를 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그가 의미있는 정보를 제공하기 위해서는 발생한 이벤트에 대한 자세한 정보를 포함하는 것이 중요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불필요하거나 중복되는 정보는 포함하지 않기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;사용자 인증에 실패했습니다.(어떤 사용자?)
오류가 발생했습니다(너무 일반적임).
&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;pre class=&quot;prolog&quot;&gt;&lt;code&gt;사용자 'USR-1234'에 대한 인증에 실패했습니다.
주문 ID: 34567에 대한 결제 처리 중 예기치 않은 오류가 발생했습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 자세한 메시지를 제공하면 로그를 훨씬 더 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣&amp;nbsp;항상 타임스탬프를 포함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 내용에 타임스탬프를 작성하는 것은 로그의 맥락을 파악하고 정리하는데 매우 중요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최근 이벤트와 며칠전 이벤트를 구분할 수 없다면 로그를 탐색하는 것이 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 로깅 프레임워크는 기본으로 로그 출력에 타임스탬프를 포함합니다.&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;6️⃣&amp;nbsp;로그 항목에 상황별 필드를 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 세부정보를 로그 항목에 추가하는 것을 통해 발생한 이벤트를 전체적으로 파악하고 다양한 시스템에서 발생한 요청, 트랜잭션등을 추적하는 것이 더 쉬워집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 요청 ID, 사용자 ID 와 같은 고유 식별자를 추가하면 로그의 명확성을 높이고 상호 연관 관계를 더 쉽게 파악가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;level&quot;: 30,
  &quot;timestamp&quot;: &quot;2023-10-25T01:06:06.783Z&quot;,
  &quot;userid&quot;: 605689,
  &quot;username&quot;: &quot;fedora&quot;,
  &quot;requestID&quot;: &quot;f9ed4675f1c53513c61a3b3b4e25b4c0&quot;,
  &quot;msg&quot;: &quot;Uploading image.png was successful&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7️⃣&amp;nbsp;스택 추적을 사용해 예기치 않은 오류를 기록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exception 로깅의 주요 목적 중 하나는 문제의 원인을 빠르게 파악하기 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류 메시지를 확인하면 무엇이 잘못되었는지 충분히 파악할 수 없지만 스택 추적을 통해 오류가 발생한 정확한 위치를 파악하면 문제를 더 빠르게 파악하고 수정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;level&quot;: 50,
  &quot;time&quot;: &quot;2022-06-15T21:23:04.436Z&quot;,
  &quot;pid&quot;: 285659,
  &quot;hostname&quot;: &quot;fedora&quot;,
  &quot;err&quot;: {
    &quot;type&quot;: &quot;Error&quot;,
    &quot;message&quot;: &quot;an error&quot;,
    &quot;stack&quot;: &quot;Error: an error\\n    at Object.&amp;lt;anonymous&amp;gt; (/home/ayo/dev/betterstack/betterstack-community/demo/snippets/main.js:23:9)\\n &quot;
  },
  &quot;msg&quot;: &quot;an error&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8️⃣&amp;nbsp;민감한 데이터가 로그에 포함되지 않도록 합니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그에는 비밀번호, token 같은 데이터가 포함되어서는 안됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경우에 따라 IP 주소도 개인 식별 정보로 간주될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9️⃣&amp;nbsp;문제 해결 목적 이상의 로그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅은 문제해결뿐 아니라 사용자 행동에 대한 정보 수집, 모니터링 등 중요한 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 것들을 이용해서 서비스 개발 방향에 큰 영향을 미칠 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능 지표를 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;항상 표준 출력에 기록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 프레임워크는 로그 출력 대상을 다양하게 제공해주고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 대상(HTTP endpoint, database 등)으로 로그를 전송할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 옵션들에도 불구하고 애플리케이션 로그를 표준 출력으로 보내는 것이 일반적으로 가장 좋은 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣1️⃣&amp;nbsp;로그 관리 시스템에서 로그를 중앙화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션이 출시되는 즉시 호스트 서버에 로그가 생성되기 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서버에 접속하여 로그를 확인하는 것은 소수의 사용자에게는 가능할 수 있지만 여러 서버에 걸쳐 접속하는 것은 금세 번거롭고 비효율적이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적의 전략은 모든 로그 데이터를 수집하여 한곳에 통합하는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자주 발생하는 이벤트나 오류 등에 사용자 정의 알림 구성&lt;/li&gt;
&lt;li&gt;조직의 다른 구성원과 원활하게 공유할 수 있습니다.&lt;/li&gt;
&lt;li&gt;애플리케이션 서버가 다운되더라도 로그 접근성을 보장합니다.&lt;/li&gt;
&lt;li&gt;로그 보존 정책을 준수하고 장기 보관을 보장하는 데 도움이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;b  Node.js의 Logging 라이브러리&lt;/h1&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 170px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Library&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;GitHub Stars&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Key Strength&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Best For&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Winston&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;20k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Highly configurable&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Complex apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Pino&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;12k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Performance-focused&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;High-throughput systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Bunyan&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;7k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;JSON logging&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Microservices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Morgan&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;9k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;HTTP request logging&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Express.js apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;debug&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;10k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Lightweight debugging&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Small projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;log4js&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;5k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Familiar API for Java devs&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Teams with Java background&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;loglevel&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Minimal but flexible&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Browser-compatible apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;roarr&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Serialization performance&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;High-scale services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;tracer&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1k+&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Colorized output&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Development environments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣&amp;nbsp;Winston&lt;/h3&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;/p&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;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 전송 및 포맷 옵션&lt;/li&gt;
&lt;li&gt;풍부한 플러그인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣&amp;nbsp;Pino&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pino&amp;nbsp;는 Node.js를 위한 구조화된 로깅 라이브러리로 기본적으로 JSON 출력을 생성하여 로그 데이터를 쉽게 검색, 모니터링, 시각화할 수 있습니다.&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;최소한의 오버헤드로 초당 10,000개 이상의 로그를 처리&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;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가볍고 빠르다.&lt;/li&gt;
&lt;li&gt;메모리가 효율적이게 설계되어 로그가 대량으로 생성되는 상황에서도 성능을 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣&amp;nbsp;Bunyan&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js에서&amp;nbsp;&lt;b&gt;JSON 로깅&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;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;각 로그 항목은 일관된 구조를 가진 JSON 객체이므로 자동화된 분석이 용이&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;4️⃣&amp;nbsp;Morgan&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Morgan은 HTTP 요청을 기록하도록 특별히 설계된 Node.js 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Express.js와 같은 웹 프레임워크에 미들웨어로 통합되어 모든 HTTP 요청을 추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 범용 로깅 도구와 달리 Morgan은 HTTP 요청 로깅에 중점을 둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.to/leapcell/a-deep-dive-into-nodejs-logging-tools-2f9f&quot;&gt;https://dev.to/leapcell/a-deep-dive-into-nodejs-logging-tools-2f9f&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@shivam.artoonsolution/node-js-logging-best-practices-and-tools-for-effective-application-monitoring-f5beadeb9b0f&quot;&gt;https://medium.com/@shivam.artoonsolution/node-js-logging-best-practices-and-tools-for-effective-application-monitoring-f5beadeb9b0f&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://last9.io/blog/node-js-logging-libraries/&quot;&gt;https://last9.io/blog/node-js-logging-libraries/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://betterstack.com/community/guides/logging/best-nodejs-logging-libraries/&quot;&gt;https://betterstack.com/community/guides/logging/best-nodejs-logging-libraries/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://betterstack.com/community/guides/logging/nodejs-logging-best-practices/&quot;&gt;https://betterstack.com/community/guides/logging/nodejs-logging-best-practices/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://betterstack.com/community/guides/logging/how-to-install-setup-and-use-pino-to-log-node-js-applications/&quot;&gt;https://betterstack.com/community/guides/logging/how-to-install-setup-and-use-pino-to-log-node-js-applications/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://betterstack.com/community/guides/logging/how-to-install-setup-and-use-winston-and-morgan-to-log-node-js-applications/&quot;&gt;https://betterstack.com/community/guides/logging/how-to-install-setup-and-use-winston-and-morgan-to-log-node-js-applications/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://getpino.io/#/&quot;&gt;https://getpino.io/#/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/winston&quot;&gt;https://www.npmjs.com/package/winston&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/179</guid>
      <comments>https://beomsic.tistory.com/entry/Logging-Library#entry179comment</comments>
      <pubDate>Tue, 16 Sep 2025 23:02:09 +0900</pubDate>
    </item>
    <item>
      <title>Typescript 컴파일러 옵션</title>
      <link>https://beomsic.tistory.com/entry/Typescript-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-%EC%98%B5%EC%85%98</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  타입스크립트 컴파일러 옵션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트의 컴파일과정에서는 작성한 코드에 타입 오류가 없는지 검사하고 오류가 없다면 자바스크립트 코드로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴파일 과정의 세부적인 사항들을 컴파일 옵션이라고 하고 개발자가 직접 설정하여 각자 원하는 방향으로 컴파일 옵션을 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;얼마나 엄격히 타입 오류를 검사할 것인지?&lt;/li&gt;
&lt;li&gt;컴파일 결과로 생성되는 자바스크립트 코드의 버전은 어떻게 할 것인지?&lt;/li&gt;
&lt;li&gt;etc..&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;code&gt;tsconfing.json&lt;/code&gt;에 작성하게 되고 이 파일은 &lt;b&gt;타입스크립트 프로젝트의 root에 존재합니다.&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;code&gt;tsconfig.json&lt;/code&gt;에서 옵션들을 미리 정의해 놓으면 컴파일 할때 명령어에 일일히 대상 파일이나 옵션을 지정하지 않아도 되어 &lt;code&gt;tsc&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt; 같은 명령어를 그냥 실행하면 현재 폴더에 있는 설정 내용을 기준으로 소스들을 컴파일하는 작업을 실행하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 폴더에 설정파일이 없다면 프로젝트내 상위 폴더로 검색해나가면서 찾아갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  컴파일러 옵션 자동 생성&lt;/h2&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;tsc --init&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;기본 설정이 완료된 tsconfig.json 파일이 생성됩니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;files&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에 포함할 허용 파일 목록을 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 찾을 수 없으면 오류가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {},
  &quot;files&quot;: [
    &quot;sys.ts&quot;,
    &quot;types.ts&quot;,
    &quot;scanner.ts&quot;,
    &quot;parser.ts&quot;,
    &quot;utilities.ts&quot;,
    &quot;binder.ts&quot;,
    &quot;checker.ts&quot;,
    &quot;tsc.ts&quot;
  ]
}&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;li&gt;files 속성은 밑에서 배울 exclude보다 우선순위가 높습니다.&lt;/li&gt;
&lt;li&gt;이 옵션이 생략되면 include와 exclude 속성으로 컴파일 대상을 결정하게 됩니다.&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;2️⃣ &lt;b&gt;extends&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 tsconfig.json 파일의 설정들을 가져와 재사용할 수 있게 해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tsconfig.json 파일의 최상위에 위치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// config/another.json
{
  &quot;compilerOptions&quot;: {
    &quot;strictNullChecks&quot;: true
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;extends&quot;: &quot;./configs/another&quot;,
  &quot;compilerOptions&quot;: {
    &quot;strictNullChecks&quot;: false
  },
  &quot;files&quot;: [
  ]
}&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;code&gt;extends&lt;/code&gt;된 설정보다 현재 파일의 설정을 우선시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ include&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;include 옵션은 tsc에게 컴파일 할 타입스크립트 파일의 범위와 위치를 알려주는 옵션입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 파일이 있을 때 &lt;code&gt;tsc&lt;/code&gt; 명령어를 각 파일에 입력하지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;files 속성과 처럼 컴파일할 파일들을 지정하는 속성이지만 &lt;code&gt;와일드 카드&lt;/code&gt; 패턴으로 지정해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;include&quot;: [&quot;src&quot;]
}&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;src 폴더 아래의 모든 타입스크립트 파일이 동시에 컴파일&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;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;tsc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7sphd/btsQDcBlda2/fyh28baBcinSxCdWlBMv81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7sphd/btsQDcBlda2/fyh28baBcinSxCdWlBMv81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7sphd/btsQDcBlda2/fyh28baBcinSxCdWlBMv81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7sphd%2FbtsQDcBlda2%2Ffyh28baBcinSxCdWlBMv81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;460&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; 디렉토리 안에 있는 타입스크립트 파일들이 컴파일되어 js 파일들이 생성되었습니다.&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;include에 명시되어 있어도 exclude에도 명시되어 있으면 제외됩니다.&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;4️⃣ &lt;b&gt;exclude&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 대상에서 제외할 파일들을 지정하는 속성&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;exclude&quot;: [
  &quot;node_modules&quot;, // node_modules 제외
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣ &lt;b&gt;compilerOptions&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 대상 파일들을 어떻게 변환할지 세세히 정하는 옵션이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;compilerOptions&quot; 속성은 생략될 수 있으며 생략시 기본 값이 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;와일드카드 패턴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;*&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;0개 이상의 문자와 일치&lt;/li&gt;
&lt;li&gt;단, &lt;b&gt;디렉토리 구분 기호(예: / 또는 )는 포함하지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;즉, 해당 디렉토리에 있는 모든 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;?&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;**&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;0개 이상의 디렉토리 이름과 일치 모든 중첩 경로 포함&lt;/li&gt;
&lt;li&gt;여러 계층에 걸쳐 중첩된 디렉토리도 모두 포함함&lt;/li&gt;
&lt;li&gt;즉, 해당 디렉토리의 하위 디렉토리의 모든 파일을 포함&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;src/**/index.js&lt;/code&gt;는 &lt;code&gt;src/index.js&lt;/code&gt;, &lt;code&gt;src/app/index.js&lt;/code&gt;, &lt;code&gt;src/app/test/index.js&lt;/code&gt; 모두에 일치&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;b&gt;compilerOptions 옵션 정리&lt;/b&gt;&lt;/h2&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;target 옵션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 결과 생성되는 자바스크립트 코드의 버전을 설정하는 옵션&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES5&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;const func = () =&amp;gt; console.log(&quot;Hello, World!&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;var func = function () {
  return console.log('Hello, World!');
};&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;target 옵션을 ES5로 설정했기 때문에 &lt;b&gt;컴파일 과정&lt;/b&gt;에서 화살표 함수 같은 ES6의 문법이 ES5의 문법으로 변환&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;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;compilerOptions&quot;: {
  &quot;target&quot;: &quot;ESNext&quot;
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴파일 결과&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;const func = () =&amp;gt; console.log('Hello, World!');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;module 옵션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환되는 자바스크립트 코드의 모듈 시스템을 설정하는 옵션&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ESNext&quot;,
    &quot;module&quot;: &quot;CommonJS&quot;
  },
  &quot;include&quot;: [&quot;src&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;]
}&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 옵션을 추가한 후 CommonJS를 설정&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;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// test.js
export const test = () =&amp;gt; {
  console.log(&quot;test&quot;);
};

--------------
// index.ts
import { test } from './test';
console.log(test);&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// test.js
&quot;use strict&quot;;
Object.defineProperty(exports, &quot;__esModule&quot;, { value: true });
exports.test = void 0;
const test = () =&amp;gt; {
    console.log('test');
};
exports.test = test;

// index.js
&quot;use strict&quot;;
Object.defineProperty(exports, &quot;__esModule&quot;, { value: true });
const test_1 = require(&quot;./test&quot;);
console.log(test_1.test);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 결과 &lt;code&gt;require&lt;/code&gt;나 &lt;code&gt;exports&lt;/code&gt; 등의 CommonJS 문법으로 코드가 변환된 것을 확인할 수 있습니다.&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;outDir&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 결과 생성할 자바스크립트 코드의 위치를 결정하는 옵션&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ESNext&quot;,
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;outDir&quot;: &quot;dist&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc를 이용해 컴파일 하면 이제 컴파일 결과가 dist 폴더에 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ZyI0/btsQCFcGLWt/ctq813Jc6SXVEOYTFp5whk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ZyI0/btsQCFcGLWt/ctq813Jc6SXVEOYTFp5whk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ZyI0/btsQCFcGLWt/ctq813Jc6SXVEOYTFp5whk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ZyI0%2FbtsQCFcGLWt%2Fctq813Jc6SXVEOYTFp5whk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;415&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ &lt;b&gt;strict&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러의 타입 검사 엄격함 수준을 정하는 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전의 test.ts를 수정했습니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;export const test = (message) =&amp;gt; {
  console.log('test ' + message);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;strict 옵션을 true로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    ...
    &quot;strict&quot;: true
  },
  &quot;include&quot;: [&quot;src&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;code&gt;test.ts&lt;/code&gt;를 살펴보면 오류가 있다는 것을 알려줍니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEzCy/btsQAOPnkhK/MmL4PsNtkcdrPRDY9Hjp9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEzCy/btsQAOPnkhK/MmL4PsNtkcdrPRDY9Hjp9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEzCy/btsQAOPnkhK/MmL4PsNtkcdrPRDY9Hjp9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEzCy%2FbtsQAOPnkhK%2FMmL4PsNtkcdrPRDY9Hjp9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;114&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;strict&lt;/b&gt; 옵션을 true로 설정하면 이제 코드를 아주 엄격하게 검사하기 때문입니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서는 매개변수의 타입은 프로그래머가 직접 지정하도록 권장.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입을 직접 지정하지 않을 경우 엄격한 타입 검사 모드에서는 오류가 발생&lt;/li&gt;
&lt;li&gt;타입스크립트는 함수 매개변수의 타입을 자동추론할 수 없기 때문입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;ModuleDetection 옵션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트의 모든 파일은 기본적으로 전역 파일(모듈)로 취급됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;code&gt;a.ts&lt;/code&gt;와 &lt;code&gt;b.ts&lt;/code&gt; 두 타입스크립트 파일에서 동일한 이름의 변수를 선언하면 오류가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// a.ts
const a = 1; // ❌

// b.ts
const a = 1; // ❌&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때에는 각 파일에 모듈 시스템 키워드(export, 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;code&gt;moduleDetection&lt;/code&gt; 옵션입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;moduleDection 옵션을 force로 설정할 경우 자동으로 모든 타입스크립트 파일이 로컬 모듈로 취급&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    ...
        &quot;moduleDetection&quot;: &quot;force&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;sourceMap&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일된 파일 디렉터리에 &lt;code&gt;.js.map&lt;/code&gt; 파일이 만들어져 변환된 js 코드가 ts의 어디에 해당하는지를 알려줍니다.&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    ...
    &quot;sourceMap&quot;: true
  },
  &quot;include&quot;: [&quot;src&quot;]
}
&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf6TSk/btsQziwUxRO/WDMVkKsfzg7S1QkYccNLwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf6TSk/btsQziwUxRO/WDMVkKsfzg7S1QkYccNLwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf6TSk/btsQziwUxRO/WDMVkKsfzg7S1QkYccNLwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf6TSk%2FbtsQziwUxRO%2FWDMVkKsfzg7S1QkYccNLwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;273&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&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;타입스크립트 프로젝트를 배포후 source 탭을 확인해보면 브라우저는 js만 인식하지만 이 매핑 파일에 의해 ts 파일을 인식합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#handbook-content&quot;&gt;https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#handbook-content&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/tsconfig/&quot;&gt;https://www.typescriptlang.org/tsconfig/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://typescript-kr.github.io/pages/compiler-options.html&quot;&gt;https://typescript-kr.github.io/pages/compiler-options.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/TS-&quot;&gt;https://inpa.tistory.com/entry/TS-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-tsconfigjson-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot;&gt; -타입스크립트-tsconfigjson-설정하기-총정리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts.winterlood.com/e7ec2f43-9d8c-4d30-bb2c-29e1b57f6a39&quot;&gt;https://ts.winterlood.com/e7ec2f43-9d8c-4d30-bb2c-29e1b57f6a39&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  타입스크립트</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/178</guid>
      <comments>https://beomsic.tistory.com/entry/Typescript-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-%EC%98%B5%EC%85%98#entry178comment</comments>
      <pubDate>Tue, 16 Sep 2025 22:56:40 +0900</pubDate>
    </item>
    <item>
      <title>Node.js 환경에서 타입스크립트 설치하기</title>
      <link>https://beomsic.tistory.com/entry/Nodejs-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Typescript를 처음 사용해보면서 Node.js 환경에서 Typescript를 설치하고 이를 실행해보는 과정을 정리하고자 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  타입스크립트 컴파일러 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 컴파일러에 의해서 자바스크립트로 변환한 다음에 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 하기 위해 타입스크립트 패키지를 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install -g typescript&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;node_modules&lt;/code&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;code&gt;tsc&lt;/code&gt;, &lt;code&gt;tsserver&lt;/code&gt; 가 설치된 것을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdcuCj/btsQBmFgBQm/stn11cLXM9ryVBcuSGxHf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdcuCj/btsQBmFgBQm/stn11cLXM9ryVBcuSGxHf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdcuCj/btsQBmFgBQm/stn11cLXM9ryVBcuSGxHf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdcuCj%2FbtsQBmFgBQm%2Fstn11cLXM9ryVBcuSGxHf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;270&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;466&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;@types/node 설치하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;npm install @types/node&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@types/node&lt;/b&gt;&lt;/span&gt;는 Node.js API(내장 모듈, 예: fs, path, http 등)에 대한 TypeScript 타입 정의 패키지입니다.&lt;/p&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;이 때 타입이 선언되지 않은 코드를 만나게 되면 오류가 발생하게 되는데 Node.js 기본 기능 역시 타입 검사를 하기 때문에 이 기본 기능들의 타입을 별도로 선언하기 위해 이 패키지를 설치해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;process&lt;/code&gt;, &lt;code&gt;Buffer&lt;/code&gt; 등의 타입 인식 오류 없이 개발할 수 있도록 지원&lt;/li&gt;
&lt;li&gt;Node.js가 제공하는 기본 기능(내장 함수, 클래스 등)에 대한 타입 정보를 가지고 있습니다.&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;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;node_modules
- @types/node&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;node_modules&lt;/code&gt; 폴더에 @types 폴더가 생성&lt;/p&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;node_modules/@types/console.d.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;declare module &quot;console&quot; {
    import console = require(&quot;node:console&quot;);
    export = console;
}
declare module &quot;node:console&quot; {
    import { InspectOptions } from &quot;node:util&quot;;
    global {
        // This needs to be global to avoid TS2403 in case lib.dom.d.ts is present in the same build
        interface Console {&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;이와같은 Node.js가 기본적으로 제공하는 기능들에 대한 타입이 선언되어 있는 파일들이 존재&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;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 실행할 타입스크립트 파일 생성&lt;/h3&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;// index.ts
const message: String = 'Hello, World!';

console.log(message);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ tsc로 컴파일후 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러를 이용해 이 타입스크립트 코드를 컴파일&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;&amp;gt; tsc index.ts&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;위 명령어를 사용하면 ts 파일이 js 파일로 변환이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;// index.js
var message = 'Hello, World!';
console.log(message);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 컴파일된 JS 파일 실행&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;node index.js

----------결과------------
Hello, World!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  TS-Node 이용해 실행하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 타입스크립트 코드를 실행하기 위해 &lt;code&gt;tsc&lt;/code&gt;를 이용해 타입스크립트 코드를 자바스크립트로 변환하고&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 너무 귀찮은 작업이기 때문에 이번에는 명령어 한번으로 타입스크립트 코드를 바로 실행시켜주는 도구 &lt;code&gt;ts-node&lt;/code&gt;를 적용해서 실행해보고자 합니다.&lt;/p&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;ts-node&lt;/code&gt;를 설치해 &lt;code&gt;ts-node&lt;/code&gt;명령어를 통해 타입스크립트를 컴파일 단계없이 실행할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;ts-node&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JIT을 통해서 typescript를 javascript로 변환하여 사전 컴파일없이 Node.js에서 TypeScript를 직접 실행할 수 있도록 합니다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;npm install -g ts-node&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;ts-node index.ts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;문제 발생&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;&amp;gt; ts-node index.ts
TypeError: Unknown file extension &quot;.ts&quot;&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;code&gt;.ts&lt;/code&gt; 확장자를 인식못하는 문제가 발생했어요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  시도한 해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;package.json&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;module&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;2️⃣ &lt;b&gt;tsconfig.json&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// tsconfig.json
{
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;esModuleInterop&quot;: true
  },
  &quot;ts-node&quot;: {
    &quot;esm&quot;: true,
    &quot;experimentalSpecifier&quot;: true
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESM 환경이기 때문에 &lt;b&gt;&quot;module&quot;: &quot;ESNext&quot;&lt;/b&gt;로 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;&quot;ts-node&quot;: {
    &quot;esm&quot;: true,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이렇게 설정한 후 다시 시도!! &amp;rArr; 결과는 같았습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Reddit 댓글과 인프런 커뮤니티 글&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 해결방법을 찾고자 구글링을 하다 여러 글을 확인해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3oq8/btsQB5Xf8Wm/XMDuORkBiyIAPmfLzWiJT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3oq8/btsQB5Xf8Wm/XMDuORkBiyIAPmfLzWiJT1/img.png&quot; data-alt=&quot;https://www.inflearn.com/news/1065346&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3oq8/btsQB5Xf8Wm/XMDuORkBiyIAPmfLzWiJT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3oq8%2FbtsQB5Xf8Wm%2FXMDuORkBiyIAPmfLzWiJT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;79&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.inflearn.com/news/1065346&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCnOI4/btsQCrliejw/mKnkZx3MTbNlNLpHjZkTY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCnOI4/btsQCrliejw/mKnkZx3MTbNlNLpHjZkTY0/img.png&quot; data-alt=&quot;https://www.reddit.com/r/typescript/comments/17vvlt7/tsnodedev_not_work_with_esm/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCnOI4/btsQCrliejw/mKnkZx3MTbNlNLpHjZkTY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCnOI4%2FbtsQCrliejw%2FmKnkZx3MTbNlNLpHjZkTY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;93&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.reddit.com/r/typescript/comments/17vvlt7/tsnodedev_not_work_with_esm/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d940Ig/btsQDeTszLy/mk4fs1GDhy38nM2aezYaB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d940Ig/btsQDeTszLy/mk4fs1GDhy38nM2aezYaB1/img.png&quot; data-alt=&quot;https://www.reddit.com/r/typescript/comments/17vvlt7/tsnodedev_not_work_with_esm/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d940Ig/btsQDeTszLy/mk4fs1GDhy38nM2aezYaB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd940Ig%2FbtsQDeTszLy%2Fmk4fs1GDhy38nM2aezYaB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;230&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.reddit.com/r/typescript/comments/17vvlt7/tsnodedev_not_work_with_esm/&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;여기서 2가지의 접근 방법을 확인할 수 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tsx&lt;/li&gt;
&lt;li&gt;vite-node&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;TSX&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;esbuild 기반의 TypeScript 실행기로써 ts-node의 대안&lt;/li&gt;
&lt;li&gt;속도가 매우 빠르며 ESM/CommonJS 모두 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TSX 사용 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 설치
npm i -D tsx

# 실행
npx tsx [파일명]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Vite-node&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;Vite 기반의 TypeScript,JavaScript 실행기. Vite의 변환 파이프라인을 활용한다.&lt;/li&gt;
&lt;li&gt;네이티브 ESM을 지원하며 Vite의 플러그인 생태계를 활용 가능하다.&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;vite-node 사용 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 설치
npm i -D vite-node

# 실행
npx vite-node [파일명]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 ESM을 사용해서 프로젝트를 진행하고 있으며 아직 Vite에 대해 제대로 학습하지 않은 상태라 &lt;code&gt;vite-node&lt;/code&gt; 보다는 &lt;code&gt;tsx&lt;/code&gt;를 사용하기로 했습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 결과&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;npx tsx index.ts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/12rNb/btsQBkAFHMp/7FlGUOIKKLqn4vcbHscE2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/12rNb/btsQBkAFHMp/7FlGUOIKKLqn4vcbHscE2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/12rNb/btsQBkAFHMp/7FlGUOIKKLqn4vcbHscE2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F12rNb%2FbtsQBkAFHMp%2F7FlGUOIKKLqn4vcbHscE2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1370&quot; height=&quot;80&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 없이 실행이 된 것을 확인했습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/typescript-tooling-in-5-minutes.html&quot;&gt;https://www.typescriptlang.org/ko/docs/handbook/typescript-tooling-in-5-minutes.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://typestrong.org/ts-node/docs/&quot;&gt;https://typestrong.org/ts-node/docs/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/tsconfig/&quot;&gt;https://www.typescriptlang.org/ko/tsconfig/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nodejs.org/api/deprecations.html#DEP0180&quot;&gt;https://nodejs.org/api/deprecations.html#DEP0180&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/news/1065346&quot;&gt;https://www.inflearn.com/news/1065346&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/typescript/comments/17vvlt7/tsnodedev_not_work_with_esm/&quot;&gt;https://www.reddit.com/r/typescript/comments/17vvlt7/tsnodedev_not_work_with_esm/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  타입스크립트</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/177</guid>
      <comments>https://beomsic.tistory.com/entry/Nodejs-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0#entry177comment</comments>
      <pubDate>Tue, 16 Sep 2025 22:49:29 +0900</pubDate>
    </item>
    <item>
      <title>  타입스크립트 개요</title>
      <link>https://beomsic.tistory.com/entry/%F0%9F%93%96-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EC%9A%94</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  타입스크립트란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;TypeScript&lt;/b&gt;&lt;/span&gt;는 Microsoft에서 개발한 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;JavaScript&lt;/span&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;b&gt;정적 타입 검사를 지원&lt;/b&gt;하여 코드 실행 전 오류를 미리 발견할 수 있습니다.&lt;/li&gt;
&lt;li&gt;최신 ECMAScript 표준(ES6+) 기능도 지원하며 기존 JavaScript 코드와 완전 호환됩니다.&lt;/li&gt;
&lt;li&gt;타입스크립트 코드는 반드시 컴파일러가 JavaScript로 변환(트랜스파일)되어 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;TypeScript&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= JavaScript + Type 문법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JavaScript Superset&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh6JzM/btsQAFFzefQ/fHyBPyMWwKR90y1DBqiDi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh6JzM/btsQAFFzefQ/fHyBPyMWwKR90y1DBqiDi1/img.png&quot; data-alt=&quot;https://ts.winterlood.com/7250edd7-a3fd-4662-b756-f11f927c73f2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh6JzM/btsQAFFzefQ/fHyBPyMWwKR90y1DBqiDi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh6JzM%2FbtsQAFFzefQ%2FfHyBPyMWwKR90y1DBqiDi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;371&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ts.winterlood.com/7250edd7-a3fd-4662-b756-f11f927c73f2&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;빠른 사용을 위해 설계되었으며 어플리케이션들을 작성하기 위해 JavaScript를 사용하지만 모든 언어는 이상한 점과 놀랄만한 점이 있으며 JavaScript 도 역시 많은 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  왜 타입스크립트를 사용?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;자바스크립트의 문제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;if ('' == 0) {
  console.log('true');
}
let x = '';
if (1 &amp;lt; x &amp;lt; 3) {
  console.log('x');
}

console.log(5 - '3');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;자바스크립트는 동적 타이핑(Dynamic Typing), 타입 강제 변환(Type Coercion)을 제공해주는 언어이기 때문에 가능!&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 점들은 자유도가 높고 유연성을 제공해주지만 프로젝트가 커질수록 단점이 됩니다.&lt;/li&gt;
&lt;li&gt;프로그램의 안정성을 떨어뜨리는 단점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트를 사용하게 되면 이런 것들을 에러로 잡아줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입스크립트는 타입을 엄격히 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;에러 메시지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 에러가 발생했을 때 에러메시지를 추상적으로 알려주는데 타입스크립트는 타입을 엄격히 검사하기 때문에 정확하게 에러메시지를 나타내줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SKYXY/btsQA7HXl3I/pbRE9K0yKcFeKYwZpP7sKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SKYXY/btsQA7HXl3I/pbRE9K0yKcFeKYwZpP7sKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SKYXY/btsQA7HXl3I/pbRE9K0yKcFeKYwZpP7sKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSKYXY%2FbtsQA7HXl3I%2FpbRE9K0yKcFeKYwZpP7sKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;660&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  동작 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;코드 작성&lt;/b&gt;: &lt;code&gt;.ts&lt;/code&gt; 또는 &lt;code&gt;.tsx&lt;/code&gt; 파일로 코드를 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;컴파일러(tsc) 실행&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;타입스크립트 컴파일러는 코드를 파싱하여 AST(추상 구문 트리) 생성 후 &lt;b&gt;정적 타입 검사&lt;/b&gt; 수행&lt;/li&gt;
&lt;li&gt;타입 오류 경고 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;트랜스파일링&lt;/b&gt;: 타입 정보를 제거하고 표준 JavaScript 코드로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ &lt;b&gt;실행&lt;/b&gt;: 변환된 JavaScript 코드는 브라우저나 Node.js 등 자바스크립트 런타임 환경에서 실행&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q5m4t/btsQBETdLph/4neIV5Ycu33JoRXBAk2qa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q5m4t/btsQBETdLph/4neIV5Ycu33JoRXBAk2qa0/img.png&quot; data-alt=&quot;https://ts.winterlood.com/d67c7b28-c191-46ee-9bdc-2ae8643c2028&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q5m4t/btsQBETdLph/4neIV5Ycu33JoRXBAk2qa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq5m4t%2FbtsQBETdLph%2F4neIV5Ycu33JoRXBAk2qa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;323&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ts.winterlood.com/d67c7b28-c191-46ee-9bdc-2ae8643c2028&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 실제 코드 실행없이 코드를 해석해 타입 오류를 찾아내는 &lt;b&gt;정적 분석기능&lt;/b&gt;을 통해 타입을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 파싱&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 코드 파일을 먼저 읽고 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;AST(Abstract Syntax Tree, 추상 구문 트리)&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;이 후 AST를 기반으로 타입 검사를 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 오류가 발견되면 컴파일 에러를 발생시키고 타입 오류가 없다면 타입 정보를 제거하고 AST를 &lt;b&gt;자바스크립트 코드&lt;/b&gt;로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&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;타입스크립트는 명시된 타입이 없더라도 초기 값이나 코드 문맥을 보고 자동으로 타입을 추론합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;let x = 3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;code&gt;x&lt;/code&gt;에 대한 타입을 따로 지정하지 않더라도 &lt;code&gt;x&lt;/code&gt;는 &lt;code&gt;number&lt;/code&gt;로 간주됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변수를 선언하거나 초기화 할 때 타입이 추론됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때 타입 추론 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;가장 일반적인 타입 (Best Common Type)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 몇 개의 표현식을 바탕으로 타입을 추론합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현식을 이용하여 가장 근접한 타입을 추론하게 되는데 이런 타입을 &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/type-inference.html&quot;&gt;&lt;b&gt;Best Common Type&lt;/b&gt;&lt;/a&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [0, 1, null]; // (number | null)[]&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;위 arr의 타입을 추론하기 위해서 배열의 각 아이템을 살펴본 후 배열의 각 아이템의 타입은 크게 &lt;code&gt;number&lt;/code&gt;와 &lt;code&gt;null&lt;/code&gt;로 구분되는 것을 통해 Best Common Type 알고리즘으로 가장 잘 호환되는 타입을 선정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;문맥상 타이핑&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 타입을 추론하는 다른 방식은 문맥상으로 타입을 결정하는 것입니다.&lt;/p&gt;
&lt;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 class=&quot;javascript&quot;&gt;&lt;code&gt;window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button);   //&amp;lt;- OK
  console.log(mouseEvent.kangaroo); //&amp;lt;- Error!
};&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;window.onmousedown&lt;/code&gt;함수의 타입을 추론하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할당문의 함수 표현식의 타입을 추론하여 매개변수의 타입인 &lt;code&gt;mouseEvent&lt;/code&gt; 를 추론해낼 수 있는데 &lt;code&gt;mouseEvent&lt;/code&gt; 매개변수에 &lt;code&gt;button&lt;/code&gt; 속성은 포함되어 있지만 &lt;code&gt;kangaroo&lt;/code&gt; 속성은 포함되지 않는다고 추론합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boeriR/btsQCDsoQdz/wOxRD040wpuKbkF9RLYJ80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boeriR/btsQCDsoQdz/wOxRD040wpuKbkF9RLYJ80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boeriR/btsQCDsoQdz/wOxRD040wpuKbkF9RLYJ80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboeriR%2FbtsQCDsoQdz%2FwOxRD040wpuKbkF9RLYJ80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;180&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 타입 검사&lt;/h3&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;b&gt;어떤 속성과 메서드를 가지고 있는가?&lt;/b&gt; 에 초점을 맞춥니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Duck Typing&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;오리처럼 걷고 오리처럼 소리 내면 오리다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 &lt;b&gt;속성 / 메서드&lt;/b&gt; 만으로 객체의 타입을 결정하는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Duck Typing 예시&lt;/b&gt; (동적 타이핑 언어 - JavaScript)&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function makeSound(animal) {
  if (animal.quack) {
    animal.quack(); // quack() 메서드가 있으면 '오리'로 간주
  }
}

const duck = {
  name: &quot;Duck&quot;,
  quack: () =&amp;gt; console.log(&quot;꽥꽥!&quot;),
};

const person = {
  name: &quot;John&quot;,
  quack: () =&amp;gt; console.log(&quot;사람도 꽥꽥!&quot;),
};

makeSound(duck);   // 꽥꽥!
makeSound(person); // 사람도 꽥꽥!&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;duck과 person은 전혀 다른 객체지만&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;둘 다 &lt;code&gt;quack()&lt;/code&gt; 메서드를 가지고 있기 때문에 &lt;code&gt;makeSound&lt;/code&gt; 함수 입장에서는 &lt;b&gt;둘 다 &quot;오리처럼 행동한다&quot;고 판단&lt;/b&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;Structural Subtyping&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;Duck Typing을 &lt;b&gt;정적 타입 시스템&lt;/b&gt;에서 구현한 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 타입이 같은 구조(속성과 메서드의 타입 포함)를 가진다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 두 타입은 서로 &lt;b&gt;호환 가능&lt;/b&gt;하다고 판단&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex1) &lt;b&gt;구조가 같으면 타입 호환 가능&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;interface User {
  name: string;
  age: number;
}

interface Person {
  name: string;
  age: number;
}

const user: User = { name: &quot;Beomsic&quot;, age: 20 };
const person: Person = user; // 구조가 같으므로 호환 가능&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;두 타입의 이름은 다르지만 name과 age라는 같은 속성을 가지고 있으므로 &lt;b&gt;Structural Subtyping&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;ex2) &lt;b&gt;일부 속성만 있어도 동작 가능&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;interface User {
  name: string;
  age: number;
}

const obj = { name: &quot;Beomsic&quot;, age: 30, location: &quot;Seoul&quot; };

const user: User = obj; // 가능&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;code&gt;obj&lt;/code&gt;에는 &lt;code&gt;location&lt;/code&gt;이 추가로 있어도 &lt;b&gt;필요한 속성(name, age)만 있으면&lt;/b&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;타입스크립트는 Structural Subtyping 기반&lt;/b&gt;으로 타입 검사 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 코드 실행 전에 아래와 같은 에러를 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잘못된 메서드 호출&lt;/li&gt;
&lt;li&gt;잘못된 타입 할당&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt; 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/intro.html&quot;&gt;https://www.typescriptlang.org/ko/docs/handbook/intro.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/2/basic-types.html&quot;&gt;https://www.typescriptlang.org/ko/docs/handbook/2/basic-types.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/type-inference.html&quot;&gt;https://www.typescriptlang.org/docs/handbook/type-inference.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.to/bilelsalemdev/abstract-syntax-tree-in-typescript-25ap&quot;&gt;https://dev.to/bilelsalemdev/abstract-syntax-tree-in-typescript-25ap&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts.winterlood.com/d67c7b28-c191-46ee-9bdc-2ae8643c2028&quot;&gt;https://ts.winterlood.com/d67c7b28-c191-46ee-9bdc-2ae8643c2028&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  타입스크립트</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/176</guid>
      <comments>https://beomsic.tistory.com/entry/%F0%9F%93%96-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EC%9A%94#entry176comment</comments>
      <pubDate>Tue, 16 Sep 2025 22:40:55 +0900</pubDate>
    </item>
    <item>
      <title>모듈 프로그래밍의 역사와 ESM</title>
      <link>https://beomsic.tistory.com/entry/%EB%AA%A8%EB%93%88-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%98-%EC%97%AD%EC%82%AC%EC%99%80-ESM</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  모듈(Module)?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 규모가 커지면서 Javascript 코드를 여러 파일과 폴더에 나누고 서로가 불러와 사용할 수 있도록 해주는 시스템의 필요성이 커졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript에서는 각 파일을 서로 다른 모듈로 생각해 각 모듈에서 변수나 함수를 공유하기 위해 이를 내보내고 불러올 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하고 있는 모듈 시스템과 사용하려는 모듈이 어떤 시스템을 따르고 있는지에 따라 사용 방식이 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  모듈 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;유지보수 용이&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈화가 잘 되어 있다면 의존성을 줄여 기능 개선이나 수정에 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;네임스페이스화&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역스코프에 존재하는 변수명이 겹치는 경우가 있을 수 있다.&lt;/li&gt;
&lt;li&gt;모듈로 분리하면 모듈만의 네임스페이스를 가지기 때문에 이런 문제를 해결할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;재사용성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 코드를 반복하지 않고 모듈로 분리해 재활용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  모듈 시스템&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9rr7X/btsQwQmCY9w/wbvfKH1ckEEFNpdcTWpO7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9rr7X/btsQwQmCY9w/wbvfKH1ckEEFNpdcTWpO7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9rr7X/btsQwQmCY9w/wbvfKH1ckEEFNpdcTWpO7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9rr7X%2FbtsQwQmCY9w%2FwbvfKH1ckEEFNpdcTWpO7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;811&quot; height=&quot;164&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;↖️ 초기 Javascript 프로그램&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;전역 변수 (Global Variables)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 초창기에는 웹 페이지에서 &lt;b&gt;간단한 DOM 조작이나 이벤트 처리&lt;/b&gt; 정도만 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 모듈 시스템은 없었고 모든 스크립트가 &lt;b&gt;전역 스코프(Global Scope)&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;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그를 사용해 불러올 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;script src=&quot;a.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;b.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;c.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&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;/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;&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;이름 충돌(Name Collision)&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;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 순서를 관리해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 예시로 확인해보기&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;/ a.js
var number = 1;
console.log(`a.js : ${number}`); 

// b.js
var number = 2;
console.log(`b.js : ${number}`);

// c.js
var number = 3;
console.log(`c.js : ${number}`);&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;script src=&quot;a.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;b.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;c.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      console.log(`number : ${number}`);
    &amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&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;&lt;b&gt;결과&lt;/b&gt;: &lt;code&gt;number : 3&lt;/code&gt; (마지막에 선언된 값으로 덮어써짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Namespace = Name + Space&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;/li&gt;
&lt;li&gt;즉, &lt;b&gt;이름 충돌을 방지하기 위해 서로 다른&lt;/b&gt; &lt;code&gt;이름의 공간&lt;/code&gt; &lt;b&gt;을 만들어 구분한다&lt;/b&gt;는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 전역 변수의 혼란을 줄이기 위해 &lt;b&gt;하나의 전역 객체&lt;/b&gt;만 두고 그 안에 모듈을 넣는 방식 등장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;전역 변수 하나만 만들고 나머지는 그 안에 계층적으로 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;var MyApp = {};
MyApp.utils = {};
MyApp.components = {};
MyApp.components.menu = {
  init: function() {
    console.log('Menu initialized');
  }
};

// 사용
MyApp.components.menu.init();&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전역 오염(Global Pollution) 완화 &amp;rarr; 충돌 가능성 &amp;darr;&lt;/td&gt;
&lt;td&gt;여전히 &lt;b&gt;의존성 관리&lt;/b&gt;가 힘듬 &amp;rarr; 순서 보장 문제 존재&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모듈을 비슷한 그룹끼리 묶을 수 있음&lt;/td&gt;
&lt;td&gt;모듈이 커질수록 전역 객체 하나가 비대해짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;모듈 간의 캡슐화가 완벽하지 않음 &amp;rarr; 내부 상태를 쉽게 변경 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;서로 호출할 때 객체의 속성(이름)을 많이 적어야해 불편&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ &lt;b&gt;IIFE (Immediately Invoked Function Expression) &amp;mdash; 즉시 실행 함수 패턴&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt; IIFE&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 스코프를 활용해 &lt;b&gt;캡슐화&lt;/b&gt;와 &lt;b&gt;모듈화&lt;/b&gt;를 더 엄격히 하고자 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 실행되는 함수를 이용해 독립적인 스코프를 만들고 필요한 것만 반환. (함수 스코프 이용)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;IIFE 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;(function () {
  var privateVar = &quot;외부에서 접근 불가&quot;;
  function privateFunction() {
    return &quot;비공개 함수&quot;;
  }
})();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 사용하면 &lt;b&gt;함수 스코프&lt;/b&gt; 때문에 외부에서 안에 있는 함수에 접근이 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;전역변수&lt;/b&gt;의 개수는 &lt;code&gt;0&lt;/code&gt; 이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  외부에서 접근할 수 없도록 보호는 했는데 밖에서 쓰고 싶다면 어떻게 해야하지?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const MyModule = (function () {
  var privateCounter = 0;

  function privateMethod() {
    console.log('Private method called');
  }

  return {
    increment: function() {
      privateCounter++;
      privateMethod();
    },
    getCount: function() {
      return privateCounter;
    }
  };
})();

// 사용 예시
MyModule.increment(); // &quot;Private method called&quot;
console.log(MyModule.getCount()); // 1
// console.log(MyModule.privateCounter); // undefined (접근 불가)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 하나의 모듈처럼 생각해서 프라이빗, 퍼블릭을 나누기 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;한계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;캡슐화&lt;/b&gt;: 외부에서 내부 변수나 함수에 접근 불가&lt;/td&gt;
&lt;td&gt;다른 모듈을 가져오려면 매개변수로 전달해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;전역 오염 방지&lt;/b&gt;: 전역 변수 생성을 최소화&lt;/td&gt;
&lt;td&gt;&lt;b&gt;비동기 로딩 불가&lt;/b&gt;: 모든 코드가 즉시 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;충돌 방지&lt;/b&gt;: 다른 라이브러리와의 이름 충돌 해결&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  V8 엔진의 등장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2008년 &lt;span style=&quot;color: #ee2323;&quot;&gt;Google V8&lt;/span&gt; 엔진&lt;/b&gt;이 등장하고 &lt;b&gt;2009년 Ryan Dahl이 &lt;span style=&quot;color: #006dd7;&quot;&gt;Node.js&lt;/span&gt;를 개발&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;자바스크립트가 클라이언트 사이드뿐만 아니라 서버 사이드에서도 동작할 수 있지 않을까?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 복잡한 애플리케이션을 구현하기 위해서는 본격적인 모듈 시스템이 필요할 것이다..!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;h2 data-ke-size=&quot;size26&quot;&gt;⭐ Common JS&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Common JS&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 단위로 모듈을 정의하고 &lt;code&gt;require&lt;/code&gt;/&lt;code&gt;module.exports&lt;/code&gt;로 가져오기 / 내보내기.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;module.exports&lt;/code&gt;, &lt;code&gt;exports&lt;/code&gt; 객체를 통해 모듈을 내보내고 &lt;code&gt;require&lt;/code&gt; 함수를 통해 모듈을 불러오기&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;// math.js
function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { add, multiply };&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const math = require('./math');
// const { add, multiply } = require('./math');

console.log(math.add(2, 3)); // 5
console.log(multiply(4, 5)); // 20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;장점&lt;/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;/li&gt;
&lt;li&gt;&lt;code&gt;require&lt;/code&gt;를 통해 &lt;b&gt;의존성 자동 해결&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;서버 사이드(Node.js)에서 매우 유용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건부 로딩&lt;/b&gt; 가능: if문 안에서도 &lt;code&gt;require&lt;/code&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;&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;동기적(Synchronous) 로딩:&lt;/b&gt; require는 해당 모듈이 완전히 로드될 때까지 기다림&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;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;CommonJS 한계&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;네이티브 지원 X&lt;/b&gt; &amp;rarr; 번들러(&lt;code&gt;Browserify&lt;/code&gt;, &lt;code&gt;Webpack&lt;/code&gt;) 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;require&lt;/code&gt;는 &lt;b&gt;동기 방식&lt;/b&gt; &amp;rarr; 브라우저 환경에서 네트워크 지연으로 인해 비효율적&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tree shaking 어려움&lt;/b&gt;: 사용하지 않는 코드 제거가 복잡&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;네이티브&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍에서 네이티브는 기본적으로 제공되는 것, 별도 도구 없이 즉시 사용 가능한 것을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &amp;ldquo;자바스크립트 실행 환경(브라우저 또는 Node.js)이 번들러나 추가 도구 없이 바로 이해하고 실행할 수 있다&amp;rdquo;를 의미&lt;/p&gt;
&lt;/blockquote&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;AMD (Asynchronous module definition)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bly3xA/btsQzv14sWu/3FGxcODR3NsfS2nVXWJGYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bly3xA/btsQzv14sWu/3FGxcODR3NsfS2nVXWJGYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bly3xA/btsQzv14sWu/3FGxcODR3NsfS2nVXWJGYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbly3xA%2FbtsQzv14sWu%2F3FGxcODR3NsfS2nVXWJGYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;476&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;752&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;CommonJS가 서버 환경에 적합하다면 브라우저 환경에서도 사용하기 위해서는 어떻게 해야 할까? 네트워크를 통해 모듈을 로드해야 하는 브라우저 환경에서는 &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; 했으며 AMD 그룹은 자바스크립트 &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;AMD&lt;/b&gt;를 구현한 라이브러리 - &lt;code&gt;require.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 모듈 정의
define(['jquery', 'underscore'], function($, _) {
  function doSomething() {
    // jQuery와 underscore 사용
    return _.map([1,2,3], function(n) { return n * 2; });
  }

  return {
    doSomething: doSomething
  };
});

// 모듈 사용
require(['myModule'], function(myModule) {
  console.log(myModule.doSomething()); // [2, 4, 6]
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Common.js 는 브라우저에서 사용하지 못하나? - browserify&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;browserify&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;code&gt;commons.js&lt;/code&gt; 를 이용해서 노드에서처럼 개발하면 브라우저에서 동작하도록 &lt;b&gt;번들링&lt;/b&gt;을 지원.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  따라서 최종 번들 파일이 &lt;b&gt;script src&lt;/b&gt;에 존재&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;browserify main.js -o bundle.js

&amp;lt;script src=&quot;bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  UMD (Universal Module Definition)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  모듈 방식을 분기처리를 통해 관리&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 생태계가 점점 커져 &lt;b&gt;CommonJS 와 AMD 모듈 시스템을 모두 지원해야 하는 상황&lt;/b&gt;이 생기게 되었고 (브라우저/서버 런타임을 모두 지원) 이를 위해 &lt;b&gt;모듈 관리 패턴 UMD&lt;/b&gt;가 탄생했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;(function (root, factory) {
  if (typeof define === 'function' &amp;amp;&amp;amp; define.amd) {
    *// AMD*
    define(['jquery'], factory);
  } else if (typeof module === 'object' &amp;amp;&amp;amp; module.exports) {
    *// CommonJS*
    module.exports = factory(require('jquery'));
  } else {
    root.MyModule = factory(root.jQuery);
  }
}(typeof self !== 'undefined' ? self : this, function ($) {

  function MyModule() {
    *// 모듈 로직*
  }

  return MyModule;
}));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  ES Modules&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;ES Modules&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 언어 차원에서 &lt;b&gt;표준 모듈 시스템&lt;/b&gt;을 도입.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2020년 5월 Node.js (v12.17.0)에서 공식적으로 ESM을 지원하기 시작&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ECMAScript&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Javascript를 만든 표준 기구&lt;/li&gt;
&lt;li&gt;우리가 모듈을 정의하겠다!! &amp;rarr; &lt;b&gt;ESModule&lt;/b&gt; 등장&lt;/li&gt;
&lt;/ul&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;// math.js
export function add(a, b) {
  return a + b;
}

export const PI = 3.14159;

// 기본 내보내기
export default function multiply(a, b) {
  return a * b;
}

// 여러 개를 한번에 내보내기
const subtract = (a, b) =&amp;gt; a - b;
const divide = (a, b) =&amp;gt; a / b;
export { subtract, divide };&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// app.js
import multiply, { add, PI, subtract } from './math.js';
import * as MathUtils from './math.js'; // 모든 내보내기를 객체로

console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20 (기본 내보내기)
console.log(PI); // 3.14159
console.log(MathUtils.divide(10, 2)); // 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ ESModule의 특징 - &lt;b&gt;정적 구조 (Static Structure)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;*// main.js
// ✅ 정적 분석 가능 - 최상위에서 import*
import { utils } from './utils.js';

*// ❌ 동적 import는 ES2020에서 별도 지원*
if (condition) {
  import('./dynamic.js').then(module =&amp;gt; {
    *// 동적 import는 Promise를 반환*
  });
}&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;code&gt;main.js&lt;/code&gt;가 실행되기 &lt;b&gt;전에&lt;/b&gt; 브라우저 또는 번들러가 &lt;code&gt;./utils.js&lt;/code&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;main.js &amp;rarr; utils.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;이처럼 import 문법이 코드 최상위(Top-Level) 에 있어야 하며 조건문이나 함수 내부에 있으면 안 됩니다. &amp;rarr; 이렇게 하면 의존성이 코드 분석 단계에서 명확히 드러나기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;정적 분석(Static Analysis)이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 분석은 프로그램을 실제로 실행하지 않고 코드 자체만 보고 분석하는 과정을 말합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;♂️ 이를 통해 빌드 도구(Webpack, Rollup, Vite 등)나 브라우저가 다음과 같은 작업을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;의존성 그래프 생성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 실행 전에 &lt;code&gt;import&lt;/code&gt; 경로를 따라가며 모듈 간 관계를 파악합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;main.js
 ├─ utils.js
 └─ api.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;2️⃣ &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;트리 쉐이킹(Tree-Shaking)&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;사용되지 않는 코드(Dead Code)를 실행 전 단계에서 미리 제거할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// utils.js
export function add() {}
export function subtract() {} // 사용되지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; main.js에서 add만 쓰면, subtract는 최종 번들에서 제거됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;최적화&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저가 어떤 모듈을 먼저 로드해야 할지 미리 결정.&lt;/li&gt;
&lt;li&gt;캐싱 전략 수립 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;정적 구조가 중요한 이유 정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;브라우저가 효율적으로 모듈을 로드&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 페이지 로드 전에 미리 **모든 모듈을 파악(정적 분석)**하고 병렬, 비동기 다운로드나 캐싱 최적화를 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script type=&quot;module&quot; src=&quot;main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&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;code&gt;main.js&lt;/code&gt;의 &lt;code&gt;import&lt;/code&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;utils.js&lt;/li&gt;
&lt;li&gt;api.js&lt;/li&gt;
&lt;li&gt;constants.js&lt;/li&gt;
&lt;li&gt;&amp;hellip;&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack, Rollup 같은 번들러가 동작할 때 import 문법은 &lt;b&gt;정적으로 확정된 모듈 구조&lt;/b&gt; 덕분에 사용하지 않는 코드를 미리 제거하고 필요한 모듈만 하나의 파일로 묶을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  브라우저에서 네이티브 지원: type=&amp;rdquo;module&amp;rdquo;&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;*&amp;lt;!-- 전통적인 스크립트 (전역 스코프 공유) --&amp;gt;*
&amp;lt;script src=&quot;legacy.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

*&amp;lt;!-- ES Module (모듈 스코프) --&amp;gt;*
&amp;lt;script type=&quot;module&quot; src=&quot;module.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

*&amp;lt;!-- 인라인 모듈 --&amp;gt;*
&amp;lt;script type=&quot;module&quot;&amp;gt;
  import { myFunction } from './utils.js';
  myFunction();
&amp;lt;/script&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;✅ &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;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;*// a.js*
var number = 1;
console.log(`a.js : ${number}`);

*// b.js*
console.log(`b.js : ${number}`); *// a.js의 number에 접근 가능*
var number = 2;
console.log(`b.js : ${number}`);&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;script src=&quot;a.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;b.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      console.log(`global : ${number}`); *// 2 출력*
    &amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&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;  &lt;b&gt;ES Modules 방식 (모듈 스코프)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;*// a.js*
const number = 1;
console.log(`a.js : ${number}`);
export default number;

*// b.js*  
import a_number from './a.js';
console.log(`imported from a: ${a_number}`);
const number = 2;
console.log(`b.js : ${number}`);
export default number;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;script type=&quot;module&quot;&amp;gt;
      import a_number from './a.js';
      import b_number from './b.js';
      console.log(`a_number : ${a_number}`); *// 1*
      console.log(`b_number : ${b_number}`); *// 2*
    &amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;  ES Modules vs CommonJS 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ES Modules&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;CommonJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문법&lt;/td&gt;
&lt;td&gt;import / export&lt;/td&gt;
&lt;td&gt;require() / module.exports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로딩&lt;/td&gt;
&lt;td&gt;정적분석 + 비동기&lt;/td&gt;
&lt;td&gt;동적 + 동기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree Shaking&lt;/td&gt;
&lt;td&gt;✅&amp;nbsp;지원&lt;/td&gt;
&lt;td&gt;❌&amp;nbsp;어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;브라우저 지원&lt;/td&gt;
&lt;td&gt;✅&amp;nbsp;네이티브 지원&lt;/td&gt;
&lt;td&gt;❌&amp;nbsp;번들러 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-level await&lt;/td&gt;
&lt;td&gt;✅&amp;nbsp; 지원&lt;/td&gt;
&lt;td&gt;❌&amp;nbsp;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조건부 로딩&lt;/td&gt;
&lt;td&gt;동적 import 필요&lt;/td&gt;
&lt;td&gt;✅&amp;nbsp;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⬆️ &lt;b&gt;Top-level await&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Top-level await&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;async&lt;/code&gt; 함수 내부가 아닌 모듈의 최상위 레벨에서 바로 &lt;code&gt;await&lt;/code&gt; 키워드를 사용할 수 있도록 해주는 기능입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;↖️ &lt;b&gt;Top-level await 이전 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;// ❌ Top-level await 불가능
const data = await fetch('/api/data'); // SyntaxError&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;code&gt;await&lt;/code&gt;는 반드시 &lt;code&gt;async&lt;/code&gt; 함수 안에서만 사용 가능했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 모듈 최상위에서 비동기 작업을 하려면 반드시 async 함수로 한 번 감싸야 했어요.&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;// 기존 해결 방법
async function init() {
  const data = await fetch('/api/data');
  console.log(data);
}
init();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;↗️ &lt;b&gt;Top-level await 이후 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES2022(ES13)부터는 모듈의 최상위에서 바로 &lt;code&gt;await&lt;/code&gt;를 사용 가능하게 되었습니다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;// ✅ Top-level await 가능 (ESM 전용)
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: center;&quot;&gt;즉, async 함수 없이도 최상위에서 await 사용 가능!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;왜 필요한가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Top-level await의 가장 큰 목적은 &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;ex) 모듈이 로드될 때 외부 API를 호출하거나, DB 연결 같은 비동기 초기화가 필요할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// db.js
export const db = await connectToDatabase(); // DB 연결 완료 후 export&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// main.js
import { db } from './db.js';

const result = await db.query('SELECT * FROM users');
console.log(result);&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;db.js에서 await를 사용하면 connectToDatabase()가 완료될 때까지 main.js 실행이 자동으로 대기됩니다.&lt;/li&gt;
&lt;li&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;왜 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;CommonJS는 동기적 로드 모델&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;require()는 &lt;b&gt;파일을 읽고, 파싱하고, 실행하는 과정이 모두 동기적&lt;/b&gt;으로 진행됩니다.&lt;/li&gt;
&lt;li&gt;즉, require()가 끝나기 전까지 다음 코드가 실행되지 않아요.&lt;/li&gt;
&lt;li&gt;만약 await를 넣어버리면 require()가 비동기로 바뀌어야 하는데 CJS의 근본적인 설계와 맞지 않음 &amp;rarr; &lt;b&gt;호환성 문제 발생&lt;/b&gt;&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;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;CJS는 오래된 Node.js 패키지들과의 호환성이 중요하기 때문에 기존 동기 모델을 깨지 않기 위해 Top-level await을 도입하지 않음.&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;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ &lt;b&gt;ESM에서 Top-level await 동작 원리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESM은 기본적으로 &lt;b&gt;비동기 모듈 시스템&lt;/b&gt;이기 때문에 모듈 로딩 시 &lt;b&gt;Top-level await&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;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// data.js
export const data = await fetch('&amp;lt;https://api.example.com&amp;gt;').then(res =&amp;gt; res.json());&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;// main.js
import { data } from './data.js';
console.log(data); // Top-level await가 끝날 때까지 자동으로 대기&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;main.js가 data.js를 &lt;b&gt;import&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;data.js의 await fetch(...) 실행&lt;/li&gt;
&lt;li&gt;완료될 때까지 main.js 실행이 &lt;b&gt;멈춤(대기)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;data&lt;/b&gt; 값이 준비되면 main.js 실행 재개&lt;/li&gt;
&lt;/ol&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는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Promise&lt;/span&gt; 기반&lt;/b&gt;으로 작동한다고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;모듈 간 의존성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Top-level await이 여러 모듈에 걸쳐있으면 &lt;b&gt;모듈 로딩 순서&lt;/b&gt;가 체인처럼 연결됩니다.&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;// step1.js
export const step1 = await new Promise(resolve =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('step1 done'), 1000);
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// step2.js
import { step1 } from './step1.js';
export const step2 = step1 + ' -&amp;gt; step2 done';&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// main.js
import { step2 } from './step2.js';
console.log(step2);&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;zwj;♂️ &lt;b&gt;실행 순서&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;main.js (대기)
  &amp;darr;
step2.js (대기)
  &amp;darr;
step1.js (1초 기다림)
  &amp;darr;
step2 실행 후 main.js 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;Top-level await의 장단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;장점 1.&lt;/b&gt; &lt;b&gt;비동기 초기화 코드 간결화&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 기존 방식
export let config;
async function loadConfig() {
  config = await fetchConfig();
}
await loadConfig(); // 불필요한 함수 래핑

// Top-level await
export const config = await fetchConfig(); // 깔끔!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;장점 2.&lt;/b&gt; &lt;b&gt;의존성 관리 단순화&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 모듈이 체인처럼 연결되어 있어도 자동으로 로딩 순서를 맞춰줌.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;장점 3.&lt;/b&gt; &lt;b&gt;ESM의 비동기적 특성과 자연스러움&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;import 자체가 Promise 기반 &amp;rarr; Top-level await와 궁합이 좋음.&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;단점 1. 모듈 로딩 속도 저하&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 모듈이 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;❌ &lt;b&gt;단점 2. 병목 현상 가능성&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;여러 모듈이 Top-level await에 의존할 경우 초기 로딩이 매우 느려질 수 있음.&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;  번들러 등장 - Webpack&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️&amp;nbsp;개발자들이 혼란스러워짐.&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;common.js&lt;/b&gt; 에서 &lt;b&gt;esm&lt;/b&gt;을 가져올 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;esm&lt;/b&gt; 에서 &lt;b&gt;common.js&lt;/b&gt; 가져올 수 있지만 변경이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 프로젝트에서 두 모듈 시스템이 섞이면 관리가 매우 어려워졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어떤 모듈 시스템을 사용할지&quot; + &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;이 혼란을 해결하기 위해 등장한 도구가 &lt;b&gt;번들러(Bundler)&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;Webpack&lt;/b&gt;, Rollup, Parcel, Vite 등&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 모듈 시스템(CJS, ESM 등)으로 작성된 파일들을 &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;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;모든 브라우저가 ES Modules를 지원하지는 않음&lt;/li&gt;
&lt;li&gt;번들러가 ESM 코드를 &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;여러 파일을 하나로 합쳐 HTTP 요청 수 감소&lt;/li&gt;
&lt;li&gt;코드 압축 및 난독화&lt;/li&gt;
&lt;li&gt;Tree shaking으로 불필요한 코드 제거&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;CommonJS, ESM, AMD 등 다양한 모듈 시스템을 &lt;b&gt;하나의 통합된 모듈 구조&lt;/b&gt;로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;hellip;.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nodejs.org/api/packages.html#determining-module-system&quot;&gt;https://nodejs.org/api/packages.html#determining-module-system&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nodejs.org/api/esm.html#top-level-await&quot;&gt;https://nodejs.org/api/esm.html#top-level-await&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/&quot;&gt;https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/605&quot;&gt;https://tech.kakao.com/posts/605&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://f-lab.kr/insight/understanding-modern-javascript-module-system?gad_source=1&amp;amp;gad_campaignid=22368870602&amp;amp;gbraid=0AAAAACGgUFfcvaAyr73FyvvH3Bij6S-Bs&amp;amp;gclid=CjwKCAjwt-_FBhBzEiwA7QEqyHkX4A8pyrSjLKKFSHDhItEoPQApgbeNN6B1lQ69djY6_BwoDj3yDxoCHeMQAvD_BwE&quot;&gt;https://f-lab.kr/insight/understanding-modern-javascript-module-system?gad_source=1&amp;amp;gad_campaignid=22368870602&amp;amp;gbraid=0AAAAACGgUFfcvaAyr73FyvvH3Bij6S-Bs&amp;amp;gclid=CjwKCAjwt-_FBhBzEiwA7QEqyHkX4A8pyrSjLKKFSHDhItEoPQApgbeNN6B1lQ69djY6_BwoDj3yDxoCHeMQAvD_BwE&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/browserify/browserify#usage&quot;&gt;https://github.com/browserify/browserify#usage&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nearform.com/digital-community/node-esm-and-exports/&quot;&gt;https://nearform.com/digital-community/node-esm-and-exports/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Mah0QakFaJk&amp;amp;ab_channel=%ED%94%84%EB%A1%B1%ED%8A%B8&quot;&gt;https://www.youtube.com/watch?v=Mah0QakFaJk&amp;amp;ab_channel=프롱트&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  자바스크립트</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/175</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%AA%A8%EB%93%88-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%98-%EC%97%AD%EC%82%AC%EC%99%80-ESM#entry175comment</comments>
      <pubDate>Sat, 13 Sep 2025 17:35:22 +0900</pubDate>
    </item>
    <item>
      <title>History API 와 클라이언트 라우팅</title>
      <link>https://beomsic.tistory.com/entry/History-API-%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%8C%85</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  SPA&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA는 URL이 바뀔 때마다 새로운 페이지를 서버로 요청하지 않고 클라이언트 단에서 라우팅을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;React&lt;/code&gt;, &lt;code&gt;Svelte&lt;/code&gt;, &lt;code&gt;Vue.js&lt;/code&gt; 같은 대부분의 프론트엔드 프레임워크를 사용할 때 이런 클라이언트 단 라우팅을 지원하는 라이브러리를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 이런 라우팅 라이브러리는 내부적으로 자바스크립트의 &lt;b&gt;History API&lt;/b&gt;를 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  SPA에서 URL Routing이 필요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  만약 URL Routing이 없다면?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SPA에서는 실제 html 파일은 하나로&lt;/li&gt;
&lt;li&gt;여러 페이지를 돌아다녔지만 방문 기록이 없습니다.&lt;/li&gt;
&lt;li&gt;따라서 뒤로가기 / 앞으로 가기 등의 기능을 사용할 수 없습니다.&lt;/li&gt;
&lt;li&gt;본인이 보고 있는 페이지의 접근 방법을 알기 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 SPA역시 우리가 알고 있는 웹페이지와 같은 사용자 경험을 제공해주어야 합니다.&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;  History API란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;History API는 브라우저가 관리하는 &lt;code&gt;세션 히스토리(session history)&lt;/code&gt; , 즉 페이지 방문 이력을 제어하기 위한 웹 표준 API 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 세션은 보통 새 창이나 탭을 열 때 생성되고 해당 창이나 탭을 닫을 때 사라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;세션 히스토리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저에서 세션 히스토리는 사용자가 현재 탭에서 이동했던 페이지 탐색 기록&lt;/p&gt;
&lt;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;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  history 전역 객체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 History API는 기본적으로 &lt;code&gt;history&lt;/code&gt; 전역 객체를 통해서 사용해 볼 수 있으며 &lt;code&gt;window&lt;/code&gt;나 &lt;code&gt;document&lt;/code&gt; 전역 객체를 통해서도 접근할 수 있습니다.&lt;/p&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;b&gt;history&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;history.length&lt;/td&gt;
&lt;td&gt;세션 히스토리에 들어있는 요소들 개수 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;history.back()&lt;/td&gt;
&lt;td&gt;뒤로 가기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;history.forward()&lt;/td&gt;
&lt;td&gt;앞으로 가기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;history.go(number)&lt;/td&gt;
&lt;td&gt;원하는 위치만큼 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 현재 세션에서 얼마나 많은 페이지를 방문했는지 알고 싶다면 다음과 같이 History API에서 제공하는 &lt;code&gt;length&lt;/code&gt; 프로퍼티에 접근하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;history.length; // 1
window.history.length; // 1
document.history.length; // 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⏩ 기록된 방문 이력을 통한 페이지 이동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;History API가 제공하는 가장 기본적인 기능은 히스토리에 기록된 페이지 방문 이력에 따라 이동하는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뒤로 가기 앞으로 가기 등의 기능을 자바스크립트로 대신 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;history.back(); // 뒤로 가기
history.forware(); // 앞으로 가기&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;zwj;♂️ &lt;b&gt;go&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;back(), forward()를 대체할 수 있는 좀 더 범용적인 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;history.go(-2); // 뒤로 2번가기
history.go(0); // 새로 고침
history.go(2); // 앞으로 2번가기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;back&lt;/code&gt; / &lt;code&gt;forward&lt;/code&gt; / &lt;code&gt;go&lt;/code&gt; 메서드는 SPA에서 잘 사용하지 않습니다!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3개의 메서드를 호출하면 브라우저는 해당 페이지를 리로드(reload)하기 때문에&lt;/p&gt;
&lt;/blockquote&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;  pushState(state, unused, url)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 들어온 url을 현재 state의 다음 state로 추가하고 뒤에 있던 state은 모두 날려버립니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로 들어온 url이 가장 최상위 state&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;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.3953%;&quot;&gt;매개변수&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 61.0465%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.3953%;&quot;&gt;state&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%;&quot;&gt;pushState()에 의해 생성되어 새로운 히스토리 항목과 관련된 JS 객체&lt;/td&gt;
&lt;td style=&quot;width: 61.0465%;&quot;&gt;사용자가 새로운&amp;nbsp;state로 이동할 때마다&amp;nbsp;popstate&amp;nbsp;이벤트가 발생되고 이벤트&amp;nbsp;state&amp;nbsp;속성은 기록 항목의&amp;nbsp;state&amp;nbsp;객체 복사본을 포함합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.3953%;&quot;&gt;unused&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%;&quot;&gt;예전 브라우저들과의 호환성을 위해 강제로 넣는 값&lt;/td&gt;
&lt;td style=&quot;width: 61.0465%;&quot;&gt;생략 불가능 - 빈 문자열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.3953%;&quot;&gt;url&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%;&quot;&gt;새로 히스토리에 쌓을 url&lt;/td&gt;
&lt;td style=&quot;width: 61.0465%;&quot;&gt;현재 url과 &lt;b&gt;origin(protocol + host + port)&lt;/b&gt;이 같은 절대 또는 상대 경로, 주소창은 바뀌지만 실제 페이지는 이동하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT17Ia/btsQoDHqbLo/UzwPAslhP6QTBlDKrY77O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT17Ia/btsQoDHqbLo/UzwPAslhP6QTBlDKrY77O0/img.png&quot; data-alt=&quot;https://alpha.velog.io/@kim_unknown_/JavaScript-History-API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT17Ia/btsQoDHqbLo/UzwPAslhP6QTBlDKrY77O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT17Ia%2FbtsQoDHqbLo%2FUzwPAslhP6QTBlDKrY77O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;347&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://alpha.velog.io/@kim_unknown_/JavaScript-History-API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서 보이는 것 처럼 &lt;code&gt;pushState()&lt;/code&gt; 메서드를 호출하면 원래 있던 페이지는 이전 페이지로 저장이 되고 인자로 넘긴 URL이 현재 페이지가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 브라우저에서 뒤로 가기 버튼을 눌러서 원래 페이지로 돌아가능 것이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 앞으로 가기 버튼은 사용할 수 없게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원래 있던 이후의 방문 히스토리를 모두 지우기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  replaceState(state, unused, url)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파라미터는 pushState 와 동일&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;파라미터로 들어온 url로 현재 페이지의 url을 변경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 state를 쌓는 것이 아닌 현재 state를 대체&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dultG4/btsQqEeeCiM/05NuXwQYL6Ahcv22aGubvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dultG4/btsQqEeeCiM/05NuXwQYL6Ahcv22aGubvk/img.png&quot; data-alt=&quot;https://alpha.velog.io/@kim_unknown_/JavaScript-History-API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dultG4/btsQqEeeCiM/05NuXwQYL6Ahcv22aGubvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdultG4%2FbtsQqEeeCiM%2F05NuXwQYL6Ahcv22aGubvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;762&quot; height=&quot;437&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://alpha.velog.io/@kim_unknown_/JavaScript-History-API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;replaceState()&lt;/code&gt; 메서드를 호출하면 원래 페이지가 인자로 넘긴 URL로 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 사용자가 브라우저에서 뒤로 가기 버튼을 누르면 원래 페이지의 이전 페이지로 이동하고 앞으로 가기 버튼을 누르면 기존의 다음 페이지로 이동할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  popstate 이벤트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;History API의 pushState(), replaceState() 메서드를 사용하면 페이지 리로딩없이 URL만 갱신하여 클라이언트 단에서 라우팅이 가능합니다.&lt;/p&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;앞으로 가기&lt;/b&gt;를 하게 된다면 &lt;code&gt;back()&lt;/code&gt;, &lt;code&gt;forward()&lt;/code&gt; 메서드 처럼 브라우저가 페이지를 다시 불러오게 되고 이는 SPA의 방식과 맞지 않게 동작하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 하지만 브라우저에서 뒤로 가기 / 앞으로 가기 버튼은 우리가 통제할 수 있는 페이지 밖에 있어 일반적인 버튼처럼 click 이벤트를 감지할 수 없습니다.&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;History API&lt;/b&gt;의 popstate 이벤트 입니다.&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;popstate&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;사용자가 브라우저에서 뒤로 가기나 앞으로 가기를 할 때 window 전역 객체에서 발생해 자바스크립트로 이벤트 핸들러를 걸어 처리할 수 있습니다.&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;window에서 발생하기 때문에 window에 이벤트 리스너를 추가해 어떤 state가 pop되는지 확인할 수 있습니다.&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;code&gt;pushState()&lt;/code&gt; 메서드나 &lt;code&gt;replaceState()&lt;/code&gt; 메서드에 의해 생성되면 &lt;code&gt;popstate&lt;/code&gt; 이벤트의 state 속성은 히스토리 엔트리 state 객체의 복사본을 갖게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pushState() 메서드나 replaceState() 메서드가 첫 번째 인자로 상태 객체를 받는 이유&lt;/li&gt;
&lt;li&gt;popstate 이벤트를 처리하는 핸들러 함수의 매개변수로 이 상태 객체가 넘어옵니다.&lt;/li&gt;
&lt;/ul&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;window.addEventListeneer(&quot;popstate&quot;, (event) =&amp;gt; {});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  여러 프레임워크에서의 라우팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Vue 프레임워크에서의 라우팅&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  History API를 이용한 Vue 라우팅&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router는 내부적으로 &lt;code&gt;window.history&lt;/code&gt;를 직접 사용합니다.&lt;/p&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: #006dd7;&quot;&gt;router/packages/router/src/history/html5.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// function useHistoryStateNavigation()
history[replace ? 'replaceState' : 'pushState'](state, '', url)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  React 프레임워크에서의 라우팅&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  History API를 이용한 React 라우팅&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;createBrowserRouter &amp;rarr; createBrowserHistory &amp;rarr; getUrlBaseHistory 순서로 &lt;b&gt;History API&lt;/b&gt;를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;createBrowserRouter
    &amp;darr;
createBrowserHistory
    &amp;darr;
getUrlBaseHistory (push, replace 등 내부 동작)&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;1️⃣ &lt;b&gt;createBrowserRouter&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;/li&gt;
&lt;li&gt;라우트 설정 객체를 받아, 라우팅 정보(path, element 등)를 관리할 수 있는 &lt;b&gt;Router 인스턴스 생성&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkj8kL/btsQol1cSbe/idkU4kBczLg5MGzZCYAB60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkj8kL/btsQol1cSbe/idkU4kBczLg5MGzZCYAB60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkj8kL/btsQol1cSbe/idkU4kBczLg5MGzZCYAB60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdkj8kL%2FbtsQol1cSbe%2FidkU4kBczLg5MGzZCYAB60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;365&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;createBrowserHistory&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; window.history &lt;b&gt;객체를 직접 사용&lt;/b&gt;하여 확장한 history 객체를 생성&lt;/li&gt;
&lt;li&gt;내부적으로 pushState, replaceState, popstate 이벤트를 활용&lt;/li&gt;
&lt;li&gt;결과적으로 &lt;b&gt;SPA 전용 브라우저 히스토리 관리 객체&lt;/b&gt;를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdqFvV/btsQqz46tF1/BK9gKAKw14ISlAJz4ZFkhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdqFvV/btsQqz46tF1/BK9gKAKw14ISlAJz4ZFkhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdqFvV/btsQqz46tF1/BK9gKAKw14ISlAJz4ZFkhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdqFvV%2FbtsQqz46tF1%2FBK9gKAKw14ISlAJz4ZFkhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;342&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;getUrlBaseHistory&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;code&gt;push&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kvtss/btsQop3DN8k/J6KpcGpLnzLL90j4UKPs2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kvtss/btsQop3DN8k/J6KpcGpLnzLL90j4UKPs2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kvtss/btsQop3DN8k/J6KpcGpLnzLL90j4UKPs2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKvtss%2FbtsQop3DN8k%2FJ6KpcGpLnzLL90j4UKPs2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;566&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;976&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;push 메서드는 window.history.pushState를 래핑하여 &lt;b&gt;새로운 URL을 스택에 추가&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✏️ 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vue와 React 모두 브라우저 History API(pushState, replaceState, popstate)를 활용한다.&lt;/li&gt;
&lt;li&gt;Vue Router는 window.history를 직접 사용하고 React Router는 이를 확장한 createBrowserHistory를 사용한다.&lt;/li&gt;
&lt;li&gt;push &amp;rarr; 주소창 변경 + SPA 내부 렌더링, popstate &amp;rarr; 뒤로 가기/앞으로 가기 시 동작.&lt;/li&gt;
&lt;li&gt;결과적으로 &lt;b&gt;서버에 불필요한 요청 없이 URL과 화면 전환을 동기화&lt;/b&gt;하는 것이 핵심.&lt;/li&gt;
&lt;li&gt;React Router는 createBrowserHistory로 커스텀 제어가 가능해 로그 추적이나 사용자 행동 분석에도 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=xvmqaUGAZpo&amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&quot;&gt;https://www.youtube.com/watch?v=xvmqaUGAZpo&amp;amp;ab_channel=우아한테크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/js-history-api/&quot;&gt;https://www.daleseo.com/js-history-api/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/History&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/History&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/History/pushState&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/History/pushState&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/popstate_event&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/Window/popstate_event&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://v3.router.vuejs.org/kr/guide/essentials/history-mode.html&quot;&gt;https://v3.router.vuejs.org/kr/guide/essentials/history-mode.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactrouter.com/6.30.1/routers/create-browser-router&quot;&gt;https://reactrouter.com/6.30.1/routers/create-browser-router&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/dom/lib.tsx#L748&quot;&gt;https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/dom/lib.tsx#L748&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://alpha.velog.io/@kim_unknown_/JavaScript-History-API&quot;&gt;https://alpha.velog.io/@kim_unknown_/JavaScript-History-API&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/174</guid>
      <comments>https://beomsic.tistory.com/entry/History-API-%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%8C%85#entry174comment</comments>
      <pubDate>Mon, 8 Sep 2025 23:07:42 +0900</pubDate>
    </item>
    <item>
      <title>DocumentFragment으로 DOM 조작하기</title>
      <link>https://beomsic.tistory.com/entry/DocumentFragment%EC%9C%BC%EB%A1%9C-DOM-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  DOM 트리에 노드를 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;createElement&lt;/code&gt; 와 &lt;code&gt;appendChild&lt;/code&gt; 를 사용해서 DOM 트리에 노드를 추가할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;const container = document.createElement('div'); // 노드를 생성
const content = document.createElement('div');
container.appendChild(content); // DOM 트리에 추가&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 직접 DOM에 접근하여 노드를 추가하게 되면 DOM에서 매번 reflow가 발생하여 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;reflow&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소의 너비, 높이, 위치 등이 변경되어 render tree가 재생성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 요소에서 reflow가 발생하면 주변 요소(부모, 자식, 형제)에도 영향을 주게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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;DocumentFragment&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DocumentFragment&lt;/b&gt; 객체는 DOM 요소를 메모리에 저장하는 임시 저장소입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DocumentFragment&lt;/b&gt;는 일반 DOM 처럼 요소를 추가하거나 조작할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 과정에서 웹 페이지에는 변화가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, 여러개의 요소들을 저장한 후 하위 DOM 트리를 준비한 후 DOM에 하위 트리를 추가합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 DocumentFragment의 노드들이 모두 DOM으로 이동하고 빈 DocumentFragment만 남게됩니다.&lt;/li&gt;
&lt;li&gt;모든 노드가 한 번에 추가되기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;리플로우도 한 번만 발생&lt;/b&gt;&lt;/span&gt;하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 사용 예제&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const fragment = document.createDocumentFragment();

for (let i = 0; i &amp;lt; 10; i++) {
  const text = document.createElement('p');
  fragment.appendChild(text);
}

document.body.appendChild(fragment);&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;zwj;♂️ &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;1️⃣ &lt;code&gt;document.createDocumentFragment()&lt;/code&gt;로 &lt;b&gt;메모리 상의 가상 DOM 컨테이너&lt;/b&gt;인 &lt;code&gt;fragment&lt;/code&gt;를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 반복문을 돌며 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 요소 10개를 만들고 이 요소들을 &lt;code&gt;fragment&lt;/code&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;브라우저의 실제 DOM 트리에 전혀 변화가 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;리플로우(Reflow), 리페인트(Repaint) 없음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 마지막에 &lt;code&gt;fragment&lt;/code&gt;를 한 번에 실제 DOM에 추가해 화면에 표시됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 요소 10개가 한 번의 DOM 업데이트로 추가됩니다.&lt;/li&gt;
&lt;li&gt;이때 reflow 한 번 발생&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;  createElement와 비교해보기&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const ELEMENT_COUNT = 10000;

function clearBody() {
  while (document.body.firstChild) {
    document.body.removeChild(document.body.firstChild);
  }
}

// DocumentFragment 사용한 경우
function testWithoutFragment() {
  clearBody();
  console.time('Without DocumentFragment');

  for (let i = 0; i &amp;lt; ELEMENT_COUNT; i++) {
    const p = document.createElement('p');
    p.textContent = `문단 ${i + 1}`;
    document.body.appendChild(p); // 매번 DOM 업데이트 발생
  }

  console.timeEnd('Without DocumentFragment');
}

// DocumentFragment 사용한 경우
function testWithFragment() {
  clearBody();
  console.time('With DocumentFragment');

  const fragment = document.createDocumentFragment();

  for (let i = 0; i &amp;lt; ELEMENT_COUNT; i++) {
    const p = document.createElement('p');
    p.textContent = `문단 ${i + 1}`;
    fragment.appendChild(p); // 메모리 상에서만 작업
  }

  // 한 번에 DOM 업데이트
  document.body.appendChild(fragment);

  console.timeEnd('With DocumentFragment');
}

// 실행
console.log(`테스트 시작: ${ELEMENT_COUNT.toLocaleString()}개의 &amp;lt;p&amp;gt; 태그 생성`);
testWithoutFragment();
testWithFragment();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WpxmK/btsQoFFgNxQ/3r7r4hKZurhL1czfsgNvOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WpxmK/btsQoFFgNxQ/3r7r4hKZurhL1czfsgNvOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WpxmK/btsQoFFgNxQ/3r7r4hKZurhL1czfsgNvOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWpxmK%2FbtsQoFFgNxQ%2F3r7r4hKZurhL1czfsgNvOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1332&quot; height=&quot;224&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/DocumentFragment&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/DocumentFragment&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/173</guid>
      <comments>https://beomsic.tistory.com/entry/DocumentFragment%EC%9C%BC%EB%A1%9C-DOM-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0#entry173comment</comments>
      <pubDate>Mon, 8 Sep 2025 22:58:26 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 아키텍처 구조 - MVC, MVVM</title>
      <link>https://beomsic.tistory.com/entry/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%EC%A1%B0-MVC-MVVM</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발에서 아키텍처를 설계하는 것은 애플리케이션의 유지보수성, 확장성 및 성능을 결정짓는 중요한 요소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 관심사의 분리는 프론트엔드 아키텍처를 설계할 때 기본적으로 고려해야 하는 원칙 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 사용자 인터페이스(UI), 비즈니스 로직, 데이터 관리 등의 관심사를 분리함으로써 각각의 부분을 독립적으로 개발하고 테스트할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ MVC 패턴&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;MVC 패턴&lt;/b&gt;&lt;/span&gt;은 애플리케이션을 &lt;b&gt;모델(Model)&lt;/b&gt;, &lt;b&gt;뷰(View)&lt;/b&gt;, &lt;b&gt;컨트롤러(Controller)&lt;/b&gt;의 세 부분으로 분리하는 가장 일반적인 아키텍처 패턴 중 하나입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  구성 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Model (모델)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션의 데이터와 비즈니스 로직을 관리&lt;/li&gt;
&lt;li&gt;데이터의 상태를 유지하고 데이터 처리 규칙을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;View (뷰)&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;사용자 인터페이스를 구성하며 Model로부터 받은 데이터를 시각적으로 표현&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;Controller (컨트롤러)&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;사용자 요청에 따라 Model을 업데이트하고 그 결과를 View에 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;관심사 분리(SoC)&lt;/b&gt;&lt;/span&gt;로 비즈니스 로직과 UI 로직을 독립적으로 관리&lt;/li&gt;
&lt;li&gt;유지보수성과 확장성이 향상됨&lt;/li&gt;
&lt;li&gt;각 컴포넌트별 독립적인 테스트 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  단점 및 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;View와 Model 의존성 증가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 이벤트, 서버 통신, 상태 변화 등으로 View와 Model의 상호작용이 복잡해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양방향 데이터 흐름(사용자 입력 &amp;rarr; Model &amp;rarr; View 업데이트)이 많아지면 복잡도가 급격히 증가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;Massive-View-Controller&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;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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SPA에서는 페이지 전환 없이 여러 상태와 화면을 관리해야 하므로 문제 심화&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;3️⃣ &lt;b&gt;View의 계층적 구조 문제&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;DOM의 트리 구조를 반영한 뷰 계층 관리 필요&lt;/li&gt;
&lt;li&gt;빈번한 리렌더링으로 인한 성능 이슈가 발생&lt;/li&gt;
&lt;li&gt;DOM 조작의 최적화 기법이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서 순수한 MVC 패턴 적용 시 다음과 같은 해결책이 필요합니다:&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;복잡한 View-Model 관계의 단순화&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;View 계층 처리를 통한 효율적인 DOM 관리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하고자 다음과 같은 방법들이 나타났습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MVVM(Vue)&lt;/b&gt; : 양방향 바인딩을 단순화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Virtual DOM(React)&lt;/b&gt; : 효율적인 DOM 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Flux/Redux&lt;/b&gt; : 단방향 데이터 흐름으로 상태 관리 단순화&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;2️⃣ MVVM&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s5HTd/btsQhqgEl9P/gUciv8hRTdRQh231r3QARk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s5HTd/btsQhqgEl9P/gUciv8hRTdRQh231r3QARk/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=Y5vOfv67h8A&amp;amp;amp;ab_channel=프롱트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s5HTd/btsQhqgEl9P/gUciv8hRTdRQh231r3QARk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs5HTd%2FbtsQhqgEl9P%2FgUciv8hRTdRQh231r3QARk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;234&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=Y5vOfv67h8A&amp;amp;ab_channel=프롱트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;MVVM 패턴&lt;/b&gt;&lt;/span&gt;은 MVC의 한계를 극복하기 위해 등장한 패턴으로 데이터 바인딩을 통해 뷰와 모델 사이의 동기화를 자동화하고 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;뷰모델(ViewModel)&lt;/b&gt;&lt;/span&gt; 이 뷰와 모델 사이의 중재자 역할을 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  구성 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Model (모델)&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;MVC와 동일하게 데이터와 비즈니스 로직을 담당&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;View (뷰)&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;ViewModel과 데이터 바인딩을 통해 자동으로 동기화&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;ViewModel (뷰모델)&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;/li&gt;
&lt;li&gt;뷰의 상태와 동작을 관리&lt;/li&gt;
&lt;li&gt;모델의 데이터를 뷰에서 쉽게 사용할 수 있는 형태로 가공하여 제공&lt;/li&gt;
&lt;li&gt;뷰의 이벤트를 받아 모델 변경으로 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;데이터 바인딩은 뷰와 모델이 자동으로 동기화되도록 하는 핵심 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현 방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Object.defineProperty()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Proxy API&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Observer 패턴&lt;/code&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;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let name = 'beomsic';
&amp;lt;/script&amp;gt;

&amp;lt;h1&amp;gt;Hello {name}!&amp;lt;/h1&amp;gt;

&amp;lt;input bind:value={name} /&amp;gt;&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;code&gt;bind:value={name}&lt;/code&gt;로 처리된 input의 value가 변경되면 자동으로 리렌더링되는 양방향 데이터 바인딩(Two-way Data Binding)을 보여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰와 모델 간의 복잡한 의존성 해결&lt;/li&gt;
&lt;li&gt;자동 동기화로 인한 개발 생산성 향상&lt;/li&gt;
&lt;li&gt;테스트 용이성 증대&lt;/li&gt;
&lt;li&gt;코드의 가독성과 유지보수성 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/li&gt;
&lt;li&gt;&lt;b&gt;추상화 비용&lt;/b&gt;: ViewModel 설계와 관리가 추가됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; 사용 사례&lt;/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;Vue.js&lt;/b&gt;: MVVM 패턴을 기반으로 설계된 대표적인 프레임워크&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Angular&lt;/b&gt;: 양방향 데이터 바인딩과 컴포넌트 기반 아키텍처&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;3️⃣ Flux 아키텍처 (React)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/da1gDv/btsQjlFl6nT/0SYNwlZYN5r4JtxSp2JJN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/da1gDv/btsQjlFl6nT/0SYNwlZYN5r4JtxSp2JJN0/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=Y5vOfv67h8A&amp;amp;amp;ab_channel=프롱트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/da1gDv/btsQjlFl6nT/0SYNwlZYN5r4JtxSp2JJN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fda1gDv%2FbtsQjlFl6nT%2F0SYNwlZYN5r4JtxSp2JJN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;327&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=Y5vOfv67h8A&amp;amp;ab_channel=프롱트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flux 아키텍처&lt;/b&gt;는 Facebook에서 React와 함께 소개한 &lt;b&gt;단방향 데이터 흐름&lt;/b&gt;을 기반으로 하는 아키텍처 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 패턴의 복잡한 양방향 데이터 흐름 문제를 해결하기 위해 설계되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ 단방향 데이터 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux의 가장 중요한 특징은 &lt;b&gt;한 방향으로만 데이터가 흐르도록 제어&lt;/b&gt;한다는 점입니다.&lt;/p&gt;
&lt;pre class=&quot;cos&quot;&gt;&lt;code&gt;View &amp;rarr; Action &amp;rarr; Dispatcher &amp;rarr; Store &amp;rarr; View&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  구성 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Action (액션)&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;type과 데이터(payload)를 포함&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;Dispatcher (디스패처)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;액션을 받아서 등록된 모든 스토어에 전달하는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Store (스토어)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션의 상태와 비즈니스 로직을 포함&lt;/li&gt;
&lt;li&gt;디스패처로부터 액션을 받아 상태를 업데이트&lt;/li&gt;
&lt;li&gt;상태 변경 시 뷰에 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;View (뷰)&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;React 컴포넌트로 구현되는 사용자 인터페이스&lt;/li&gt;
&lt;li&gt;스토어의 상태 변화를 감지하고 리렌더링&lt;/li&gt;
&lt;li&gt;사용자 인터랙션 시 액션을 디스패치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/li&gt;
&lt;li&gt;&lt;b&gt;액션 생성&lt;/b&gt;: 이벤트에 따른 액션 객체 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디스패치&lt;/b&gt;: 액션을 디스패처에 전달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스토어 업데이트&lt;/b&gt;: 디스패처가 모든 스토어에 액션 전달, 스토어에서 상태 변경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뷰 업데이트&lt;/b&gt;: 스토어의 상태 변화를 감지한 뷰가 리렌더링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  장점&lt;/h3&gt;
&lt;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;li&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Redux&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux는 Flux 패턴을 더욱 간소화하고 예측 가능하게 만든 상태 관리 라이브러리입니다.&lt;/p&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;Redux의 핵심 원칙&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;Single Source of Truth&lt;/b&gt;: 하나의 스토어에서 전체 상태 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;State is Read-only&lt;/b&gt;: 상태는 액션을 통해서만 변경 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Changes are Made with Pure Functions&lt;/b&gt;: 순수 함수로 구현&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;프론트엔드 아키텍처는 애플리케이션의 복잡성과 요구사항에 따라 선택되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 패턴은 고유한 장단점을 가지고 있으며 프로젝트의 성격, 팀의 경험, 그리고 장기적인 유지보수 계획을 고려하여 적절한 아키텍처를 선택하는 것이 중요한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관심사의 분리&lt;/b&gt;, &lt;b&gt;단방향 데이터 흐름&lt;/b&gt;, &lt;b&gt;예측 가능한 상태 관리&lt;/b&gt;라는 핵심 원칙을 바탕으로 프로젝트에 최적화된 아키텍처를 설계하는 것이 핵심!&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://f-lab.kr/insight/understanding-frontend-architecture&quot;&gt;https://f-lab.kr/insight/understanding-frontend-architecture&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@manyoung/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%9B%B9%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EB%B9%84%EA%B5%90%EB%B6%84%EC%84%9D-mvc%EC%99%80-mvvm-e446a0f46d8c&quot;&gt;https://medium.com/@manyoung/프론트엔드-웹애플리케이션-아키텍쳐-비교분석-mvc와-mvvm-e446a0f46d8c&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Y5vOfv67h8A&amp;amp;ab_channel=%ED%94%84%EB%A1%B1%ED%8A%B8&quot;&gt;https://www.youtube.com/watch?v=Y5vOfv67h8A&amp;amp;ab_channel=프롱트&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/twolinecode/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EA%B5%AC%ED%98%84-%EC%A0%84%EB%9E%B5-chapter2-by-%EC%9D%B4%EB%8F%99%ED%98%95%EB%8B%98-fb6672e74524&quot;&gt;https://medium.com/twolinecode/프론트엔드-아키텍처의-구현-전략-chapter2-by-이동형님-fb6672e74524&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@heoh06/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%ED%8A%B8-%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-mvc-%ED%8C%A8%ED%84%B4-c0639999820a&quot;&gt;https://medium.com/@heoh06/프론트엔트-기술면접-mvc-패턴-c0639999820a&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://f-lab.kr/insight/understanding-mvc-pattern-for-frontend?gad_source=1&amp;amp;gclid=Cj0KCQjwz7C2BhDkARIsAA_SZKb1xXqIITX1VVA3UrWKyLqZKNkZdzIP7mg4UDpo1IExFlTNI-zkrwsaApWEEALw_wcB&quot;&gt;https://f-lab.kr/insight/understanding-mvc-pattern-for-frontend?gad_source=1&amp;amp;gclid=Cj0KCQjwz7C2BhDkARIsAA_SZKb1xXqIITX1VVA3UrWKyLqZKNkZdzIP7mg4UDpo1IExFlTNI-zkrwsaApWEEALw_wcB&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/172</guid>
      <comments>https://beomsic.tistory.com/entry/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%EC%A1%B0-MVC-MVVM#entry172comment</comments>
      <pubDate>Tue, 2 Sep 2025 16:49:55 +0900</pubDate>
    </item>
    <item>
      <title> &amp;zwj;  구글 엔지니어는 이렇게 일한다 (2) - 팀워크 이끌어내기</title>
      <link>https://beomsic.tistory.com/entry/%F0%9F%A7%91%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-%EA%B5%AC%EA%B8%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%8A%94-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9D%BC%ED%95%9C%EB%8B%A4-2-%ED%8C%80%EC%9B%8C%ED%81%AC-%EC%9D%B4%EB%81%8C%EC%96%B4%EB%82%B4%EA%B8%B0</link>
      <description>&lt;h1&gt;  2. 팀워크 이끌어내기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 주제를 논하려면 여러분이 반드시 통제해야 하는 하나의 변수, 즉 &lt;code&gt;&amp;lsquo;여러분 자신&amp;rsquo;&lt;/code&gt; 을 조망하는 데서 시작해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 완벽하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 인간을 &amp;lsquo;간헐적 버그들의 집합&amp;rsquo;에 가깝다고 이야기하곤 하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 동료에 내재된 버그를 이해하려면 무엇보다 &lt;b&gt;여러분 내면에 서식하는 버그를 먼저 이해&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;이번 장의 핵심 주제는 소프트웨어 개발은 &amp;lsquo;팀의 단합된 노력&amp;rsquo;의 결실이라는 점입&lt;/span&gt;니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 엔지니어링팀이 (혹은 어떤 형태든 창의적 협업이) 성공하려면 겸손, 존중, 신뢰라는 핵심 원칙에 맞게 여러분 자신의 행동을 바로잡아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2.1 내 코드를 숨기고 싶어요.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 자신이 진행 중인 작업물을 다른 사람이 보고 판단하는걸 두려워합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 두려움은 인간의 본성에 속할 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 소프트웨어 개발에서의 좀 더 보편적인 경향을 찾아낼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;불안감&lt;/b&gt;&lt;/span&gt;은 사실 더 큰 문제의 징후임을 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2.2 천재 신화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서의 업무는 거의 대부분이 천재 수준의 지능을 요구하지 않는 반면, 모든 업무가 최소한의 사회성을 요구합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리의 경력을 미래로 이어주는 핵심은 &lt;b&gt;다른 사람과 얼마나 잘 협력하느냐&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2.3 숨기는 건 해롭다.&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  2.3.1 조기 감지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위대한 아이디어를 세상으로부터 숨기고 완벽히 다듬어질 때까지 아무도 들여다보지 못하게 하는 건 엄청난 도박입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 설계에는 근본적인 실수가 스며 있기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 피드백을 &lt;b&gt;&amp;lsquo;조기에&amp;rsquo;&lt;/b&gt; 받을수록 이러한 위험이 크게 줄어듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증된 주문인 &lt;b&gt;&amp;lsquo;일찍 실패하고, 빨리 실패하고, 자주 실패하라&amp;rsquo;.&lt;/b&gt; 를 기억해두세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조기 공유의 효과는 단지 개인적인 실수를 예방하고 아이디어를 검증하는 데 그치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 소위 &lt;code&gt;버스 지수&lt;/code&gt;를 높여주기도 하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  2.3.2 버스 지수&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;버스 지수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 명의 팀원이 버스에 치어서 일을 할 수 없게 될 때 프로젝트가 망하게 되는지를 나타내는 지수&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 지식을 동료 한 명과 공유한다면 버스 지수는 두 배가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소한 각 책임 영역마다 2차 소유자(담당자)를 두고 제대로 된 문서를 갖춰 둔다면 프로젝트의 미래를 보장하고 버스 지수를 높이는 데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⏩ 2.3.3 진척 속도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머는 긴밀하게 피드백받을 때 가장 효율적으로 일합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수 하나를 짜고 컴파일하고, 테스트 하나 짜고 컴파일하고, 리팩터링 살짝 하고 컴파일합니다.&lt;/li&gt;
&lt;li&gt;이 방법이 우리가 코드를 작성하자마다 가장 빠르게 오타와 버그를 잡는 길입니다.&lt;/li&gt;
&lt;/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;code&gt;가능한 한 일찍 피드백하고, 가능한 한 일찍 테스트하고, 보안과 프로덕션 환경을 가능한 한 초기부터 고려한다&lt;/code&gt;라는 목표를 천명하고 있습니다.&lt;/p&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; 원점 회귀(shift left)&lt;/b&gt;하라는 아이디어에 녹아 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 문제를 빨리 찾을수록 고치는 비용이 낮아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 피드백 루프는 코드 수준뿐 아니라 전체 프로젝트 수준에서도 이뤄져야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;야심 찬 프로젝트는 빠르게 진화하기 때문에 변화하는 환경에 잘 적응해야만 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각하지도 못한 설계 장애나 정치적인 장벽에 부딪히거나 단순히 무언가가 계확한 대로 동작하지 않을 수 있습니다. 요구사항이 기대와 다르게 바뀔 수도 있고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분이라면 계획이나 설계 변경이 필요한 시점을 즉시 알려줄 피드백 루프를 어떻게 마련할 것인가요? &amp;rarr; &lt;b&gt;정답은 팀플레이입니다&lt;/b&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;  2.4 모든 건 팀에 달렸다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lsquo;소프트웨어 엔지니어링은 팀의 단합된 노력입니다.&amp;rsquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람과 함께 일해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비전을 공유하세요.&lt;/li&gt;
&lt;li&gt;역할을 나누세요.&lt;/li&gt;
&lt;li&gt;다른 이로부터 배우세요.&lt;/li&gt;
&lt;li&gt;멋진 팀을 만드세요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 2.4.1. 사회적 상호작용의 세 기둥&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업의 열반에 들어가려면 가장 먼저 사회적 스킬의 세 기둥을 배우고 익혀야 합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;겸손(humility)&lt;/td&gt;
&lt;td&gt;당신은 모든 것을 알지도, 완벽하지도 않습니다. 겸손한 사람은 배움에 열려있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;존중(respect)&lt;/td&gt;
&lt;td&gt;함께 일하는 동료를 진심으로 생각합니다. 친절하게 대하고 그들의 능력과 성취에 감사해합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;신뢰(trust)&lt;/td&gt;
&lt;td&gt;동료들이 유능하고 올바른 일을 하리라 믿습니다. 필요하면 그들에게 스스로 방향을 정하게 해도 좋습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  모든 사람이 만족할 만큼 겸손한가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  다른 이들을 진심으로 존중하나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  서로 신뢰하나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  2.4.2 세 기둥이 중요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얻을 수 있는 교훈은 &lt;b&gt;&amp;lsquo;사회적 관계의 힘을 과소평가하지 말라&amp;rsquo;&lt;/b&gt;는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계는 언제나 프로젝트보다 오래 지속됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동료들과 끈끈해지면 여러분이 필요할 때 기꺼이 자신들의 수고를 마다하지 않을 것입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  2.4.3 겸손, 존중, 신뢰 실천하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  자존심 버리기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겸손은 중요하지만 그렇다고 바짝 엎드리라는 뜻은 아닙니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자신감을 갖는건 나쁠게 없다.&lt;/li&gt;
&lt;li&gt;그저 모든 걸 다 아는 듯 행동하지 말라는 뜻입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나은 방법이 있습니다. 바로 &lt;b&gt;&amp;lsquo;집단적&amp;rsquo; 자존심&lt;/b&gt;을 찾는 것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신이 잘 아는 분야에 대해 걱정하는 대신 팀의 성취와 단체의 자부심을 높이려 노력하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  비평하고 비평받는 법 배우기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문적인 소프트웨어 엔지니어링 환경이라면 비평에 개인적인 감정이 실리는 경우는 거의 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 환경을 조성하려면 먼저 &amp;lsquo;누군가의 창조적 산출물에 대한 건설적인 비평&amp;rsquo;과 &amp;lsquo;다른 이의 성향에 대한 맹렬한 공격&amp;rsquo;의 차이를 모두가 바르게 이해해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건설적 비판은 프로젝트에 도움이 되며 개선을 위한 지침을 줄 수 있고, 또 주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 가장 중요한 점은 &lt;b&gt;건설적으로 비판하는 사람은 상대방을 진심으로 생각하고 상대방의 업무가 개선되길 바라야 한다는 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;동료를 존중하는 법을 배우고 건설적이고 공손하게 비평하는 법&lt;/b&gt;을 배워야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분 자신도 비평을 잘 수용할 줄 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 기술에 겸손해야 함은 물론 상대는 내 최우선 관심사를 진심으로 생각하며 절대 나를 어리석다고 생각하는 게 아님을 믿어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 자존감을 우리가 작성한 코드(혹은 그 어떤 창작물이더라도)와 동일시해서는 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가에게 &amp;lsquo;잘못했다&amp;rsquo;라고 해서는 안 됩니다. 무언가를 &amp;lsquo;고치라고 요구&amp;rsquo;해서도 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;↩️ &lt;b&gt;빠르게 실패하고 반복하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서 제가 정말 좋아하는 좌우명은 &lt;b&gt;&amp;lsquo;실패는 선택이다&amp;rsquo;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에서는 &amp;lsquo;가끔씩 실패하지 않는다면 충분히 혁신적이지 않거나 위험을 충분히 감수하지 않은 것이다&amp;rsquo;라는 믿음이 널리 통용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패를 &amp;lsquo;배우고 다음 단계로 넘어갈 수 있는 절호의 기회&amp;rsquo;라고 생각하는 것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 만 가지의 잘못된 방식을 찾아낸 것일 뿐 실패한 게 아니다. 잘못하고 폐기한 시도 하나하나가 다음 단계로 이끌어주기 때문에 나는 낙담하지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-토머스 에디슨-&lt;/p&gt;
&lt;/blockquote&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.4.4 비난 없는 포스트모템 문화&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패한 근본 원인을 분석하여 문서로 남기는 것이 실수로부터 배우는 핵심입니다. 이를 구글은 &lt;b&gt;포스트 모템(postmortem)&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 포스트모템 문서가 쓸모없는 사죄, 변명, 지적으로 채워지지 않도록 각별히 주의하세요. 이건 포스트모템의 목적이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제대로 된 모스트포템에는 무엇을 배웠는지와 배운 것을 토대로 앞으로 무엇을 바꿀지가 담겨야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 포스트모템을 쉽게 열람할 수 있고 포스트모템에서 제안한 변화를 팀이 실천하는지 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패를 제대로 기록해두면 다른 이들도 무슨 일이 있었는지 알 수 있고 똑같은 실수를 반복하는 일을 피할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훌륭한 &lt;b&gt;포스트 모템&lt;/b&gt;에는 다음 내용이 담겨야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사건의 개요&lt;/li&gt;
&lt;li&gt;사건을 인지하고 해결에 이르기까지의 타임라인&lt;/li&gt;
&lt;li&gt;사건의 근본 원인&lt;/li&gt;
&lt;li&gt;영향과 피해 평가&lt;/li&gt;
&lt;li&gt;문제를 즉시 해결하기 위한 조치 항목&lt;/li&gt;
&lt;li&gt;재발 방지를 위한 조치 항목&lt;/li&gt;
&lt;li&gt;해당 경험에서 얻은 교훈&lt;/li&gt;
&lt;/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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 이로부터 배우는 데 열려 있을수록 여러분의 영향력도 커집니다. 결점이 많은 사람일수록 더 강해보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경험상 고집불통 팀원의 의견이나 반대에는 더 이상 귀 기울이지 않게 되고, 대신 공인된 장애물 취급하며 피해 다닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분도 이런 사람이 되길 원치는 않을 테니&lt;b&gt; &amp;lsquo;다른 사람이 내 생각을 바꿔도 괜찮아&amp;rsquo;&lt;/b&gt;라는 생각을 항상 머릿속에 담아두길 바랍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 결점을 드러낸다는 것은 겸손을 겉으로 표현하는 일이며, 책임을 지고 의무를 다 하려는 의지의 표출입니다.&lt;/li&gt;
&lt;li&gt;그리고 다른 이들의 의견을 신뢰한다는 신호이기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 사람들은 당신의 솔직함과 용기를 존중하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때때로 여러분이 할 수 있는 최선의 말은 &amp;lsquo;저는 잘 모르겠습니다&amp;rsquo;일 수도 있습니다&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.4.5 구글답게 하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;구글 다움&amp;rsquo;이 갖춰야할 기준&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강력한 리더십을 보이고 &amp;lsquo;겸손, 존중, 신뢰&amp;rsquo;를 드러내는 태도와 행동들을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 모호함을 뚫고 번창한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;끊임없이 변화하는 환경 속에서도 상충하는 메시지와 방향에 잘 대처하고, 합의를 이끌어내고, 문제에 대한 진전을 이룰 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 피드백을 소중히 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;피드백을 주고받을 때 품위와 겸손을 유지하고 개인과 팀의 발전에 피드백이 주는 가치를 이해합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 저항(항상성)을 극복한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 이들이 저항하거나 관성 때문에 움직이지 않으려 하더라도 야심 찬 목표를 세우고 밀고 나아갑니다&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;4️⃣ 사용자를 우선한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글 제품의 사용자 입장에서 생각하고 존중하며 그들에가 가장 도움되는 행동을 추구합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5️⃣ 팀에 관심을 기울인다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동료들의 입장에서 생각하고 존중하며 팀의 결집을 위해 누가 시키지 않더라도 적극적으로 돕습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6️⃣ 옳은 일을 한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 일에 강한 윤리 의식을 갖고 임합니다. 팀과 제품의 진정성을 지키기 위해서라면 어렵거나 불편한 결정을 내릴 수 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  2장을 읽고나서,,,&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 내 코드를 숨기고 싶어요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 코드를 동료에게 보여주거나 리뷰받을 때 불안함을 자주 느꼈다. 완벽하지 못한 결과물이 드러나는 것이 두려웠지만 이것이 대부분 개발자에게 자연스러운 감정임을 책을 통해 다시 깨달았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점과 실수에 집착하는 대신 불안의 뿌리가 결국 성장의 기회임을 받아들이는 연습이 필요함을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 버스 지수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 인턴 생활을 할 때 작업에 대한 담당자 / 2차 담당자를 지정해 진행하고 일의 맥락을 꼼꼼히 기록하고 공유하는 문화였습니다. 이런 문화 덕분에 누군가 빠져도 프로젝트가 잘 이어질 수 있었는데 이런 경험을 통해서 버스 지수가 얼마나 실무에서 중요한지 이해할 수 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기롬은 꼼꼼하게 공유는 투명하게!@&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 사회적 상호작용의 세 기둥&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 팀 프로젝트 협업 상황에서 &amp;lsquo;겸손, 존중, 신뢰&amp;rsquo;의 가치를 종종 실감했는데 특히 인턴십 과정에서 해당 회사에서는 &amp;lsquo;서로 신뢰하고 솔직하게 소통&amp;rsquo; &amp;lsquo;나보다 뛰어난 동료와 함께 한다&amp;rsquo; 라는 팀 문화가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나보다 뛰어난 사람과 일하며 배움의 기회를 얻고 서로 격렬하게 의견을 교환해도 신뢰를 기반으로 솔직한 소통이 가능했었던 것 같고 실패와 충돌도 두려움 없이 받아들이며 자신이 하는 작업의 퀄리티과 자신감을 동시에 높일 수 있었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 피드백과 반복&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;빠르게 실패하고 반복하라&amp;rsquo;는 구글의 철학처럼 피드백을 빠르고 자주 받으며 개선해나가는 것이 내가 개발자로서 성장하는데 중요한 요소중 하나겠구나.. 계획이 틀어지거나 설계에 변경이 필요할 때도 혼자 고민하지 않고 즉각 팀과 공유하여 결정하는 과정을 통해서 내가 놓친 부분에 대해서 보완해나가자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 실패 후 기록과 학습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회고나 포스트모템 문화를 통해 결과만이 아니라 그 과정에서 무엇을 배웠고 앞으로 어떻게 바꿀지 생각하는 습관이 중요함을 다시 깨닫게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 실패를 숨기지 않고 문서화해 공유하는 것이 다음 성공을 위한 중요한 요소구나!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실수와 불안은 성장의 기회!&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기록과 공유&lt;/b&gt;가 개인과 팀, 프로젝트 모두에게 도움이 된다!&lt;/li&gt;
&lt;li&gt;&lt;b&gt;겸손, 존중, 신뢰&lt;/b&gt;를 통해서 프로젝트를 진행하자.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빠른 피드백&amp;ndash;반복&amp;ndash;학습&lt;/b&gt;이 개발자로 성장하는 것과 곧 나의 실력이다!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000061352347&quot;&gt;https://product.kyobobook.co.kr/detail/S000061352347&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  book/ &amp;zwj;  구글 엔지니어는 이렇게 일한다</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/171</guid>
      <comments>https://beomsic.tistory.com/entry/%F0%9F%A7%91%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-%EA%B5%AC%EA%B8%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%8A%94-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9D%BC%ED%95%9C%EB%8B%A4-2-%ED%8C%80%EC%9B%8C%ED%81%AC-%EC%9D%B4%EB%81%8C%EC%96%B4%EB%82%B4%EA%B8%B0#entry171comment</comments>
      <pubDate>Tue, 2 Sep 2025 09:47:21 +0900</pubDate>
    </item>
    <item>
      <title> &amp;zwj;  구글 엔지니어는 이렇게 일한다 (1) - 소프트웨어 엔지니어링</title>
      <link>https://beomsic.tistory.com/entry/%F0%9F%A7%91%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-%EA%B5%AC%EA%B8%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%8A%94-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9D%BC%ED%95%9C%EB%8B%A4-1-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81</link>
      <description>&lt;h1&gt;  1. 소프트웨어 엔지니어링&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 엔지니어링은 흐르는 시간 위에서 순간순간의 프로그래밍을 모두 합산한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-구글-&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 엔지니어링에서 프로그래밍이 큰 비중을 차지하는 건 틀림없지만 프로그래밍은 결국 새로운 소프트웨어를 제작하는 수단입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이를 받아들인다면 자연스럽게 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로그래밍 작업(개발, development)&lt;/b&gt;&lt;/span&gt;과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;소프트웨어 엔지니어링 작업(개발 / development + 수정 / modification + 유지보수 / maintenance)&lt;/b&gt;&lt;/span&gt;의 차이도 궁금할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;시간&lt;/b&gt;&lt;/span&gt;이라는 요소가 더해지면서 프로그래밍에는 중요한 차원이 하나 늘어서 더 입체적으로 바뀝니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 프로그램에 미치는 영향을 알아보려면 &amp;ldquo;이 코드의 예상 수명은?&amp;rdquo; 이라는 질문을 던져보면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧게 생을 마감하는 코드는 대체로 시간의 영향을 받지 않습니다. 하지만 수명이 길어질수록 변경이라는 요소가 점점 중요해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 생각하는 소프트웨어 엔지니어링과 프로그래밍을 가르는 핵심은 이 사실을 인식하는 데서 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⏱️ 1-1. 시간과 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;소프트웨어 프로젝트의 기대수명과 업그레이드의 중요도의 관계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 외부 환경의 변화에 대비하기 시작해야 하고 지속 가능하게 하기 위해서는 요구되는 변경들의 영향을 계획하고 관리해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  단명하는 프로그램용 코드와 수명이 훨씬 긴 프로젝트가 만들어내는 코드는 구체적으로 어떻게 다를까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수명이 길어질 수록 &lt;code&gt;동작한다&lt;/code&gt; 와 &lt;code&gt;유지보수 가능하다&lt;/code&gt;의 차이를 더 분명히 인지해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘을 구분하는 완전한 해법은 없습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어를 &lt;b&gt;장기간 유지보수&lt;/b&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.1.1 하이럼의 법칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔지니어들이 프로젝트를 유지보수 하고 있다면 &lt;code&gt;동작한다&lt;/code&gt; 와 &lt;code&gt;유지보수 가능하다&lt;/code&gt; 를 구분 짓는 가장 중요한 요인은 &lt;b&gt;하이럼의 법칙&lt;/b&gt; 일 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;API 사용자가 충분히 많다면 API 명세에 적힌 내용은 중요하지 않습니다.&lt;/li&gt;
&lt;li&gt;시스템에서 눈에 보이는 모든 행위(동작)를 누군가는 이용하게 될 것이기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  시간의 흐름에 따른 변경과 유지보수를 이야기하기 위해서는 하이럼의 법칙을 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이럼의 법칙은 최선의 의도, 최고의 엔지니어, 꼼꼼한 코드 리뷰가 뒷받침되더라도 공표한 계약(명세)이나 모범 사례를 완벽하게 구현해냈다고 단정할 수 없다는 현실을 표현한 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 소유자는 인터페이스를 명확하게 설명해놓으면 어느 정도의 유연성과 자유를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현실에서는 API 사용자가 (명세에는 없는) 기능을 찾아 활용하기도 하며 그 기능이 유용해 널리 쓰이면 추후 API를 변경하기 어렵게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 변경은 누군가의 워크플로와 충돌한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 변경이 얼마나 유용할 지를 분석할 때는 이러한 충돌을 조사, 식별, 해결하는 데 따르는 비용도 고려해야 합니다.&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.1.2 사례: 해시 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;동작한다&lt;/code&gt; 와 &lt;code&gt;옳다&lt;/code&gt; 의 차이를 보여주는 아주 기초적인 예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 컨테이너의 반복 순서에 의존하더라도 프로그램의 수명이 짧다면 기술적 문제를 겪지 않을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 소프트웨어 엔지니어링 프로젝트에서 이런 의존성은 &lt;b&gt;위험 요인&lt;/b&gt;에 해당합니다.&lt;/p&gt;
&lt;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;당장 돌아가야 한다&lt;/code&gt; 라는 생각으로 작성한 코드와 &lt;code&gt;언제까지고 작동해야 한다&lt;/code&gt; 라는 생각으로 작성한 코드의 차이를 생각해보면 어떤 관계인지가 분명하게 그려질 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이용하는 API의 명세에 명시되지 않은, 즉 언제든 변할 수 있는 기능을 사용하는 코드는 &amp;ldquo;임시방편적인&amp;rdquo; 혹은 &amp;ldquo;기발한&amp;rdquo; 코드입니다.&lt;/li&gt;
&lt;li&gt;반대로 모범 사례를 따르고 미래에 대비한 코드는 &lt;b&gt;&amp;ldquo;클린하고 유지보수 가능한&amp;rdquo;&lt;/b&gt; 코드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.1.3 &amp;lsquo;변하지 않기&amp;rsquo;를 목표로 하지 않는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간과 변경에 대처하는 이유에 관한 모든 논의의 밑바탕에는 &lt;b&gt;변경은 피할수 없다&lt;/b&gt; 라는 가정이 깔려있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;구글은 데이터센터를 비용 효율적인 장비들로 꾸리길 원합니다(특히 CPU 효율 개선에 힘씁니다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 구글에서 오래전에 만들어둔 알고리즘과 데이터 구조는 최신 장비에서 효율이 떨어지기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 리스트와 이진 검색 트리는 여전히 잘 동작하지만 CPU 클록과 메모리 지연시간의 격차가 점점 벌어지면서 &amp;ldquo;효율적인&amp;rdquo; 코드의 모습이 변하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 소프트웨어 설계도 제때 변경해주지 않으면 최신 하드웨어를 도입하는 효과가 퇴색됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;↗️ 1-2. 규모 확장과 효율성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직에서 코드를 작성하고 관리하는 데 활용하는 모든 것이 총 비용과 자원 소비 측면에서 확장 가능해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.2.2 확장 가능한 정책들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 좋아하는 구글 내부 정책 중 인프라팀이 인프라 변경을 안전하게 진행하게끔 보호해주는 정책이 하나 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &amp;ldquo;인프라를 변경하여 서비스가 중단되는 등의 문제가 발생하더라도 같은 문제가 지속적 통합(Continuous Integration, CI) 시스템의 자동 테스트에서 발견되지 않는다면 인프라 팀의 책임이 아니다&amp;rdquo;라는 정책입니다.&lt;/p&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;ldquo;비욘세 규칙&amp;rdquo;이며 친군하게 표현하면 &amp;ldquo;네가 좋아했다면 CI 테스트를 준비해뒀어야지&amp;rdquo;라는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 CI 시스템에 추가해두지 않은 테스트는 인프라 팀이 책임지지 않는다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  우리는 전문성과 공유 포럼이 조직 확장에 기여하는 바가 크다는 사실을 깨달았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔지니어들이 포럼에 질문하고 답하는 과정에서 지식이 전파되고 새로운 전문가가 성장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.2.4 원점 회귀(왼쪽으로 옮기기)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 워크플로를 시간순으로 생각했을 때 문제 발견 시점을 왼쪽으로 이동시킬 수록 수정 비용이 줄어듭니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽으로 옮기는 행위를 &lt;b&gt;원점 회귀&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 커밋 전에 정적 검사나 코드 리뷰로 찾아낸 버그는 프로덕션 이후에 발견한 버그보다 훨씬 싸게 고칠 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그래서 개발 프로세스 초기에 품질, 안정성, 보안 문제를 찾아 알려주는 도구와 관례를 제공하는 일은 구글 인프라 팀의 주요 목표중 하나입니다.&lt;/blockquote&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.3 트레이드오프와 비용&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 결정에 이르는 과정은 쉽게 간과되곤 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  비용의 정확한 뜻&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용은 금액만을 지칭하는 것이 아닌 투입된 노력과 다음의 요소들까지 모두 포괄합니다.&lt;/p&gt;
&lt;table style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;비용&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;금융 비용&lt;/td&gt;
&lt;td&gt;돈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리소스 비용&lt;/td&gt;
&lt;td&gt;CPU 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인적 비용&lt;/td&gt;
&lt;td&gt;엔지니어링 노력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;거래 비용&lt;/td&gt;
&lt;td&gt;조치를 취하는 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기회 비용&lt;/td&gt;
&lt;td&gt;조치를 취하지 않는 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사회적 비용&lt;/td&gt;
&lt;td&gt;선택이 사회 전체에 미치는 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 엔지니어링 처럼 창의적이고 수익성이 높은 분야에서는 금융 비용보다는 인적 비용이 제한 요소일 가능성이 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔지니어들이 행복을 느끼게 만들고 일에 집중하고 참여할 수 있게 해주면 효율이 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.3.1 사례: 화이트보드 마커&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 엔지니어링 조직의 선택을 결정짓는 요인은 다음의 몇 가지로 압축됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반드시 해야 하는 일(법적 요구사항, 고객 요구사항)&lt;/li&gt;
&lt;li&gt;근거에 기반하여 당시 내릴 수 있는 최선의 선택(적절한 결정권자가 확정)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  의사결정이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;내가 시켰으니까&lt;/b&gt;&lt;/span&gt;가 되어서는 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.3.3 사례: 분산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전혀 과학적이지 않은 트위터 여론조사에 따르면 소프트웨어가 거대해진 오늘날에도 약 60%~70%의 개발자가 빌드를 로컬에서 실행한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분의 조직에서는 빌드가 끝나기를 기다리느라 얼마나 많은 생산적인 시간을 낭비하고 있나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000년대 중반 구글은 로컬에서 컴파일 했으며 코드베이스가 커져가며 컴파일 시간도 꾸준히 늘어났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 구글은 자체 분산 빌드 시스템을 개발했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템을 개발할 엔지니어를 투입해야 했고, 다른 모두의 컴파일 습관과 워크플로를 바꿔 새로운 시스템에 적응시키는 데는 더 많은 엔지니어 시간이 투입됐습니다. 빌드 시스템 자체를 구동할 컴퓨팅 자원도 별도로 필요했음은 물론입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 전체적으로는 절약되는 비용이 훨씬 컸음이 명백했습니다. 빌드가 빨라졌고 엔지니어가 멍 때리는 시간이 줄었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1.3.5 결정 재고하기와 잘못 인정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;잘 드러나지는 않지만 잘못했음을 인정할 수 있게 해주는 능력&lt;/b&gt;&lt;/span&gt; 역시 데이터 중심 문화가 주는 커다란 장점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 취합한 데이터에 기초하여 특정 시점에 결정을 내립니다. 제대로 된 데이터이고 가정의 수는 가능한 한 적어야 좋겠지만 결정 시점에 가용한 데이터만을 활용해야 한다는 한계가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 새로운 데이터를 얻어 상황이 바뀌거나 가정이 무너진다면 기존 결정에 오류가 있었음이 밝혀질 수도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당시에는 옳았지만 지금은 아닐 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시간&lt;/b&gt;은 기술적 의존성과 소프트웨어 시스템뿐 아니라 의사결정에 활용되는 데이터도 달라지게 하기 때문이죠.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우리는 데이터에 기초한 의사결정을 강력히 지지합니다.&lt;/li&gt;
&lt;li&gt;하지만 데이터 자체도 시간이 지나면 변하고 새로운 데이터가 나타날 수 있음을 알죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 시스템의 생애 동안 &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;받습니다.&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;  소프트웨어 엔지니어링 vs 프로그래밍&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 관련은 깊지만 상이한 두 용어인 &lt;code&gt;프로그래밍&lt;/code&gt; 과 &lt;code&gt;소프트웨어 엔지니어링&lt;/code&gt; 을 구별해야 한다고 생각합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 차이 대부분은 시간 흐름에 따른 코드 관리, 시간 흐름에 따른 규모의 확장의 영향, 이런 관점에서의 의사결정 방식에 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍은 코드를 생산하는 즉각적인 행위입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 엔지니어링은 활용 가치가 남아 있는 한 오랫동안 코드를 유용하게 관리하고 팀 간 협업을 가능케하는 정책, 관례, 도구 모두를 아우르는 종합적인 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  1장을 읽고나서,,,&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로그래밍과 소프트웨어 엔지니어링의 본질적 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그래밍&lt;/b&gt;은 &amp;ldquo;작동하는 코드&amp;rdquo;를 만드는 즉각적 행위라면 &lt;b&gt;소프트웨어 엔지니어링&lt;/b&gt;은 코드의 &amp;ldquo;장기적 생존&amp;rdquo;과 관련된 모든 것(정책, 관례, 도구, 협업 등)을 아우르는 개념이구나!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 시간과 변경 / 유지보수의 중요성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간이 흐름에 따라 &amp;ldquo;동작하는 것&amp;rdquo;과 &amp;ldquo;유지보수 가능한 것&amp;rdquo;의 차이가 점점 커진다는 점 그리고 이 차이를 극복하는 데 명확한 해법이 없다..!&lt;/li&gt;
&lt;li&gt;하이럼의 법칙처럼 사용자의 의도하지 않은 동작으로 인해 코드 변경 비용으로 이어질 수 있음을 배움.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 정책, 도구, 자동화의 필요성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글의 인프라팀 정책 &amp;ldquo;비욘세 규칙&amp;rdquo;와 같은 정책과 자동화(자동 테스트, CI 등)를 통해 대규모 조직에서도 시스템을 안정적으로 운영하는 것을 배움&lt;/li&gt;
&lt;li&gt;코드 품질을 보장하고 비용을 줄이기 위한 방법을 고민을 해야겠다.. (&lt;code&gt;원점 회귀 전략&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 비용과 트레이드오프를 고민하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어 개발에서 비용은 단순한 금전뿐 아니라 엔지니어의 노력, 시스템 자원, 의사 결정 비용, 기회비용 등 다양한 형태로 존재함을 배움.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;현재의 최선의 선택&lt;/code&gt;이 나중에 바뀔 수 있음을 인정하고 데이터와 환경의 변화에 따라 결정을 재고하는 유연성의 중요성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장기적이고 지속 가능한 소프트웨어를 만들려면 코딩을 넘어서 &lt;b&gt;설계, 프로세스 개선, 협업, 자동화, 문화&lt;/b&gt; 전반에 관심을 가져야 하며 시간의 흐름에 대응하는 유연한 사고가 필요하다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Ref.&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000061352347&quot;&gt;https://product.kyobobook.co.kr/detail/S000061352347&lt;/a&gt;&lt;/p&gt;</description>
      <category>  book/ &amp;zwj;  구글 엔지니어는 이렇게 일한다</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/170</guid>
      <comments>https://beomsic.tistory.com/entry/%F0%9F%A7%91%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-%EA%B5%AC%EA%B8%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%8A%94-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9D%BC%ED%95%9C%EB%8B%A4-1-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81#entry170comment</comments>
      <pubDate>Sun, 31 Aug 2025 11:51:05 +0900</pubDate>
    </item>
    <item>
      <title>워커 스레드를 통한 멀티 쓰레딩</title>
      <link>https://beomsic.tistory.com/entry/%EC%9B%8C%EC%BB%A4-%EC%8A%A4%EB%A0%88%EB%93%9C%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%A9%80%ED%8B%B0-%EC%93%B0%EB%A0%88%EB%94%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://beomsic.tistory.com/entry/Nodejs-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B7%B8%EB%A6%AC%EA%B3%A0-libuv&quot;&gt;이전 글&lt;/a&gt;을 통해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS는 &lt;b&gt;이벤트 루프&lt;/b&gt;를 가진 &lt;b&gt;싱글 스레드 모델&lt;/b&gt;을 사용해 파일 읽기, 네트워크 요청, 데이터베이스 쿼리와 같은 IO 작업을 메인 스레드를 차단하지 않고 효율적으로 처리하는 것을 학습했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;논 블로킹 I/O와 멀티 플렉싱을 활용함으로 작업 실행을 차단하지 않고 성능을 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 하지만 이런 싱글 스레드 모델에서의 문제점도 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;I/O 작업&lt;/b&gt;&lt;/span&gt;은 논블로킹으로 잘 처리하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CPU 집약적인 작업&lt;/b&gt;&lt;/span&gt;은 메인 이벤트 루프를 차단합니다.&lt;/li&gt;
&lt;li&gt;전체 애플리케이션이 응답하지 않게 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;따라서 병목 현상이 되는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CPU 집약적 작업&lt;/b&gt;&lt;/span&gt;에는 적합하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Node.js 에서 I/O 작업&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;다시 한번 Node.js 는 싱글 스레드환경에서 어떻게 I/O 작업을 처리하는지 살펴보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xbjsi/btsQcVuHSKn/YYKfEYnjJ9DuCohKUyzmv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xbjsi/btsQcVuHSKn/YYKfEYnjJ9DuCohKUyzmv0/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=Vej327jN8WI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xbjsi/btsQcVuHSKn/YYKfEYnjJ9DuCohKUyzmv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxbjsi%2FbtsQcVuHSKn%2FYYKfEYnjJ9DuCohKUyzmv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;581&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=Vej327jN8WI&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;Node.js는 싱글 스레드에서 실행되어 한 번에 하나의 Javascript 작업만 실행할 수 있습니다.&lt;/p&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;libuv&lt;/code&gt; 덕분에 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;동작 원리&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;파일 시스템 접근과 같은 시간이 많이 소요되는 작업을 libuv의 스레드풀을 이용하여 오프로드합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 Node.js는 Javascript 수준에서는 단일 스레드이지만 내부적으로는 스레드를 사용합니다.&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;code&gt;libuv&lt;/code&gt;는 Node.js 의 비동기 동작을 구동하고 call stack에서 자바스크립트 코드는 한 번에 하나씩 실행되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;♂️ &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;1️⃣ Node.js는 파일 읽기나 비밀번호 해시와 같은 I/O 작업을 접하게 되면 libuv에 넘겨지고 libuv는 내부적으로 이 비동기 작업들을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ libuv는 I/O 작업을 task queue에 넣은 후 libuv 스레드 풀에 있는 스레드 중 하나가 작업을 선택하여 백그라운드에서 처리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 스레드들은 자바스크립트 로직을 수행하지 않고 디스크나 네트워크 접근과 같은 작업을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 작업이 완료되면 libuv는 이벤트 디멀티플렉서를 이용해 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;4️⃣ 이벤트 루프는 이벤트 queue에서 완료된 작업을 가져오고 완료된 I/O 작업과 관련된 콜백이 call stack으로 전송되어 V8 엔진에서 실행됩니다.&lt;/p&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;  Node.js는 싱글 스레드이지만 libuv와 내부 스레드 풀을 이용해 메인 스레드를 블로킹하지 않고 I/O가 많은 작업을 효율적으로 처리할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 쿼리, 파일 읽기등의 I/O 작업을 수행할 때 Node.js 는 이런 작업을 시스템 커널이나 운영체제에 오프로드하여 I/O 작업이 완료될 때까지 다른 작업을 실행할 수 있게 합니다.&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;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const fs = require(&quot;fs/promises&quot;);

async function readFile() {
  console.log(&quot;Start reading file...&quot;);
  const data = await fs.readFile(&quot;file.txt&quot;, &quot;utf8&quot;);
  console.log(&quot;File content: &quot;, data);
  console.log(&quot;Finished reading..&quot;);
}

readFile();
console.log(&quot;Hello World!&quot;);

------결과-------
Start reading file...
Hello World!
File content:  
Finished reading..&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;이 코드에서 fs.readFile 을 OS가 별도의 I/O 스레드에서 처리하기 때문에 논블로킹으로 파일을 읽는 동안 이벤트 루프는 다른 요청이나 작업을 처리할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js 는 파일 읽기 작업을 libuv의 스레드풀로 보냅니다.&lt;/li&gt;
&lt;li&gt;파일을 읽는 동안 이벤트 루프가 계속 진행됩니다. (바로 &lt;code&gt;Hello World!&lt;/code&gt; 를 볼 수 있습니다.)&lt;/li&gt;
&lt;li&gt;파일을 읽고나면 비동기 함수가 실행을 재개해 파일 내용과 완료 메시지를 출력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;await fs.readFile(...);&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;Node.js에게 이 함수를 여기서 일시 중지하지만 프로그램의 나머지는 차단하지 말아라! 라고 알려줍니다.&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;⚠️ libuv 스레드는 Javascript 코드를 실행하지 않는다.&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  자바스크립트 코드를 사용하지 않는다는게 무슨 말이지??&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;자바스크립트 코드와 네이티브 코드(C/C++) 의 차이를 이해해보기&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 작성한 코드는 &lt;b&gt;자바스크립트 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const fs = require('fs');

fs.readFile('data.txt', 'utf8', (err, data) =&amp;gt; {
  if (err) throw err;
  console.log('파일 내용:', data);
});&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;fs.readFile&lt;/code&gt; 이 &lt;b&gt;자바스크립트 함수&lt;/b&gt;처럼 보이지만 실제로는 Node.js가 내부적으로 &lt;b&gt;네이티브 C/C++ 코드&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;/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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;code&gt;fs.readFile&lt;/code&gt; 호출 (작성한 자바스크립트 코드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ Node.js 내부에서 &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;code&gt;fs.readFile&lt;/code&gt; 는 &lt;code&gt;process.binding('fs')&lt;/code&gt;를 통해 C++에 등록된 함수와 연결됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ libuv 스레드 풀에서 실제 I/O 처리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 파일 읽기는 OS 시스템 콜 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ 작업 완료되면 이벤트 루프를 통해 Javascript 콜백 실행&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다시 V8이 돌아와서 Javascript 코드(&lt;code&gt;console.log(...)&lt;/code&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;&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;: V8이 실행하는 코드 (우리가 작성한 &lt;code&gt;fs.readFile&lt;/code&gt;, 콜백 함수, &lt;code&gt;console.log&lt;/code&gt; 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네이티브 코드(C/C++)&lt;/b&gt;: Node.js 내부, libuv 스레드 풀에서 실행되는 코드 (실제 파일 열기, 읽기 등 OS 호출)&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;libuv 스레드가 실행하는 건 자바스크립트가 아니라 C/C++ 네이티브 코드&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 코드로 &lt;code&gt;fs.readFile&lt;/code&gt;을 호출하는 것은 &amp;ldquo;C 함수 호출&amp;rdquo;을 요청한 것에 불과합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;동작 과정 정리&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;[내가 작성한 JS 코드] ─&amp;gt; V8 엔진 ─&amp;gt; Node.js 네이티브 바인딩 ─&amp;gt; libuv(C) 스레드 풀 ─&amp;gt; OS
                                              │
                                              ▼
                                    완료 시 이벤트 루프에서 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;✅ &lt;b&gt;정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우리가 작성하는 건 전부 자바스크립트 코드가 맞습니다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;fs.readFile&lt;/code&gt;, &lt;code&gt;crypto.pbkdf2&lt;/code&gt; 같은 함수는 &lt;b&gt;C/C++ 네이티브 코드와 연결된 Javascript API&lt;/b&gt;일 뿐&lt;/li&gt;
&lt;li&gt;libuv 스레드가 직접 &lt;b&gt;Javascript 코드를 실행하는 건 불가능&lt;/b&gt;합니다. (V8만 가능)&lt;/li&gt;
&lt;li&gt;libuv는 단지 &amp;ldquo;Javascript가 요청한 무거운 작업을 대신 실행하고 결과를 다시 Javascript로 알려주는 역할&amp;rdquo;을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 예시 - 피보나치 수열 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피보나치 수열을 계산하는 코드가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CPU 집약적인 작업&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function fibonacci(n) {
  if (n &amp;lt;= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const result = fibonacci(40);
console.log(result);
console.log(&quot;Hello World!&quot;);

------결과-------
102334155
Hello World!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업들은 메인 스레드에서 실행되어 그 동안에는 다른 일이 일어날 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Hello World!&lt;/code&gt; 출력은 피보나치 계산이 완료될 때 까지 지연됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 수학적 연산, loop, JSON parsing 같은 Javascript 로직이 메인 스레드에서 실행되는 것을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이런 작업들에 대해서는 libuv의 스레드는 도움이 되지 않습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 이건 I/O 작업이 아니기 때문!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  자바스크립트 코드는 오직 &lt;b&gt;V8 엔진의 메인 스레드&lt;/b&gt;에서만 실행&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CPU 집약적인 Javascript 작업에 대한 처리하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ 워커 스레드&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNhhzl/btsQeB28lV9/h4ujda6VGKGzAy9SvS4Gg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNhhzl/btsQeB28lV9/h4ujda6VGKGzAy9SvS4Gg1/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=Vej327jN8WI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNhhzl/btsQeB28lV9/h4ujda6VGKGzAy9SvS4Gg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNhhzl%2FbtsQeB28lV9%2Fh4ujda6VGKGzAy9SvS4Gg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;481&quot; height=&quot;583&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=Vej327jN8WI&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;워커 스레드를 사용하면 이벤트 루프를 차단하지 않고 별도의 스레드에서 Javascript 코드를 병렬로 실행할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;워커 스레드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 워커 스레드가 &lt;b&gt;자체 메모리&lt;/b&gt;와 &lt;b&gt;이벤트 루프&lt;/b&gt;를 가지고 백그라운드에서 실행되는 미니 &lt;b&gt;Node.js 환경&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 메인 스레드가 사용자에게 작업을 처리해 제공해주는 동안 워커 스레드는 그 뒤에서 다른 작업을 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;worker.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const { parentPort } = require(&quot;worker_threads&quot;);

function fibonacci(n) {
  if (n &amp;lt;= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const result = fibonacci(40);

parentPort.postMessage(result);&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;2️⃣ &lt;b&gt;main.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const { Worker } = require(&quot;worker_threads&quot;);

function runWokerThread() {
  return new Promise((resolve, reject) =&amp;gt; {
    const worker = new Worker(&quot;./worker.js&quot;);
    worker.on(&quot;message&quot;, resolve);
    worker.on(&quot;error&quot;, reject);
  });
}

async function main() {
  console.log(&quot;Starting...&quot;);
  const result = await runWokerThread();
  console.log(`Worker result: ${result}`);
}

main();
console.log(&quot;Hello World!&quot;);&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;code&gt;runWorkerThread()&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 워커 스레드를 생성하고 .worker.js로 가리킵니다.&lt;/li&gt;
&lt;li&gt;해당 워커 스레드는 메인 스레드와 완전히 독립된 스레드에서 실행됩니다.&lt;/li&gt;
&lt;li&gt;이것을 Promise로 감싸 async-await와 함께 사용하고 두 가지 이벤트를 수신합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;worker가 작업을 마치고 &lt;b&gt;메시지&lt;/b&gt;를 통해 결과를 보내면 Promise가 resolve됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 도중 문제가 생기면 error로 잡아냅니다.&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;3️⃣ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Starting...
Hello World!
Worker result: 102334155&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;⭐ main 함수에서 &lt;code&gt;await runWorkerThread&lt;/code&gt; 를 실행해 메인 스레드를 블로킹하지 않고 워커를 시작하고 결과를 기다립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 복잡하고 무거운 CPU 집약적 작업을 수행하더라도 메인 스레드는 정지되지 않고 다음 작업이 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 사이 worker.js 내부에서 무거운 계산을 처리하고 부모 포트를 이용해 최종 결과를 보내줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 사용량이 많은 Javascript 로직을 워커 스레드로 오프로드하여 병렬로 실행하고 메인 스레드를 가볍고 빠르게 유지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Vej327jN8WI&quot;&gt;https://www.youtube.com/watch?v=Vej327jN8WI&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/js-node-worker-threads/&quot;&gt;https://www.daleseo.com/js-node-worker-threads/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/169</guid>
      <comments>https://beomsic.tistory.com/entry/%EC%9B%8C%EC%BB%A4-%EC%8A%A4%EB%A0%88%EB%93%9C%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%A9%80%ED%8B%B0-%EC%93%B0%EB%A0%88%EB%94%A9#entry169comment</comments>
      <pubDate>Sat, 30 Aug 2025 16:12:55 +0900</pubDate>
    </item>
    <item>
      <title>fetch API</title>
      <link>https://beomsic.tistory.com/entry/fetch-API</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  비동기 데이터 통신&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 요청을 통해서 서버에서 데이터를 가져오는 것은 동적이고 상호작용적인 웹 애플리케이션에서 필수가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 비동기 데이터 통신을 하는 방식으로는 &lt;b&gt;XMLHTTPRequest&lt;/b&gt; 객체를 활용하는 것으로 수년 동안 사용한 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;Fetch API&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬의 네트워크 탭을 확인하면&lt;code&gt;xhr&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt; type을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgm0c2/btsQacJc9nq/NiTfz1B0InDlFudXsFn2TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgm0c2/btsQacJc9nq/NiTfz1B0InDlFudXsFn2TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgm0c2/btsQacJc9nq/NiTfz1B0InDlFudXsFn2TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgm0c2%2FbtsQacJc9nq%2FNiTfz1B0InDlFudXsFn2TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;509&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  XMLHttpRequest&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;XMLHttpRequest(XHR)&lt;/b&gt;은 AJAX 요청을 생성하는 JavaScript API입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XHR의 메서드로 브라우저와 서버간의 네트워크 요청을 전송할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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;// XMLHttpRequest 객체 생성
var xhr = new XMLHttpRequest();

xhr.open(&quot;GET&quot;, &quot;&amp;lt;https://test.com&amp;gt;&quot;, true);

xhr.onload = function () {
    if (xhr.status === 200) {
        var data = JSON.parse(xhr.responseText);
        console.log(data);
    } else {
        console.error(&quot;Request failed: &quot; + xhr.status);
    }
};

xhr.send();&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;zwj;♂️ &lt;b&gt;사용 방법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;XMLHttpRequest 객체 생성&lt;/li&gt;
&lt;li&gt;open() 메서드를 통해 요청에 필요한 정보를 설정&lt;/li&gt;
&lt;li&gt;send() 메서드로 서버에 요청&lt;/li&gt;
&lt;li&gt;응답에 대한 콜백 함수 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  AJAX&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 자바스크립트와 XML&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Asynchronous Javascript and XML&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지를 전체 새로고침 없이 서버와 데이터를 비동기적으로 주고받아 부분적으로만 업데이트하는 웹 개발 기법입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠르게 동작하는 동적인 웹 페이지를 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 형태의 데이터를 주고 받을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;XML&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;텍스트 파일 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 페이지 전체를 다시 로딩하지 않고도 웹 페이지의 일부분만을 갱신해 속도가 향상되고 코딩의 양이 줄어듭니다.&lt;/li&gt;
&lt;li&gt;서버의 처리가 완료될 때까지 기다리지 않고 처리가 가능하다.&lt;/li&gt;
&lt;li&gt;다양한 UI를 가능하게 합니다.&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;&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;AJAX를 쓸 수 없는 브라우저에 대한 문제 이슈 존재&lt;/li&gt;
&lt;li&gt;HTTP 클라이언트의 기능이 한정&lt;/li&gt;
&lt;li&gt;XMLHttpRequest를 통해 통신하는 경우 사용자에게 아무런 진행 정보가 주어지지 않습니다.(요청이 완료되지 않았는데 사용자가 페이지를 떠나거나 오작동 가능.)&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;  fetch&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fetch 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;XMLHttpRequest보다 강력하고 유연하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch는 거의 모든 웹 브라우저가 지원하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch를 이용하면 비동기 HTTP 요청을 만드는데 충분합니다.&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;  fetch 와 XMLHttpRequest 차이 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &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;XHR은 &lt;b&gt;콜백&lt;/b&gt; 을 받아 responseText, responseXMl 등을 통해 응답을 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch는 &lt;b&gt;promise&lt;/b&gt; 를 기반으로 동작하여 response.json(), response.text()를 사용합니다.&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;code&gt;.then()&lt;/code&gt;과 &lt;code&gt;.catch()&lt;/code&gt;를 이용한 체이닝이 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;async/await&lt;/code&gt; 문법을 사용해 코드를 더 간결하게 작성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;cors&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;XHR은 CORS를 위한 추가 설정이 필요하지만 fetch는 CORS를 지원하여 간단하게 설정이 가능합니다&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;fetch('url', {
  mode: 'cors',
  credentials: 'same-origin',
  headers: {
    'Content-Type': 'application/json',    
  },
});&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;3️⃣ &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;XHR 에서는 &lt;code&gt;progress 이벤트&lt;/code&gt; 를 통해 업로드 진행률 데이터를 받을 수 있습니다.&lt;/p&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;XHR&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;code&gt;upload.onprogress&lt;/code&gt; 이벤트를 사용해 업로드 진행률 추적 가능&lt;/li&gt;
&lt;li&gt;다운로드도 &lt;code&gt;onprogress&lt;/code&gt; 이벤트를 통해 스트리밍 중간 상태 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fetch&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드 진행률을 직접 추적하는 기능이 없음 ❌&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fetch는 스트림(Stream) 기반으로 설계&lt;/b&gt;되었기 때문에 전체 진행률을 한 번에 알기 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream&quot;&gt;ReadableStream API&lt;/a&gt;를 사용하여 fetch로도 다운로드 진행률을 추적할 수는 있습니다.&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;4️⃣ &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;XHR&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;code&gt;xhr.abort()&lt;/code&gt; 메서드로 요청 취소 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fetch&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 취소 기능이 없었음&lt;/li&gt;
&lt;li&gt;현재는 &lt;b&gt;AbortController&lt;/b&gt; API로 요청 취소 가능 ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const controller = new AbortController();
const signal = controller.signal;

fetch(&quot;&amp;lt;https://example.com/test&amp;gt;&quot;, { signal })
  .then(res =&amp;gt; res.json())
  .then(data =&amp;gt; console.log(data))
  .catch(err =&amp;gt; console.error(&quot;❌&quot;, err));

// 1초 후 요청 취소
setTimeout(() =&amp;gt; controller.abort(), 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  사용 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  fetch&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 함수를 호출하는 즉시 브라우저는 HTTP 요청을 생성합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;fetch(url)

fetch(url, options)
  .then(res =&amp;gt; res.text())
  .then(text =&amp;gt; ...));&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;URL만 전달하여 함수를 실행할 수 있습니다 (GET 메소드로 요청)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch는 기본으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;GET METHOD&lt;/b&gt;&lt;/span&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;span style=&quot;color: #006dd7;&quot;&gt;options&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;헤더, 본문 등 구체적인 요청 정보를 지정해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;options&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;method&lt;/td&gt;
&lt;td&gt;요청 메소드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;headers&lt;/td&gt;
&lt;td&gt;요청 헤더&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;body&lt;/td&gt;
&lt;td&gt;요청 본문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mode&lt;/td&gt;
&lt;td&gt;cors 관련&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;♂️ &lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청한 url로 설정한 HTTP Method로 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;fetch를 통해 ajax를 호출 시 해당 주소에 요청을 보낸 후 응답 객체를 받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;첫 번째 then&lt;/b&gt; 에서 그 응답을 받아 &lt;b&gt;res.text() 메서드로 파싱한 text 값을 리턴&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다음 then&lt;/b&gt; 에서 &lt;b&gt;리턴받은 text를 받고 원하는 처리&lt;/b&gt;를 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;↗️ Json 업로드&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;fetch('/upload', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    }
    body: JSON.stringify({name: 'beomsic'})
})&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;body 필드에 JSON 문자열을 지정해서 요청 본문을 설정합니다.&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;↩️ Response&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fetch&lt;/b&gt; 함수의 HTTP 요청은 서버로 전달되어 서버는 HTTP 응답을 다시 되돌려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 이 응답 데이터는 &lt;b&gt;fetch 함수를 실행하면 바로 얻는 것이 아닙니다.&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;fetch 함수는 응답을 바로 반환하는 것이 아니라 Promise 객체를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;fetch&lt;/b&gt; 함수는 응답 대신 프라미스 객체를 반환하고 프라미스가 이행되면 Response 객체를 얻습니다. 이 Response 객체에는 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;  &lt;b&gt;주요 필드&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;필드&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;HTTP 상태 코드 (ex: 200)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ok&lt;/td&gt;
&lt;td&gt;HTTP 상태코드가 200~299 사이인지 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;body&lt;/td&gt;
&lt;td&gt;응답 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;headers&lt;/td&gt;
&lt;td&gt;응답 헤더&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;응답 본문&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;response.text()&lt;/td&gt;
&lt;td&gt;응답을 읽고 텍스트를 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.json()&lt;/td&gt;
&lt;td&gt;응답을 JSON 형태로 파싱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.formData()&lt;/td&gt;
&lt;td&gt;응답을 FormData 객체 형태로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.blob()&lt;/td&gt;
&lt;td&gt;응답을 Blob(타입이 있는 이진 데이터, ex: 이미지)형태로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.arrayBuffer()&lt;/td&gt;
&lt;td&gt;응답을 ArrayBuffer(바이너리 데이터를 로우 레벨 형식으로 표현)형태로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️&lt;b&gt; 주의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 자료 형태 반환하는 메서드는 한번만 사용가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;response.text() 를 사용해 응답을 얻고 나면 본문의 콘텐츠는 모두 처리가 되어 response.json()을 입력해도 동작하지 않게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  fetch 함수는 왜 프라미스를 두 번 사용할까?&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async () =&amp;gt; {
    try{
        const response = await fetch('/test')
        const data = response.json();
    }catch(e) {
        console.error(`⚠️ ERROR: ${e}`);
    }
}&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;1️⃣ fetch 함수가 반환하는 프로미스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ response 객체의 메소드가 반환하는 프라미스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 헤더를 모두 받으면 response 객체를 제공해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch 가 반환한 첫 번째 프로미스가 이행될 때&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;브라우저가 body 데이터를 모두 받으면 데이터를 만듭니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;.json()&lt;/code&gt;, &lt;code&gt;.text()&lt;/code&gt;, &lt;code&gt;.blob()&lt;/code&gt; 같은 본문 조회 메소드가 반환한 두 번째 프로미스가 이행될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;시간을 지연하면서 본문을 청크 단위로 응답하는 서버&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const http = require('http');

const server = http.createServer(async (req, res) =&amp;gt; {
  if (req.url === '/test') {
    // 응답 헤더 전송
    res.writeHead(200, { 'Content-Type': 'text/plain' });

    // 청크 단위로 1초 간격 응답
    for (const i of Array.from({ length: 5 }).keys()) {
      res.write(`chunk ${i}\\n`);
      await new Promise((resolve) =&amp;gt; setTimeout(resolve, 1000));
    }

    // 응답 종료
    res.end();
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000, () =&amp;gt; {
  console.log('  Server running at &amp;lt;http://localhost:3000&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;b&gt;curl 요청을 통해서 확인해보기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bouvp1/btsQbACy10J/I56qW4oKosSKQ5qCSO1xMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bouvp1/btsQbACy10J/I56qW4oKosSKQ5qCSO1xMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bouvp1/btsQbACy10J/I56qW4oKosSKQ5qCSO1xMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbouvp1%2FbtsQbACy10J%2FI56qW4oKosSKQ5qCSO1xMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;461&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매초 마다 &lt;code&gt;chunk x&lt;/code&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;브라우저에서 fetch 함수는 어떻게 동작??&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const response = await fetch(&quot;&amp;lt;http://localhost:3000/test&amp;gt;&quot;);

// 브라우저가 모든 헤더를 받았을 때
console.log(&quot;response:&quot;, response);

// 브라우저가 body 전체 수신 및 문자열로 변환할 때
const text = await response.text();
console.log(&quot;text:&quot;, text);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br7Nfw/btsP88mZ4CE/FNyyPQI5OKsLwcNKRfiMPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br7Nfw/btsP88mZ4CE/FNyyPQI5OKsLwcNKRfiMPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br7Nfw/btsP88mZ4CE/FNyyPQI5OKsLwcNKRfiMPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr7Nfw%2FbtsP88mZ4CE%2FFNyyPQI5OKsLwcNKRfiMPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;265&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 헤더를 실어서 먼저 응답하기 때문에 Response 객체를 먼저 만들고&lt;/li&gt;
&lt;li&gt;일정 시간이 지연된 뒤 모든 바디가 수신되면 text 로그가 출력됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  그래서 두 번의 프로미스가 필요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;첫 번째 프로미스 (&lt;code&gt;fetch&lt;/code&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;&quot;응답이 왔다!&quot;를 알려줌 (본문은 아직일 수도 있음)&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;2️⃣ &lt;b&gt;두 번째 프로미스 (&lt;code&gt;.json()&lt;/code&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;응답 body를 끝까지 다 받고 응답을 받기 위해 지정한 방식(JSON, 텍스트, Blob 등)으로 파싱 완료되었을 때 알려줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jeonghwan-kim.github.io/2023/12/27/fetch&quot;&gt;https://jeonghwan-kim.github.io/2023/12/27/fetch&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mj-dev.site/blog/velog-migration/api-xhr-fetch&quot;&gt;https://mj-dev.site/blog/velog-migration/api-xhr-fetch&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/168</guid>
      <comments>https://beomsic.tistory.com/entry/fetch-API#entry168comment</comments>
      <pubDate>Wed, 27 Aug 2025 22:45:22 +0900</pubDate>
    </item>
    <item>
      <title>비동기 프로그래밍 - Promise 와 async / await</title>
      <link>https://beomsic.tistory.com/entry/%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Promise-%EC%99%80-async-await</link>
      <description>&lt;h1&gt;⚠️ 콜백 지옥&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 싱글 스레드로 한 번에 하나의 작업만을 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해서 비동기 프로그래밍으로 처리를 해서 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기&lt;/b&gt;는 특정 코드 작업이 끝날때까지 기다리지 않고 다음 코드를 수행하는 방식으로 비동기 작업 결과에 따라 다른 작업을 수행해야할 경우 콜백 함수를 이용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;b&gt;함수 객체를 전달&lt;/b&gt;해 호출된 함수 내에서 매개변수 함수를 실행하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;function fun(text, callback) {
  callback(text);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;콜백 지옥&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function fetchData(callback) {
  setTimeout(() =&amp;gt; {
    const data = 'user data';
    callback(data);
  }, 1000);
}

function processData(data, callback) {
  setTimeout(() =&amp;gt; {
    const processedData = `${data} processed`;
    callback(processedData);
  }, 1000);
}

function saveData(data, callback) {
  setTimeout(() =&amp;gt; {
    callback(data);
  }, 1000);
}

fetchData((data) =&amp;gt; {
  processData(data, (processData) =&amp;gt; {
    saveData(processData, (res) =&amp;gt; {
      console.log(res);
      console.log('끝!!');
    });
  });
});

console.log('start!');

----결과-----
start!
user data processed // 3초 후 
끝!!&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;콜백안에 콜백안에 콜백&amp;hellip;&lt;/li&gt;
&lt;li&gt;어떤 함수가 일을 마치고 다음에 실행할 일을 콜백함수의 인자로 넣고 또 그 함수가 일을 마치면 할 일을 인자로 넣고&amp;hellip; 반복&lt;/li&gt;
&lt;li&gt;코드의 계속해서 깊어지고 중첩되면서 복잡도가 증가하고 유지보수가 어려워집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;위 코드의 함수 호출 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;fetchData&lt;/code&gt; 실행&lt;/li&gt;
&lt;li&gt;callback 함수인 &lt;code&gt;processData&lt;/code&gt; 에 인자를 전달하고 호출&lt;/li&gt;
&lt;li&gt;마찬가지로 &lt;code&gt;processData&lt;/code&gt;에서 callback 함수인 &lt;code&gt;saveData&lt;/code&gt;에 인자를 전달하고 호출&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;⭐ Promise&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;b&gt;약속&lt;/b&gt; 하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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;function fetchData() {
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; resolve('user data', 1000));
  });
}

function processData(data) {
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; resolve(`${data} processed`, 1000));
  });
}

function saveData(data) {
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      if (Math.random() &amp;gt; 0.1) {
        resolve(`${data} saved`, 1000);
      }
      reject('failed');
    }, 1000);
  });
}

fetchData()
  .then((data) =&amp;gt; processData(data))
  .then((processed) =&amp;gt; saveData(processed))
  .then((result) =&amp;gt; console.log(result))
  .catch((error) =&amp;gt; console.error('Error: ', error))
  .finally(() =&amp;gt; console.log('end!!'));

console.log('start!');

-----결과-----
start!
user data processed saved
end!!&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;메소드 체이닝&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 콜백 지옥보다 훨씬 더 깔끔하다&lt;/p&gt;
&lt;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;code&gt;then&lt;/code&gt; 과 &lt;code&gt;catch&lt;/code&gt; 메서드 체이닝을 통해 성공과 실패에 대한 처리를 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/di80uV/btsQa9yny0Z/FNY2mIKVBncow7gW8KFY80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/di80uV/btsQa9yny0Z/FNY2mIKVBncow7gW8KFY80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/di80uV/btsQa9yny0Z/FNY2mIKVBncow7gW8KFY80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdi80uV%2FbtsQa9yny0Z%2FFNY2mIKVBncow7gW8KFY80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;305&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;상태&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pending (대기)&lt;/td&gt;
&lt;td&gt;처리 진행중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fulfilled (이행)&lt;/td&gt;
&lt;td&gt;처리가 성공으로 완료된 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rejected (거부)&lt;/td&gt;
&lt;td&gt;처리가 실패한 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;resolve()&lt;/code&gt; &amp;rarr; 성공적으로 완료된 상태(&lt;code&gt;fulfilled&lt;/code&gt;)로 바꿈&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reject()&lt;/code&gt; &amp;rarr; 실패 상태(&lt;code&gt;rejected&lt;/code&gt;)로 바꿈&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;Pending&lt;/b&gt; 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 비동기 처리 로직이 완료되지 않은 상태입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    resolve('완료');
  }, 3000);
});

console.log(promise);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/33aug/btsP9I2UVdg/1vwmFX1BsRVYU7pCqMbOf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/33aug/btsP9I2UVdg/1vwmFX1BsRVYU7pCqMbOf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/33aug/btsP9I2UVdg/1vwmFX1BsRVYU7pCqMbOf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F33aug%2FbtsP9I2UVdg%2F1vwmFX1BsRVYU7pCqMbOf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;508&quot; height=&quot;183&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ &lt;b&gt;Fulfilled&lt;/b&gt; 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 3초 동안 기다린 후 다시 출력을 해보면 이행 상태로 변합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7JYm/btsP8Bwqc7f/3l75GadbMNCwhZ2lopGrBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7JYm/btsP8Bwqc7f/3l75GadbMNCwhZ2lopGrBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7JYm/btsP8Bwqc7f/3l75GadbMNCwhZ2lopGrBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7JYm%2FbtsP8Bwqc7f%2F3l75GadbMNCwhZ2lopGrBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;224&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이행 상태로 변한 프로미스 객체는 &lt;code&gt;then&lt;/code&gt; 메서드를 호출해 처리 결과 값을 받을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {}, 3000);
});

console.log(promise);&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;이 코드는 3초가 지나도 계속해서 &lt;code&gt;pending&lt;/code&gt; 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ &lt;b&gt;Rejected&lt;/b&gt; 상태&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    reject('에러');
  }, 3000);
});

console.log(promise);&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;위 코드를 실행한 후 3초뒤에 상태를 확인해보면 &lt;code&gt;rejected&lt;/code&gt; 상태가 된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLuu7Y/btsP8EGsH1k/RMBqENpDJ7os2PeXFxQttK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLuu7Y/btsP8EGsH1k/RMBqENpDJ7os2PeXFxQttK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLuu7Y/btsP8EGsH1k/RMBqENpDJ7os2PeXFxQttK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLuu7Y%2FbtsP8EGsH1k%2FRMBqENpDJ7os2PeXFxQttK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;208&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;322&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;  resolve 와 reject&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise는 두 개의 파라미터를 받을 수 있습니다.&lt;/p&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;resolve&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공적으로 일을 마치고 나면 수행할 일&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    resolve('promise!');
  }, 2000);
});&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;resolve 안에 argument를 넣어주면 then 함수가 실행될 때 then 함수의 첫 번째 파라미터에 해당 argument 값이 들어가게 됩니다.&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;reject&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    reject('error!');
  }, 2000);
});&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;li&gt;catch함수가 실행될 때 reject에 들어간 argument를 통해 실행합니다.&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;프로미스가 생성 된후 성공 / 실패 결과를 &lt;code&gt;then&lt;/code&gt;, &lt;code&gt;catch&lt;/code&gt;, &lt;code&gt;finally&lt;/code&gt; 핸들러를 통해서 다음 작업을 진행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  then&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스가 &lt;b&gt;fulfilled&lt;/b&gt; 상태가 되었을 때 실행할 콜백 함수를 등록하고 &lt;b&gt;새로운 프로미스를 반환&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    resolve('promise!');
  }, 2000);
});

promise.then((res) =&amp;gt; {
  console.log(res); // promise!
});

--------결과--------
promise!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;then&lt;/b&gt;에서 사용하는 파라미터 값 - &lt;b&gt;resolve&lt;/b&gt;의 인자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  catch&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스가 rejected 상태가 되었을 때 실행할 콜백 함수를 등록하고 &lt;b&gt;새로운 프로미스를 반환&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const promise = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    reject('error!');
  }, 2000);
});

promise.catch((res) =&amp;gt; {
  console.log(res); // error!
});

--------결과--------
error!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;catch&lt;/b&gt; 에서 사용하는 파라미터 값 - &lt;b&gt;reject&lt;/b&gt;의 인자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ finally&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스가 &lt;b&gt;이행되었거나 거부되었거나 상관없이&lt;/b&gt; 실행할 콜백 함수를 등록하고 &lt;b&gt;새로운 프로미스를 반환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;finally는 then, catch 와 달리 파라미터를 받지 않습니다.&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;const getPromise = (sec) =&amp;gt;
  new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      reject('error!');
    }, sec * 1000);
  });

getPromise(3)
  .then((res) =&amp;gt; {
    console.log('&amp;gt; resolve');
    return getPromise(3);
  })
  .catch((res) =&amp;gt; {
    console.log('&amp;gt; reject');
  })
  .finally(() =&amp;gt; {
    console.log('---finally!---');
  });

-------결과--------
&amp;gt; reject
---finally!---&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;프로미스 체이닝&lt;/b&gt;&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;promise()
  .then((res) =&amp;gt; {
    console.log('&amp;gt; resolve');
    return getPromise(3);
  })
  .then((res) =&amp;gt; {
    console.log('&amp;gt; resolve2');
  })
  .catch((res) =&amp;gt; {
    console.log('&amp;gt; reject');
  })
  .finally(() =&amp;gt; {
    console.log('---finally!---');
  });&lt;/code&gt;&lt;/pre&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;code&gt;then()&lt;/code&gt; 에서 값을 리턴시 다음 &lt;code&gt;then()&lt;/code&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  프로미스 정적 메서드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스는 여러 정적 메서드를 제공해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 메서드를 이용하면 객체를 초기화 / 생성하지 않고도 바로 사용할 수 있습니다.&lt;/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️⃣ Promise.resolve(), Promise.reject()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스의 resolve() 메서드를 사용하기 위해서는 프로미스를 생성자를 통해 생성하고 그 안의 콜백 함수의 매개변수를 통해 호출해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 정적 메서드를 통해 한번에 호출할 수 있도록 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function resolveStaticFun() {
  const num = 1;
  return Promise.resolve(num);
}

resolveStaticFun()
  .then((num) =&amp;gt; {
    console.log(num);
  }
  .catch((error) =&amp;gt; {
    console.error(error);
  }&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;code&gt;Promise.reject()&lt;/code&gt; 역시 같은 방식으로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;const reject = Promise.reject(new Error('error'));

reject.catch(error =&amp;gt; console.error(error)); &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ Promise.all()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 프로미스를 동시에 처리할 때 유용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 비동기 처리가 이행될 때까지 기다리고 모든 프로미스가 완료되면 then 핸들러가 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const user_api = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('user'), 1000);
});
const post_api = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('post'), 3000);
});
const comment_api = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('comment'), 1000);
});

const promises = [user_api, post_api, comment_api];

const time = new Date();
Promise.all(promises)
  .then((res) =&amp;gt; {
    console.log(res);
    console.log((new Date() - time) / 1000);
  })
  .catch((error) =&amp;gt; {
    console.log(error);
  });

-----결과------
[ 'user', 'post', 'comment' ]
3.017&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;모든 프로미스가 순차적으로 실행된다면 5초가 걸리겠지만 가장 긴 프로미스 처리시간만큼만 시간이 소요됩니다.&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;3️⃣ Promise.any()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Promise.all()&lt;/code&gt;과 달리 주어진 프로미스중 하나라도 완료되면 바로 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const user_api = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('user'), 3000);
});
const post_api = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('post'), 2000);
});
const comment_api = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; resolve('comment'), 1000);
});

const promises = [user_api, post_api, comment_api];

const time = new Date();
Promise.any(promises)
  .then((res) =&amp;gt; {
    console.log(res);
    console.log((new Date() - time) / 1000);
  })
  .catch((error) =&amp;gt; {
    console.log(error);
  });

---결과---
comment
1.018&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  async - await&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;Promise&lt;/b&gt;를 사용해서 콜백지옥보다는 깔끔한데 아직 불편한 점이 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 하나가 매우 길어짐(메소드 체이닝)&lt;/li&gt;
&lt;li&gt;함수를 계속 이어서 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 Promise를 보다 직관적으로 사용할 수 있도록 도입된 것이 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;async&lt;/b&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;await&lt;/b&gt;&lt;/span&gt; 입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function fetchData() {
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; resolve('user data', 1000));
  });
}

function processData(data) {
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; resolve(`${data} processed`, 1000));
  });
}

function saveData(data) {
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      if (Math.random() &amp;gt; 0.1) {
        resolve(`${data} saved`, 1000);
      }
      reject('failed');
    }, 1000);
  });
}

async function process() {
  try {
    const data = await fetchData();
    const processed = await processData(data);
    const result = await saveData(processed);
    console.log(result);
  } catch (error) {
    console.error('Error: ', error);
  } finally {
    console.log('끝!');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이전 promise 메소드 체이닝보다 읽기에 더 직관적입니다.&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;code&gt;async&lt;/code&gt; 키워드가 붙은 함수 또는 &lt;code&gt;Node.js의 ESM의 최상위 스코프&lt;/code&gt;에서만 사용할 수 있습니다.&lt;/p&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;await&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스 작업이 완료될 때까지 코드의 진행을 멈춘 후 그 결과를 반환합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;await를 사용하면 다음 코드가 await를 한 함수가 끝나기 전까지는 실행되지 않습니다.&lt;/li&gt;
&lt;li&gt;하지만 스레드를 블로킹하는 것은 아님&lt;/li&gt;
&lt;li&gt;따라서 다른 함수가 실행될 것이 있다면 그 함수는 실행이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;비동기 프로그래밍을 하지 않는 것처럼 비동기 프로그래밍을 할 수 있다!!&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 과정이 시간이 걸리는 동기 과정인 것처럼 코드를 작성할 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6AhQFPVl96s&amp;amp;ab_channel=%EC%96%84%ED%8C%8D%ED%95%9C%EC%BD%94%EB%94%A9%EC%82%AC%EC%A0%84&quot;&gt;https://www.youtube.com/watch?v=6AhQFPVl96s&amp;amp;ab_channel=얄팍한코딩사전&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.javascript.info/promise-basics&quot;&gt;https://ko.javascript.info/promise-basics&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/167</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Promise-%EC%99%80-async-await#entry167comment</comments>
      <pubDate>Wed, 27 Aug 2025 22:36:25 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 이벤트</title>
      <link>https://beomsic.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  브라우저 이벤트란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에는 많은 이벤트가 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 버튼을 클릭&lt;/li&gt;
&lt;li&gt;화면의 크기 조절&lt;/li&gt;
&lt;li&gt;화면 스크롤&lt;/li&gt;
&lt;li&gt;등등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 사용자의 인터렉션(이벤트)가 발생하면 그에 해당하는 일을 등록해 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트는 함수에 연결되어 이벤트가 발생하지 전에는 함수가 실행되지 않다가 이벤트가 발생되면 실행됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이벤트 핸들러&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  이벤트 객체&lt;/h3&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;이벤트 객체는 이벤트가 발생한 요소, 이벤트 타입, 마우스 좌표, 키보드 입력 등 이벤트와 관련된 정보를 제공합니다.&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;table style=&quot;border-collapse: collapse; width: 100%; height: 143px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;속성&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.type&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이벤트의 종류&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lsquo;click&amp;rsquo;, &amp;lsquo;keyup&amp;rsquo;, &amp;lsquo;mousedown&amp;rsquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.target&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이벤트가 실제로 발생한 요소&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;버튼 클릭 이벤트 &amp;rarr; 클릭된 버튼 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.currentTarget&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이벤트 리스너가 등록된 요소(핸들러가 바인딩된 요소)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.preventDefault()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이벤트의 기본 동작을 막습니다.&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;링크 클릭 시 페이지 이동 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.stopPropagation()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이벤트가 캡처링/버블링 단계에서 더 이상 전파되지 않도록 방지&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이벤트 흐름 중단, 부모 핸들러 실행 막기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.clientX,&amp;nbsp;event.clientY&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;마우스 이벤트의 경우 뷰 포트 기준 마우스 좌표&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;event.key&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;키보드 이벤트의 경우 눌린 키 문자열&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lsquo;Enter&amp;rsquo;, &amp;lsquo;a&amp;rsquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;event.target&lt;/code&gt; 와 &lt;code&gt;event.currentTarget&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;parent&quot;&amp;gt;
      부모 요소
      &amp;lt;button id=&quot;child&quot;&amp;gt;자식 버튼&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
      const parent = document.getElementById('parent');

      // 부모 요소에 클릭 이벤트 리스너 등록
      parent.addEventListener('click', function (event) {
        console.log('event.target:', event.target); // 실제 클릭된 요소
        console.log('event.currentTarget:', event.currentTarget); // 이벤트 리스너가 등록된 요소
      });
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&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;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;# 부모 요소 클릭시
event.target: &amp;lt;div id=&quot;parent&quot;&amp;gt;&amp;hellip;&amp;lt;/div&amp;gt;
event.currentTarget: &amp;lt;div id=&quot;parent&quot;&amp;gt;...&amp;lt;/div&amp;gt;

# 자식 요소 클릭시
event.target: &amp;lt;div id=&quot;child&quot;&amp;gt;자식 버튼&amp;lt;/div&amp;gt;
event.currentTarget: &amp;lt;div id=&quot;parent&quot;&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;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이벤트가 발생할 때마다 새로 생성되어 이벤트 핸들러의 인자로 전달됩니다.&lt;/li&gt;
&lt;li&gt;이벤트 버블링이나 캡처링 단계에서도 동일한 이벤트 객체가 전달되어 이벤트 흐름을 제어하거나 원본 이벤트 요소를 확인 가능.&lt;/li&gt;
&lt;li&gt;이벤트 위임(Event Delegation)에 필수적이며 이벤트 핸들러를 부모 요소에 등록하고 자식 요소에서 발생한 이벤트를 처리할 수 있도록 도와줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;↗️ 이벤트 전파 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &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;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;)에서 시작해 이벤트 발생 요소까지 DOM 트리를 아래로 순차적으로 내려가며 전달됩니다.&lt;/li&gt;
&lt;li&gt;이 단계에서 감지하려면 &lt;code&gt;addEventListener&lt;/code&gt;의 세 번째 인자 또는 옵션 객체에 &lt;code&gt;capture: true&lt;/code&gt;를 설정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;타깃 단계&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트가 실제로 발생한 타깃 요소에서 핸들러가 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &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;타깃 요소에서 이벤트가 시작되어 다시 DOM 트리를 따라 부모 요소로 '거품처럼' 올라갑니다.&lt;/li&gt;
&lt;li&gt;부모 요소 더 바깥 요소에 등록된 이벤트 핸들러가 순차적으로 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;버블링&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;div onclick=&quot;alert('!!')&quot;&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;Test&amp;lt;/span&amp;gt;&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;Test 를 클릭했을 때 핸들러가 동작&lt;/p&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 data-token-index=&quot;0&quot;&gt;&lt;b&gt;&amp;lt;span&amp;gt;&lt;/b&gt; 을 클릭했는데 &lt;b&gt;&amp;lt;div&amp;gt;&lt;/b&gt;에 할당한 핸들러가 동작하지? &amp;rarr; &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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 요소에서 이벤트가 발생 시 해당 요소에 할당된 핸들러가 동작하고 이어서 부모 요소 핸들러가 동작&lt;/li&gt;
&lt;li&gt;마지막 조상 요소를 만날 때까지 반복되면서 요소 각각에 할당된 핸들러가 동작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;예시 -&lt;span data-token-index=&quot;0&quot;&gt; &amp;lt;p&amp;gt; 클릭시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;form onclick=&quot;alert('form')&quot;&amp;gt;FORM
  &amp;lt;div onclick=&quot;alert('div')&quot;&amp;gt;DIV
    &amp;lt;p onclick=&quot;alert('p')&quot;&amp;gt;P&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;lt;p&amp;gt;에 할당된 onclick 핸들러 동작&lt;/li&gt;
&lt;li&gt;&amp;lt;div&amp;gt; 에 할당된 onclick 핸들러 동작&lt;/li&gt;
&lt;li&gt;&amp;lt;form&amp;gt;에 할당된 onclick 핸들러 동작&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트가 제일 깊은 요소부터 부모 요소를 거슬러 올라가면서 발생하는 모습 &amp;rarr; 물속 거품과 닯았다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;버블링 중단&lt;/b&gt;&lt;/p&gt;
&lt;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;event.stopPropagation()&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핸들러에게 이벤트를 처리하고 난 후 버블링을 중단하도록 하는 명령&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;body onclick=&quot;alert(`버블링 안됌`)&quot;&amp;gt;
  &amp;lt;button onclick=&quot;event.stopPropagation()&quot;&amp;gt;중단&amp;lt;/button&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;캡처링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트가 발생하면 최상위 조상에서 시작해 아래로 전파되어 자식요소의 이벤트도 함께 발생&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;element.addEventListener(..., { capture: true })&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;addEventListener의 3번째 매개변수로 &lt;code&gt;true&lt;/code&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; &lt;code&gt;id = child&lt;/code&gt; &lt;b&gt;선택&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;ancestor&quot;&amp;gt;
    &amp;lt;div id=&quot;parent&quot;&amp;gt;
           &amp;lt;div id=&quot;child&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;ancestor.addEventListener(&quot;click&quot;, (e) =&amp;gt; {
  console.log('ancestor click');
}, true)

parent.addEventListener(&quot;click&quot;, (e) =&amp;gt; {
  console.log('parent click');
}, true)

child.addEventListener(&quot;click&quot;, (e) =&amp;gt; {
  console.log('child click');
}, true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;&amp;gt; ancestor click
&amp;gt; parent click
&amp;gt; child click&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  이벤트 종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;UI&lt;/b&gt; 이벤트&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이벤트&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;load&lt;/td&gt;
&lt;td&gt;웹페에지나 스크립트 로드 완료되었을 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;error&lt;/td&gt;
&lt;td&gt;브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지않는 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resize&lt;/td&gt;
&lt;td&gt;브라우저 창 크기 조절하는 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scroll&lt;/td&gt;
&lt;td&gt;사용자가 페이지를 위 아래로 스크롤하는 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;select&lt;/td&gt;
&lt;td&gt;텍스트를 선택하는 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;Keyboard&lt;/b&gt; 이벤트&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이벤트&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;keydown&lt;/td&gt;
&lt;td&gt;키를 누르고 있을 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;keyup&lt;/td&gt;
&lt;td&gt;누르고 있던 키를 뗄 경우 (키에서 손을 뗀 경우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;keypress&lt;/td&gt;
&lt;td&gt;키를 누르고 뗏을 경우 (키가 입력될 때)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;keyup&lt;/code&gt; 이랑 &lt;code&gt;keypress&lt;/code&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;code&gt;keyup&lt;/code&gt;은 키보드의 모든 키에 대해 발생하고 키가 올라가는(떼는) 순간 이벤트가 발생&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keypress&lt;/code&gt;는 문자가 입력되는 키에 대해서만 발생하며 비문자 키에는 반응하지 않습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 쉬프트 키나 방향키에는 이벤트가 발생하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;키보드 입력 처리 시 &lt;code&gt;keyup&lt;/code&gt;은 키를 뗐을 때 동작하므로 입력 완료 직후에 처리할 때 사용되고 &lt;code&gt;keypress&lt;/code&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;3️⃣ &lt;b&gt;Mouse&lt;/b&gt; 이벤트&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 67px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이벤트&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;click&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;마우스 버튼을 클릭했을 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;mousemove&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;마우스를 움직일 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;mouseover&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;마우스를 요소 위로 움직였을 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ &lt;b&gt;Form&lt;/b&gt; 이벤트&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이벤트&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;input&lt;/td&gt;
&lt;td&gt;input 또는 testarea 요소의 값이 변경된 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;change&lt;/td&gt;
&lt;td&gt;select box, checkbox, radion button 상태가 변경된 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;submit&lt;/td&gt;
&lt;td&gt;form 내용을 submit 한 경우 (버튼, 키)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reset&lt;/td&gt;
&lt;td&gt;reset 버튼 클릭한 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  이벤트 핸들러 등록 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트에 반응하기 위해서는 이벤트가 발생했을 때 실행되는 함수인 &lt;b&gt;핸들러&lt;/b&gt;를 등록해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러는 여러 가지 방법으로 할당할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;인라인 이벤트 핸들러&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 태그의 속성으로 직접 함수를 등록하는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;hsp&quot;&gt;&lt;code&gt;&amp;lt;button onclick=&quot;alert('버튼을 클릭!')&quot;&amp;gt;버튼&amp;lt;/button&amp;gt;&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;HTML과 JS가 혼합되어 있어 여러 이벤트 처리 시 코드가 지저분해질 수 있음.&lt;/li&gt;
&lt;li&gt;요소마다 직접 작성해야 하고 유지보수 어려움.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;script 안에서 함수를 만들고 태그안에 지정할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;button onclick=&quot;handler()&quot;&amp;gt;버튼2&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;
  function handler() {
    alert('handler!!');
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 프로퍼티 리스너 방식&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 대상에 해당하는 객체의 프로퍼티로 이벤트를 등록하는 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트에 &lt;b&gt;오직 하나의 이벤트 핸들러만을 바인딩&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;button class=&quot;btn&quot;&amp;gt;Click&amp;lt;/button&amp;gt;
&amp;lt;script&amp;gt;
    const btn = document.querySelector('.btn');
    btn.onclick = function() {
        alert('Button click event handler1');
    };

    btn.onclick = function() {
        alert('Button click event handler2');
    };
&amp;lt;/script&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;/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️⃣ addEventListener&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수 지정&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bitxxy/btsP555bWBm/34kDcPzKXJbVKTofC1zVC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bitxxy/btsP555bWBm/34kDcPzKXJbVKTofC1zVC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bitxxy/btsP555bWBm/34kDcPzKXJbVKTofC1zVC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbitxxy%2FbtsP555bWBm%2F34kDcPzKXJbVKTofC1zVC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1296&quot; height=&quot;194&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  왜 &lt;b&gt;AddEventListener&lt;/b&gt; 를 사용?&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여러 이벤트 핸들러 지정&lt;/td&gt;
&lt;td&gt;하나의 이벤트에 여러 개의 이벤트 핸들러를 등록할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이벤트 해제 및 제거&lt;/td&gt;
&lt;td&gt;&lt;b&gt;removeEventListener&lt;/b&gt;를 사용해 특정 시점에 이벤트를 제거하거나 필요하지 않을 때 이벤트 리스너를 해제하는데 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;캡처링 버블링 지원&lt;/td&gt;
&lt;td&gt;캡처링, 버블링을 지정해 이벤트 전파를 세밀하게 제어할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  이벤트 핸들러 내부의 this&lt;/h2&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️⃣ 인라인 이벤트 핸들러&lt;/h3&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;button onclick=&quot;inline()&quot;&amp;gt;Button&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;
    function inline () {
      console.log(this);
    }
&amp;lt;/script&amp;gt;

--------결과--------
Window {window: Window, self: Window, document: document, name: '', location: Location, &amp;hellip;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인라인 이벤트 핸들러&lt;/b&gt;는 HTML 속성 안에 직접 작성된 자바스크립트 코드로 브라우저가 이를 기본적으로 일반 함수처럼 호출합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 함수는 호출할 때 어떤 객체에도 묶여 있지 않은 상태이기 때문에 this&lt;b&gt;는 전역 객체인&lt;/b&gt; window&lt;b&gt;를 참조&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 프로퍼티 리스너 방식&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;button class=&quot;btn&quot;&amp;gt;Button&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;
  const btn = document.querySelector('.btn');

  btn.onclick = function (e) {
    console.log(this);
    console.log(e.currentTarget);
    console.log(this === e.currentTarget);
  };
&amp;lt;/script&amp;gt;

------결과------
&amp;lt;button class=&quot;btn&quot;&amp;gt;Button&amp;gt;&amp;lt;/button&amp;gt;
&amp;lt;button class=&quot;btn&quot;&amp;gt;Button&amp;gt;&amp;lt;/button&amp;gt;
true&lt;/code&gt;&lt;/pre&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;code&gt;element.onclick()&lt;/code&gt;과 유사하게 호출되어 &lt;code&gt;this&lt;/code&gt;가 해당 요소(&lt;code&gt;element&lt;/code&gt;)를 가리키게 됩니다.&lt;/li&gt;
&lt;li&gt;이벤트 객체의 &lt;code&gt;currentTarget&lt;/code&gt;과 &lt;code&gt;this&lt;/code&gt;가 일치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ addEventListener&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;button class=&quot;btn&quot;&amp;gt;Button&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;
    const btn = document.querySelector('.btn');

    btn.addEventListener('click', function (e) {
      console.log(this); 
      console.log(e.currentTarget);
      console.log(this === e.currentTarget);
    });
&amp;lt;/script&amp;gt;

------결과------
&amp;lt;button class=&quot;btn&quot;&amp;gt;Button&amp;gt;&amp;lt;/button&amp;gt;
&amp;lt;button class=&quot;btn&quot;&amp;gt;Button&amp;gt;&amp;lt;/button&amp;gt;
true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;addEventListener에 등록된 함수는 &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;code&gt;handler.call(element)&lt;/code&gt;와 같이 호출되어 this가 이벤트 대상 요소를 가리킵니다.&lt;/li&gt;
&lt;li&gt;이벤트 객체의 &lt;code&gt;currentTarget&lt;/code&gt;과 &lt;code&gt;this&lt;/code&gt;가 일치&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소마다 핸들러를 할당하지 않고 하나의 부모 요소에서 이벤트 핸들러를 단 하나만 할당&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡처링과 버블링을 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 요소에서 할당한 핸들러에서 &lt;code&gt;event.target&lt;/code&gt; 을 이용하면 실제 이벤트를 발생시킨 요소를 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;button id=&quot;addItemBtn&quot;&amp;gt;항목 추가&amp;lt;/button&amp;gt;
  &amp;lt;ul id=&quot;itemList&quot;&amp;gt;
    &amp;lt;li data-id=&quot;1&quot;&amp;gt;아이템 1&amp;lt;/li&amp;gt;
    &amp;lt;li data-id=&quot;2&quot;&amp;gt;아이템 2&amp;lt;/li&amp;gt;
    &amp;lt;li data-id=&quot;3&quot;&amp;gt;아이템 3&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;

  &amp;lt;script&amp;gt;
    const itemList = document.getElementById('itemList');
    let count = 3;

    // 부모 요소에 이벤트 리스너 등록 (이벤트 위임)
    itemList.addEventListener('click', function (event) {
      if (event.target.tagName.toLowerCase() === 'li') {
        alert(`클릭한 항목: ${event.target.textContent} (id: ${event.target.dataset.id})`);
      }
    });

    // 버튼 클릭 시 새 항목 추가 (동적 생성 요소도 위임 가능)
    document.getElementById('addItemBtn').addEventListener('click', () =&amp;gt; {
      count++;
      const li = document.createElement('li');
      li.textContent = `아이템 ${count}`;
      li.dataset.id = count;
      itemList.appendChild(li);
    });
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&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;code&gt;ul#itemList&lt;/code&gt; 에 클릭 이벤트 리스너를 한 번만 등록합니다.&lt;/li&gt;
&lt;li&gt;클릭된 실제 대상(&lt;code&gt;event.target&lt;/code&gt;)이 &lt;code&gt;li&lt;/code&gt; 요소인지 확인 후 처리&lt;/li&gt;
&lt;li&gt;새로 추가된 항목들도 각각 개별 리스너 없이 부모 리스너에 의해 처리&lt;/li&gt;
&lt;li&gt;이렇게 이벤트 위임으로 다수의 자식 요소 이벤트를 효과적으로 관리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  DOM APIs&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript를 통해 DOM API로 DOM을 조작함으로써 웹 페이지의 동적인 요소를 만들 수 있습니다.&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;  DOM 요소 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM을 조작하기 위한 첫 단계는 원하는 HTML 요소를 선택하는 것입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;document.getElementById()&lt;/td&gt;
&lt;td&gt;HTML 요소의 고유한 id 속성 값을 사용해 특정 요소를 찾습니다.&lt;/td&gt;
&lt;td&gt;id는 페이지내에서 유일해 하나의 요소만 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;document.querySelector()&lt;/td&gt;
&lt;td&gt;CSS 선택자 문법을 사용해 DOM에서 가장 먼저 일치하는 요소 하나를 찾습니다.&lt;/td&gt;
&lt;td&gt;클래스, ID, 태그명 등 다양한 CSS 선택자를 사용할 수 있어 원하는 요소를 쉽게 찾을 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;document.querySelectorAll()&lt;/td&gt;
&lt;td&gt;CSS 선택자에 일치하는 모든 요소를 NodeList 객체로 반환합니다.&lt;/td&gt;
&lt;td&gt;NodeList는 배열과 유사하지만 일부 배열 메서드만 사용할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfDLkP/btsP5rU9NoQ/NSQkKhOdD108WGi1jeonz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfDLkP/btsP5rU9NoQ/NSQkKhOdD108WGi1jeonz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfDLkP/btsP5rU9NoQ/NSQkKhOdD108WGi1jeonz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfDLkP%2FbtsP5rU9NoQ%2FNSQkKhOdD108WGi1jeonz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;374&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  노드 속성 정보 얻기 - Properties&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 DOM 요소의 다양한 정보를 얻을 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;API&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;element.tagName&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;요소의 HTML 태그 이름을 대문자 문자열로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;element.textContent&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;해당 요소와 자식 요소의 모든 텍스트 콘텐츠를 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;element.nodeType&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;노드의 타입을 숫자로 반환 (1: element 노드, 3: text 노드)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  DOM 탐색&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.childNodes&lt;/td&gt;
&lt;td&gt;모든 자식 노드(Element 및 Text 노드 포함)를 NodeList로 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.firstChild&lt;/td&gt;
&lt;td&gt;첫 번째 자식 노드를 반환합니다. 공백, 줄바꿈도 텍스트 노드로 간주되어 주의해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.firstElementChild&lt;/td&gt;
&lt;td&gt;첫 번째 요소노드를 반환합니다. 공백 등을 무시하고 실제 태그만 찾을 때 유용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.parentElement&lt;/td&gt;
&lt;td&gt;부모 요소 노드를 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.nextSibling&lt;/td&gt;
&lt;td&gt;바로 다음 형제 노드를 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.nextElementSibling&lt;/td&gt;
&lt;td&gt;바로 다음 형제 요소(Element) 노드를 반환합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.closest(selector)&lt;/td&gt;
&lt;td&gt;현재 요소부터 시작해 부모 방향으로 탐색하며 주어진 CSS 선택자와 가장 가까이 일치하는 조상 요소를 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  DOM 조작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM 요소를 추가, 삭제, 교체하는 방법입니다.&lt;/p&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;기본적인 조작 API&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;removeChild(child)&lt;/td&gt;
&lt;td&gt;특정 자식 노드를 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;appendChild(child)&lt;/td&gt;
&lt;td&gt;특정 부모 노드의 마지막 자식으로 노드를 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;insertBefore(newNode, rNode)&lt;/td&gt;
&lt;td&gt;rNode 앞에 새로운 노드를 삽입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cloneNode()&lt;/td&gt;
&lt;td&gt;노드를 복제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replaceChild(newChild, oldChild)&lt;/td&gt;
&lt;td&gt;기존 자식 노드를 새로운 노드로 교체&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;문자열 기반의 쉬운 조작&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 &lt;b&gt;DOM을 직접 조작&lt;/b&gt;할 때는 &lt;code&gt;createElement&lt;/code&gt;, &lt;code&gt;appendChild&lt;/code&gt; 같은 &lt;b&gt;노드 단위 조작&lt;/b&gt;이 있고 &lt;code&gt;innerHTML&lt;/code&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;1️⃣ innerText&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;: element 안의 text 값들만 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;값 설정하기&lt;/b&gt;: html을 포함한 문자열을 입력하면 html 코드가 &lt;b&gt;문자열&lt;/b&gt; 그대로 element에 포함됩니다.&lt;/li&gt;
&lt;li&gt;요소의 &lt;b&gt;텍스트 콘텐츠만&lt;/b&gt; 다룸 (HTML 태그는 그대로 문자열로 처리)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;const div = document.querySelector(&quot;#myDiv&quot;);

console.log(div.innerText);  // 요소 안의 텍스트 반환
div.innerText = &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;2️⃣ innerHTML&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;element&lt;/b&gt; 안의 &lt;b&gt;HTML&lt;/b&gt; / &lt;b&gt;XML&lt;/b&gt;을 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;값 설정하기&lt;/b&gt;: html 코드 입력시 &lt;b&gt;html element&lt;/b&gt;가 element안에 포함&lt;/li&gt;
&lt;li&gt;요소의 &lt;b&gt;HTML 전체 문자열&lt;/b&gt; 반환 / 설정&lt;/li&gt;
&lt;li&gt;문자열을 &lt;b&gt;파싱해서 DOM 트리로 변환&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;기존 자식 노드를 모두 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;const div = document.querySelector(&quot;#firstDiv&quot;);

console.log(div.innerHTML);
// &quot;&amp;lt;span&amp;gt;Hello&amp;lt;/span&amp;gt;&quot; 같은 HTML 문자열 반환

div.innerHTML = &quot;&amp;lt;b&amp;gt;굵은 글씨&amp;lt;/b&amp;gt;&quot;;
// 기존 내용 사라지고 새로운 HTML 반영&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;3️⃣ insertAdjacentHTML(position, text)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 DOM 을 &lt;b&gt;덮어쓰지 않고&lt;/b&gt; 지정한 위치에 &lt;b&gt;HTML 문자열 삽입&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;position 값
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;beforebegin&quot; : 요소 &lt;b&gt;바깥, 앞&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&quot;afterbegin&quot; : 요소 &lt;b&gt;안쪽, 첫 번째 자식 앞&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&quot;beforeend&quot; : 요소 &lt;b&gt;안쪽, 마지막 자식 뒤&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&quot;afterend&quot; : 요소 &lt;b&gt;바깥, 뒤&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;const div = document.querySelector(&quot;#firstDiv&quot;);

// div 안에 &amp;lt;span&amp;gt; 삽입
div.insertAdjacentHTML(&quot;afterbegin&quot;, &quot;&amp;lt;span&amp;gt;Hi!&amp;lt;/span&amp;gt;&quot;);

// div 뒤에 &amp;lt;p&amp;gt; 삽입
div.insertAdjacentHTML(&quot;afterend&quot;, &quot;&amp;lt;p&amp;gt;끝!&amp;lt;/p&amp;gt;&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;템플릿 리터럴(Template Literal)&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;JavaScript의 &lt;b&gt;템플릿 리터럴(Template Literal)&lt;/b&gt; 문법을 사용하면 쉽게 HTML 문자열을 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;const user = {
   name : &quot;beomsic&quot;
}

// 백틱(`)을 사용하여 변수를 ${...} 안에 추가
const result = `
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      Hello! ${user.name}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
`;

console.log(template); &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Scripting/Events&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Scripting/Events&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Events&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/Events&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Events#Event_handler_properties&quot;&gt;https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Events#Event_handler_properties&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/jsref/dom_obj_document.asp&quot;&gt;https://www.w3schools.com/jsref/dom_obj_document.asp&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/jsref/dom_obj_all.asp&quot;&gt;https://www.w3schools.com/jsref/dom_obj_all.asp&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.javascript.info/introduction-browser-events&quot;&gt;https://ko.javascript.info/introduction-browser-events&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JS-&quot;&gt;https://inpa.tistory.com/entry/JS-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%BA%A1%EC%B3%90%EB%A7%81&quot;&gt; -버블링-캡쳐링&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.javascript.info/bubbling-and-capturing&quot;&gt;https://ko.javascript.info/bubbling-and-capturing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.javascript.info/event-delegation&quot;&gt;https://ko.javascript.info/event-delegation&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/HTML</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/166</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8#entry166comment</comments>
      <pubDate>Tue, 26 Aug 2025 13:51:58 +0900</pubDate>
    </item>
    <item>
      <title>CSS 알아보기</title>
      <link>https://beomsic.tistory.com/entry/CSS-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CSS 란?&lt;/h2&gt;
&lt;aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSS - Cascading Style Sheets&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;레이아웃&lt;/b&gt;을 담당하는 스타일 시트 언어입니다.&lt;/li&gt;
&lt;li&gt;HTML이 웹 콘텐츠의 뼈대를 만든다면 CSS는 그 뼈대에 색상, 글꼴, 위치 등 시각적인 요소를 입히는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  HTML과 CSS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML과 CSS는 각자의 문법을 가지는 별개의 언어.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML은 CSS를 포함할 수 있지만 HTML없이 존재하는 CSS는 의미가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  동작 원리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML과 브라우저의 동작 과정에서 CSS가 어떻게 적용되는지 간단하게 흐름 설명&lt;/li&gt;
&lt;li&gt;렌더링 파이프라인의 개념(HTML 파싱 &amp;rarr; CSS 파싱 &amp;rarr; 적용 &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;  기본 문법(사용법)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Av2J9/btsP4mfq763/yskOdkOkD89ZdOVOu02YT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Av2J9/btsP4mfq763/yskOdkOkD89ZdOVOu02YT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Av2J9/btsP4mfq763/yskOdkOkD89ZdOVOu02YT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAv2J9%2FbtsP4mfq763%2FyskOdkOkD89ZdOVOu02YT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;755&quot; height=&quot;216&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;370&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;CSS는 Selector(선택자) 와 Declaratives(선언부) 로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택자 CSS를 적용하고자 하는 HTML 요소&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;선언부&lt;/th&gt;
&lt;th&gt;하나 이상의 선언을 &lt;span style=&quot;color: #006dd7;&quot;&gt;;&lt;/span&gt; 으로 구분하고 중괄호를 사용해 전체를 둘러쌉니다.&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &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;HTML 요소 선택자, 아이디(#id), 클래스(.class), 그룹 선택자, 전체 선택자&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;선언은 CSS의 속성(property)와 속성 값(value)를 가지고 : 으로 연결됩니다.&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;  CSS 선언 방식&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;style을 html 파일에 적용하는 방법&lt;/p&gt;
&lt;/blockquote&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️⃣ 인라인 (in-line) 방식&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;HTML 요소(태그)&lt;/span&gt;&lt;/b&gt;안에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;style&lt;/b&gt;&lt;/span&gt; 속성을 사용 단일 HTML 요소에 고유한 스타일을 적용&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;h1 style=&quot;color:blue;&quot;&amp;gt;Blue H1&amp;lt;/h1&amp;gt;
&amp;lt;p style=&quot;color:red;&quot;&amp;gt;Red paragraph&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ Internal (embedded) 방식&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;head 섹션에서 style 태그를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt; &amp;lt;/style&amp;gt;&lt;/code&gt; 안에 작성&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;style type=&quot;text/css&quot;&amp;gt;
        body {background-color: powderblue;}
        h1   {color: blue;}
        p    {color: red;}
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
...
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ External (link) 방식&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 CSS 파일을 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;link&amp;gt;&lt;/span&gt;&lt;/b&gt; 를 이용해 불러와 적용하는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CSS Selector&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 선택자는 스타일을 지정하기 위한 HTML 요소를 찾는데 사용합니다.&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️⃣ HTML 요소(태그) 선택자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS를 적용할 HTML 요소 대상의 이름을 직접 사용해 선택&lt;/p&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;h2 {
  color: red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ id 선택자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS를 적용할 대상으로 &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;code&gt;#&lt;/code&gt; 를 사용해서 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;#test {
  color: red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ class 선택자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특정 집단(class)의 여러 요소를 한 번에 선택&lt;/b&gt;할 때 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 클래스 이름을 가지는 모든 요소들을 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;.test {
  color: red;
}

.test2{
  color: blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ 그룹 선택자&lt;/h3&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;code&gt;쉼표(,)&lt;/code&gt; 로 구분하고 연결&lt;/li&gt;
&lt;li&gt;이를 통해 코드를 중복하지 않게되어 코드가 간결해집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;h1, h2 {
  color: yellow;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣ 전체 선택자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소 내부의 모든 요소를 선택하기 위해 사용.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; 문자를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;* {
  color: red;
}

div * {
  color: blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CSS Cascading&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS는 &lt;b&gt;Cascading Style Sheets&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;cascading&lt;/b&gt; 를 이해해보려고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 요소는 적용할 수 있는 여러개의 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;&lt;b&gt;계층성(cascade)&lt;/b&gt; 과 밀접하게 관련된 &lt;b&gt;우선 순위(specificity)&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;웹 페이지의 디자인을 정의하는 CSS 스타일 규칙이 여러 소스에서 적용될 때, 어떤 스타일을 최종적으로 적용할지 결정하는 우선순위 규칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 원하는 element를 원하는 스타일로 만들기 위해서는 작동 방식을 이해해야 합니다.&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;  CSS 상속&lt;/h3&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;&lt;b&gt;✅ 상속하는 속성 예시 -&lt;/b&gt; &lt;code&gt;color&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt;
  p { color: blue; }
&amp;lt;/style&amp;gt;

&amp;lt;p&amp;gt;Parent 
  &amp;lt;span&amp;gt;Child&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;&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;Parent 과 Child 의 색이 모두 파란색이 됩니다.&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qWA4L/btsP4wIQ6eb/yErupKncYjKUPjttIXf9W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qWA4L/btsP4wIQ6eb/yErupKncYjKUPjttIXf9W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qWA4L/btsP4wIQ6eb/yErupKncYjKUPjttIXf9W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqWA4L%2FbtsP4wIQ6eb%2FyErupKncYjKUPjttIXf9W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;526&quot; height=&quot;220&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;상속하지 않는 속성 예시 -&lt;/b&gt; &lt;code&gt;padding&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt;
  p { padding: 20px; }
&amp;lt;/style&amp;gt;

&amp;lt;p&amp;gt;Parent 
  &amp;lt;span&amp;gt;Child&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;&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;p 태그에서만 여백을 만들고 span 에서는 여백을 만들지 않습니다.&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;  CSS 중요도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS가 어떻게 선언되었는지에 따라서 우선순위가 달라집니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;head&lt;/code&gt; 섹션에서 &lt;code&gt;style&lt;/code&gt; 태그를 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;head&lt;/code&gt; 섹션에서 &lt;code&gt;style&lt;/code&gt; 태그내의 &lt;code&gt;@import&lt;/code&gt; 문&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 로 연결된 CSS 파일&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 로 연결된 CSS 파일 내의 &lt;code&gt;@import&lt;/code&gt; 문&lt;/li&gt;
&lt;li&gt;브라우저 디폴트 스타일 시트&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  CSS Specificity - 스타일을 적용할 때의 우선순위&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일을 적용할 대상을 명확하게 특정할 수록 명시도가 높아지고 우선순위가 높아집니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;!important 
&amp;gt; 인라인 스타일 
&amp;gt; 아이디 선택자 
&amp;gt; 클래스 / 어트리뷰트 / 가상 선택자
&amp;gt; 태그 선택자
&amp;gt; 전체 선택자
&amp;gt; 상위 요소에 의해 상속된 속성&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;style&amp;gt;
    p        { color: red !important; }
    #thing   { color: blue; }

    div.food { color: chocolate; }
    .food    { color: green; }
    div      { color: orange; }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;p id=&quot;thing&quot;&amp;gt;Red Thing&amp;lt;/p&amp;gt;
  &amp;lt;div class=&quot;food&quot;&amp;gt;Chocolate&amp;lt;/div&amp;gt;
&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; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUPtye/btsP4viUBEq/ym9LOAbhLZVKwstpDJzl2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUPtye/btsP4viUBEq/ym9LOAbhLZVKwstpDJzl2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUPtye/btsP4viUBEq/ym9LOAbhLZVKwstpDJzl2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUPtye%2FbtsP4viUBEq%2Fym9LOAbhLZVKwstpDJzl2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;267&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;!important&lt;/b&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;나중에 설정한 값이 적용되지 않게 하려면 속성값(value) 뒤에 !important를 붙이면 무조건 해당 값이 적용됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;color: blue !important&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;!important&lt;/code&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;CSS의 우선순위 규칙을 무시&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;!important&lt;/b&gt;는 주로 다음과 같은 상황에서 사용됩니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;외부 라이브러리/프레임워크의 스타일을 재정의&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Bootstrap 같은 외부 라이브러리는 강력한 기본 스타일을 제공합니다. &lt;br /&gt;특정 컴포넌트의 스타일을 미세하게 조정하고 싶지만 라이브러리의 높은 우선순위 규칙 때문에 변경이 어려울 때 &lt;span data-token-index=&quot;1&quot;&gt;!important&lt;/span&gt;를 사용해 원하는 스타일을 덮어쓸 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;개발자 도구를 이용한 디버깅&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;특정 스타일을 테스트하거나 디버깅해야 할 때 다른 규칙을 일일이 수정하는 대신 &lt;b&gt;!important&lt;/b&gt;를 추가하여 빠르게 원하는 결과를 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;유틸리티 클래스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;display: none !important; 처럼 특정 목적을 위한 유틸리티 클래스를 만들 때 사용해 어떤 상황에서든 항상 적용되도록 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⏬ CSS 선언 순서&lt;/h3&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;p&amp;gt;this is red&amp;lt;/p&amp;gt;
  &amp;lt;p class=&quot;blue red&quot;&amp;gt;this is blue&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;p { color: blue; } 
p { color: red; } /* 우선 적용 */

.red { color: red; }
.blue { color: blue; } /* 우선 적용 */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Getting_started/Your_first_website/Styling_the_content&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Getting_started/Your_first_website/Styling_the_content&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Styling_basics/What_is_CSS&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Styling_basics/What_is_CSS&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/html/html_css.asp&quot;&gt;https://www.w3schools.com/html/html_css.asp&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/cssref/trysel.php&quot;&gt;https://www.w3schools.com/cssref/trysel.php&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/CSS-&quot;&gt;https://inpa.tistory.com/entry/CSS-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/CSS-%F0%9F%93%9A-css-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC&quot;&gt; -css-기본-문법-정리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/css3-inheritance-cascading&quot;&gt;https://poiemaweb.com/css3-inheritance-cascading&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_cascade/Specificity&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_cascade/Specificity&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/HTML</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/165</guid>
      <comments>https://beomsic.tistory.com/entry/CSS-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0#entry165comment</comments>
      <pubDate>Mon, 25 Aug 2025 23:33:26 +0900</pubDate>
    </item>
    <item>
      <title>Node.js - 싱글 스레드, 이벤트 루프 그리고 libuv</title>
      <link>https://beomsic.tistory.com/entry/Nodejs-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B7%B8%EB%A6%AC%EA%B3%A0-libuv</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Node.js 의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js에 대해서 학습을 해보면 아래 내용을 학습하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글 스레드&lt;/li&gt;
&lt;li&gt;이벤트 루프&lt;/li&gt;
&lt;li&gt;논 블로킹 I/O&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  어떻게 Node js는 싱글 스레드로 여러 비동기 작업들을 블로킹 없이 수행하는 것일까?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  싱글 스레드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드 시스템에서는 여러 스레드가 동시에 작업을 처리할 수 있어 작업이 병렬적으로 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 싱글 스레드 시스템에서는 한 번에 하나의 스레드만 코드를 실행하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 싱글 스레드 환경에서 프로그램은 다음 작업으로 넘어가기 위해서는 한 가지 작업을 완료해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;  &lt;b&gt;셰프가 1명뿐인 주방&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;셰프가 재료 손질을 하는 동안 동시에 스테이크를 굽거나 디저트를 만드는 일은 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 하나 뿐으로 JavaScript는 Call Stack을 사용해 함수 실행을 추적하고 함수가 호출되면 스택에 추가되며 함수가 반환되면 스택에서 제거됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function a() {
  console.log('A');
}

function b() {
  a();
  console.log('B');
}

b();&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; b&lt;/b&gt;&lt;/span&gt;가 스택에 추가되고 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;b&lt;/b&gt;&lt;/span&gt; 내부에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;a&lt;/b&gt;&lt;/span&gt;가 호출&lt;/li&gt;
&lt;li&gt;따라서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;a&lt;/b&gt; &lt;/span&gt;가 스택에 추가&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;a&lt;/b&gt;&lt;/span&gt;는 잠금 실행하고 스택에서 제거&lt;/li&gt;
&lt;li&gt;이후 함수&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; b&lt;/b&gt;&lt;/span&gt;가 계속해서&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; b&lt;/b&gt;&lt;/span&gt;를 잠그고 스택에서 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 간단한 작업에는 이 방법이 적합하지만 작업이 오랜 시간 걸리는 상황이라면 어떨까??&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;복잡한 알고리즘&lt;/b&gt;&lt;/i&gt; /&lt;i&gt;&lt;b&gt; 동기적 네트워크 요청&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는 작업을 완료하는데 스레드가 차단되어 다른 작업들이 블로킹되어 실행되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 단일 스레드 모델의 문제 중 하나는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메인 스레드를 차단&lt;/b&gt;&lt;/span&gt;한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 사용자의 요청이 들어와 처리하는 시스템에서 복잡한 계산이나 시간이 오래 걸리는 동기식 I/O작업 요청이 들어온다면 스레드가 하나뿐으로 이후에 요청된 모든 작업은 해당 작업이 완료될 때까지 기다려야 합니다.&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;code&gt;병렬성(Parallelism)&lt;/code&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;최신 시스템에는 여러 개의 CPU 코어가 있어 여러 작업을 동시에 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만 싱글 스레드 모델에서는 한 번에 하나의 작업만 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비디오 인코딩, 데이터 작업, 암호화 등 CPU를 많이 사용하는 작업은 여러 CPU 코어에 효율적으로 분산될 수 없어 시스템 속도가 느려질 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해 &lt;b&gt;Apache&lt;/b&gt;와 같은 웹 서버는 멀티 스레드 모델을 사용해 각 요청이 별도의 스레드로 처리되도록 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업시간이 긴 요청으로 인해 다른 요청이 차단되지 않습니다.&lt;/li&gt;
&lt;li&gt;이 방법은 소수의 사용자에게는 효과적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;멀티 스레드 모델의 문제&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽이 증가하면 수천 개의 스레드를 생성하고 관리하게 되어 과도한 컨텍스트 전환으로 인해 메모리 사용량이 높아지고 성능 병목 현상이 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;  Node.js&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OCecG/btsP5tKDuma/EPZftU4RKOlFVYYJL8e05K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OCecG/btsP5tKDuma/EPZftU4RKOlFVYYJL8e05K/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=os7KcmJvtN4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OCecG/btsP5tKDuma/EPZftU4RKOlFVYYJL8e05K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOCecG%2FbtsP5tKDuma%2FEPZftU4RKOlFVYYJL8e05K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;731&quot; height=&quot;351&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=os7KcmJvtN4&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;Node.js 는 요청마다 새로운 스레드를 생성하는 대신 단일스레드로 동작하지만 이벤트 기반의 논블로킹 I/O 모델을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 느린 작업을 기다리지 않고도 여러 개의 동시 요청에 대해 효율적으로 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이런 일들이 어떻게 가능한 것일까?!?!?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 이벤트 루프와 백그라운드 워커 스레드를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⭐ 이벤트 루프와 libuv&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qMzef/btsP5nRbYSH/Zpeicst6SK5sk97XOcoYUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qMzef/btsP5nRbYSH/Zpeicst6SK5sk97XOcoYUK/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=os7KcmJvtN4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qMzef/btsP5nRbYSH/Zpeicst6SK5sk97XOcoYUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqMzef%2FbtsP5nRbYSH%2FZpeicst6SK5sk97XOcoYUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;276&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=os7KcmJvtN4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 실행하는 작업을 차단하지 않고 여러 요청을 동시에 처리할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node.js&lt;/b&gt;는 기다리는 대신 작업을 위임합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 하나의 작업이 완료될 때까지 기다리는 것이 아닌 새로운 작업이 있는지 확인하고 작업간 효율적으로 전환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;  Event Demultiplexer&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node.js 이벤트 기반 아키텍처의 핵심 구성 요소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 진행중인 비동기 작업(파일 읽기, 데이터베이스, 네트워크 요청 등)을 모니터링하고 처리할 준비가 되면 Node.js에게 알리는&lt;code&gt;listener&lt;/code&gt; 역할&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js 가 파일 읽기, 데이터베이스 호출, 외부 API에서 데이터 가져오기 등 I/O와 관련된 요청을 받으면 이벤트 루프는 작업을 차단하지 않고 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Event Demultiplexer&lt;/b&gt;&lt;/span&gt;에 작업을 위임합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ Node.js 프로그램 동작 (I/O 작업)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 애플리케이션이 비동기 요청 (파일 읽기, 데이터베이스 쿼리, API 요청 처리 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 이벤트 루프는 작업을 &lt;b&gt;Event Demultiplexer&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;3️⃣ &lt;b&gt;Event Demultiplexer&lt;/b&gt; 는I/O 작업을 모니터링하고 작업이 완료될 때까지 기다립니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;I/O 작업이 진행되는 동안 Node.js 는 다른 작업들을 계속해서 실행&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;4️⃣ 작업이 완료되면 이벤트 큐에 푸시해 이벤트 루프에게 알리고 이벤트 루프는 이벤트를 수집하고 해당 콜백을 실행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 큐에 푸시: 작업을 처리할 준비가 되었다 의미&lt;/li&gt;
&lt;li&gt;이벤트 루프는 이벤트 큐를 지속적으로 확인하고 처리하지 않은 작업을 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식을 통해 Node.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;  그렇다면!! 실제로 &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;h2 data-ke-size=&quot;size26&quot;&gt;  Libuv&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node.js 의 비동기 이벤트 기반 아키텍처를 구현하는 C 라이브러리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제들의 커널을 추상화해 다양한 운영체제에서 일관되게 작동&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Libuv 역할&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuHVDA/btsP4bxgKsa/s065VURLF76PDbTCcp59hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuHVDA/btsP4bxgKsa/s065VURLF76PDbTCcp59hk/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=os7KcmJvtN4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuHVDA/btsP4bxgKsa/s065VURLF76PDbTCcp59hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuHVDA%2FbtsP4bxgKsa%2Fs065VURLF76PDbTCcp59hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;452&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=os7KcmJvtN4&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;1️⃣ 이벤트 디멀티플렉서가 I/O 작업을 수신하면 이를 &lt;b&gt;Libuv&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;2️⃣ &lt;b&gt;Libuv&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;ex) 파일 시스템 작업이나 암호화 같이 CPU 집약적인 작업
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 스레드 풀의 worker 스레드에 작업을 분산.&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;3️⃣ 작업이 완료되면 &lt;b&gt;Libuv&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;Node.js는 싱글 스레드이지만 실제로 무거운 작업의 경우에는 백그라운드 워커 스레드를 사용해 I/O 작업을 처리합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  I/O 멀티플렉싱&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 커널 스레드가 여러 I/O 자원(파일, 소켓 등)을 감시하면서 어떤 자원에서 이벤트(읽기, 쓰기, 연결 등)가 발생했는지 알려주는 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;  &lt;b&gt;운영체제별 I/O 멀티플렉싱 API&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;운영체제&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;epoll&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mac OS&lt;/td&gt;
&lt;td&gt;kqueue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;IOCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;libuv&lt;/b&gt;는 이처럼 각 운영체제마다 다른 비동기 I/O 멀티플렉싱 API를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추상화&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 개발자는 OS에 따라 다른 API를 신경 쓸 필요 없이 Node.js의 통일된 비동기 API(&lt;code&gt;fs.readFile&lt;/code&gt;, &lt;code&gt;http.get&lt;/code&gt; 등)를 사용하면 됩니다.&lt;/p&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;libuv&lt;/b&gt;가 내부적으로 현재 실행 환경에 맞는 API를 선택하여 비동기 I/O를 처리해 줍니다.&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;  Thread pool&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;libuv&lt;/b&gt;는 worker threads를 관리하는 Thread Pool를 사용합니다.&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 data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;OS 커널이 제공하는 비동기 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;네트워크 I/O 등 OS 커널이 비동기적으로 처리할 수 있는 작업은 &lt;code style=&quot;letter-spacing: 0px;&quot;&gt;epoll (Linux)&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;kqueue (macOS)&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;IOCP (Windows)&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;와 같은 OS 커널의 API를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이를 통해 효율적으로 I/O 작업을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;libuv의 워커 스레드 풀 사용&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;OS 커널이 비동기 API를 제공하지 않는 작업(예: 파일 I/O, DNS 조회, CPU 집약적인 암호화 작업)의 경우 libuv는 워커 &lt;b&gt;스레드 풀&lt;/b&gt;을 사용합니다.&lt;/li&gt;
&lt;li&gt;이 스레드 풀의 스레드들이 작업을 백그라운드에서 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Thread Pool 동작 방식&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;libuv&lt;/b&gt;에 파일 읽기 같은 비동기 작업을 요청하면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OS 커널이 해당 작업을 비동기적으로 지원하는지 확인합니다&lt;/li&gt;
&lt;li&gt;커널이 지원한다면 커널에게 비동기적으로 요청합니다&lt;/li&gt;
&lt;li&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;4개의 스레드&lt;/b&gt;를 가지는 스레드 풀을 생성합니다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UV_THREADPOOL_SIZE&lt;/code&gt; 환경변수로 스레드 수를 조정할 수 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;파일 I/O와 네트워크 I/O의 차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;네트워크 I/O&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;네트워크 I/O(예: 웹 서버에서 데이터베이스 쿼리, HTTP 요청/응답)는 대부분의 운영체제에서 &lt;b&gt;비동기 API&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;libuv&lt;/b&gt;는 &lt;code&gt;epoll&lt;/code&gt;, &lt;code&gt;kqueue&lt;/code&gt;, &lt;code&gt;IOCP&lt;/code&gt;와 같은 OS의 &lt;b&gt;I/O 멀티플렉싱 API&lt;/b&gt;를 사용하여 네트워크 소켓의 이벤트를 감시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API들은 네트워크 작업이 완료될 때까지 &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;이벤트가 발생하면 OS가 &lt;b&gt;libuv&lt;/b&gt;에 통지하고 &lt;b&gt;libuv&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: #006dd7;&quot;&gt;&lt;b&gt;파일 I/O&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 I/O(예: 로컬 디스크에서 파일 읽기/쓰기)는 네트워크 I/O와 달리 &lt;b&gt;OS별로 비동기 지원 여부가 다르거나 비효율적&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;POSIX(Linux, macOS) 시스템에서는 파일 I/O를 위한 효율적인 비동기 API가 제한적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;libuv&lt;/b&gt;는 &lt;b&gt;스레드 풀&lt;/b&gt;을 사용하여 파일 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;l&lt;/b&gt;&lt;b&gt;ibuv&lt;/b&gt;는 파일 읽기 작업을 워커 스레드 중 하나에 위임하고 메인 스레드는 블로킹되지 않고 다른 작업을 계속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워커 스레드가 파일 읽기를 완료하면 결과를 이벤트 루프에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=os7KcmJvtN4&quot;&gt;https://www.youtube.com/watch?v=os7KcmJvtN4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/node-js/demultiplexer-in-nodejs/&quot;&gt;https://www.geeksforgeeks.org/node-js/demultiplexer-in-nodejs/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/node-js/libuv-in-node-js/&quot;&gt;https://www.geeksforgeeks.org/node-js/libuv-in-node-js/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.libuv.org/en/v1.x/&quot;&gt;https://docs.libuv.org/en/v1.x/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=nGn60vDSxQ4&quot;&gt;https://www.youtube.com/watch?v=nGn60vDSxQ4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/softup-technologies/node-js-internals-libuv-and-the-event-loop-behind-the-curtain-30708c5ca83&quot;&gt;https://medium.com/softup-technologies/node-js-internals-libuv-and-the-event-loop-behind-the-curtain-30708c5ca83&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/164</guid>
      <comments>https://beomsic.tistory.com/entry/Nodejs-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B7%B8%EB%A6%AC%EA%B3%A0-libuv#entry164comment</comments>
      <pubDate>Sat, 23 Aug 2025 17:21:41 +0900</pubDate>
    </item>
    <item>
      <title>Express 을 이용해 웹페이지를 표현하는 방법 - 템플릿 엔진</title>
      <link>https://beomsic.tistory.com/entry/Express-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80%EB%A5%BC-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%97%94%EC%A7%84</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Express - 웹 페이지를 표현하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;express&lt;/code&gt;를 이용해 웹페이지를 표현하는 방법으로는 두가지 방법이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;정적인 파일을 전달&lt;/li&gt;
&lt;li&gt;동적인 파일을 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정적인 파일을 이용해 웹페이지 표현하기&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;project/
├── app.js                 # Express 서버 메인 파일
├── package.json
├── package-lock.json
├── public/
│   ├── static.html        # 메인 페이지&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;code&gt;static.html&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    Hello World!
  &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;&lt;code&gt;app.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const path = require(&quot;path&quot;);
const app = express();
const port = 3000;

app.use(express.static(&quot;public&quot;));

app.listen(port, () =&amp;gt; {
  console.log(` &amp;lt;http://localhost&amp;gt;:${port}에서 실행중...`);
});&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uer5u/btsPXXfw63f/1wBKS0e05fBouHhiji39H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uer5u/btsPXXfw63f/1wBKS0e05fBouHhiji39H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uer5u/btsPXXfw63f/1wBKS0e05fBouHhiji39H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUer5u%2FbtsPXXfw63f%2F1wBKS0e05fBouHhiji39H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;206&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;static.html&lt;/code&gt;의 내용을 수정하고 현재 &lt;code&gt;localhost:3000&lt;/code&gt;을 리로드하면 변경된 내용이 바로 반영됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCLY1M/btsPXWnmMHq/u2wpjBmvgumgvdr6qicaPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCLY1M/btsPXWnmMHq/u2wpjBmvgumgvdr6qicaPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCLY1M/btsPXWnmMHq/u2wpjBmvgumgvdr6qicaPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCLY1M%2FbtsPXWnmMHq%2Fu2wpjBmvgumgvdr6qicaPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;219&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정적인 파일은 서버를 껏다가 다시 킬 필요가 없이 내용을 수정하면 바로 반영할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  동적으로 웹페이지 표현하기&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;app.get(&quot;/dynamic&quot;, function (req, res) {
  let output = `
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang=&quot;en&quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        Hello Dynamic!@!
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
  `;
  res.send(output);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;code&gt;app.get&lt;/code&gt; 의 경우는 내용이 수정되면 다시 껏다가 켜야 반영이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;동적으로 처리를 하면 수정한 코드를 다시 실행해주어야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  그럼 html으로 정적인 페이지를 통해서 처리하는게 더 좋은거 아닌가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;code&gt;HTML&lt;/code&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;ex) 같은 텍스트를 여러번 반복해서 보여주고 싶다&lt;/li&gt;
&lt;li&gt;texttext...&lt;/li&gt;
&lt;li&gt;text&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;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;app.get(&quot;/dynamic&quot;, function (req, res) {
  let texts = &quot;&quot;;
  for (let i = 0; i &amp;lt; 5; i++) {
    texts = texts + &quot;&amp;lt;p&amp;gt;text&amp;lt;/p&amp;gt;&quot;;
  }
  let output = `
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang=&quot;en&quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        Hello Dynamic!@!
        ${texts}
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
  `;
  res.send(output);
});&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bND4wL/btsPXRl8C3p/ECmPSpl7KX9TU5iUkVvPgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bND4wL/btsPXRl8C3p/ECmPSpl7KX9TU5iUkVvPgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bND4wL/btsPXRl8C3p/ECmPSpl7KX9TU5iUkVvPgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbND4wL%2FbtsPXRl8C3p%2FECmPSpl7KX9TU5iUkVvPgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;426&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⭐ 템플릿 엔진&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  동적인 웹 페이지를 생성하는 데 사용하는 도구&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션에서는 템플릿 엔진을 사용하여 &lt;b&gt;서버에서 생성된 데이터&lt;/b&gt;와 &lt;b&gt;템플릿 파일&lt;/b&gt;을 조합하여 &lt;b&gt;HTML 코드&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;/li&gt;
&lt;li&gt;대표적인 예시: EJS, Handlebars, Pug(Jade) 등&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  동작 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 요청&lt;/b&gt;: 클라이언트(브라우저)가 서버에 웹 페이지를 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 처리&lt;/b&gt;: 서버는 데이터베이스 등에서 필요한 데이터를 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;템플릿 결합&lt;/b&gt;: 서버는 가져온 데이터와 HTML 뼈대인 &lt;b&gt;템플릿 파일&lt;/b&gt;을 템플릿 엔진을 통해 결합합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTML 생성&lt;/b&gt;: 템플릿 엔진은 데이터가 채워진 완전한 HTML 코드를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 응답&lt;/b&gt;: 완성된 HTML이 클라이언트에게 전송됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 서버는 매번 정적인 HTML 파일을 보내는 대신 데이터만 바꿔서 동적인 페이지를 효율적으로 제공합니다.&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;1️⃣ Pug (Jade)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pug는 HTML의 태그를 최소화한 간결한 문법을 사용하는 템플릿 엔진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들여쓰기(Indentation)를 기반으로 HTML의 계층 구조를 나타내며 닫는 태그를 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&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;HTML과 다른 문법을 사용해 학습이 필요합니다.&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;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;doctype html
html
  head
    title= 'Pug Example'
  body
    h1= '안녕하세요, ' + user.name + '님!'
    ul
      each post in posts
        li
          h2= post.title
          p= post.content&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ EJS (Embedded Javascript Templates)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EJS는 &lt;b&gt;HTML 코드 내부에 JavaScript 코드&lt;/b&gt;를 삽입하여 동적인 웹 페이지를 생성하는 템플릿 엔진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 HTML 문법을 그대로 사용하면서 &lt;code&gt;&amp;lt;% ... %&amp;gt;&lt;/code&gt; 와 같은 특수 태그로 JavaScript 코드를 삽입합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;%= ... %&amp;gt;&lt;/code&gt; 와 같은 문법을 사용해 서버에서 전달받은 데이터를 HTML에 쉽게 삽입할 수 있습니다.&lt;/li&gt;
&lt;li&gt;HTML과 JS가 함께 보여 직관적으로 코드를 이해할 수 있습니다.&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;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;EJS Example&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;안녕하세요, &amp;lt;%= user.name %&amp;gt;님!&amp;lt;/h1&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;% posts.forEach(function(post) { %&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;h2&amp;gt;&amp;lt;%= post.title %&amp;gt;&amp;lt;/h2&amp;gt;
          &amp;lt;p&amp;gt;&amp;lt;%= post.content %&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;/li&amp;gt;
      &amp;lt;% }); %&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;HTML 내부에 &lt;code&gt;&amp;lt;% %&amp;gt;&lt;/code&gt;와 &lt;code&gt;&amp;lt;%= %&amp;gt;&lt;/code&gt; 등을 사용하여 JavaScript 로직을 삽입합니다&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; ️ EJS 사용해보기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚙️ 설치&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm install -D ejs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 패키지 구조&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;├── public/                # 클라이언트에 직접 제공될 정적 파일
│   ├── css/               # CSS 파일
│   ├── js/                # JavaScript 파일
│   └── images/            # 이미지 파일
├── views/                 # EJS 템플릿 파일
│   ├── index.ejs          # 메인 페이지 템플릿
│   └── partials/          # 재사용 가능한 부분 템플릿
│       ├── header.ejs
│       └── footer.ejs
├── app.js              # Express 서버 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;views&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;EJS 파일들은 클라이언트가 직접 접근할 수 없어 일반적으로 views라는 별도 폴더에 저장합니다.&lt;/li&gt;
&lt;li&gt;Express의 &lt;code&gt;res.render()&lt;/code&gt; 메서드는 이 폴더를 기준으로 템플릿 파일을 찾습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;partials&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;EJS에서는 &lt;code&gt;&amp;lt;%- include('partials/header.ejs') %&amp;gt;&lt;/code&gt;와 같은 문법으로 포함시킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  EJS 설정&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const path = require(&quot;path&quot;);
const app = express();
const port = 3000;

// EJS를 템플릿 엔진으로 설정
app.set(&quot;view engine&quot;, &quot;ejs&quot;);
// 템플릿 파일들이 위치한 디렉토리 설정 (views 폴더)
app.set(&quot;views&quot;, path.join(__dirname, &quot;views&quot;));

// 정적 파일 서빙을 위해 public 폴더 설정
app.use(express.static(&quot;public&quot;));

app.get(&quot;/&quot;, (req, res) =&amp;gt; {
  const userData = { name: &quot;게스트&quot;, isLoggedIn: false };
  const postsData = [
    {
      title: &quot;첫 번째 게시물&quot;,
      content: &quot;안녕하세요, EJS를 사용한 첫 페이지입니다.&quot;,
    },
    { title: &quot;두 번째 게시물&quot;, content: &quot;데이터가 동적으로 출력되고 있어요!&quot; },
  ];

  // res.render()를 사용하여 템플릿과 데이터를 결합
  res.render(&quot;index&quot;, { user: userData, posts: postsData });
});

// 서버 시작
app.listen(port, () =&amp;gt; {
  console.log(` &amp;lt;http://localhost&amp;gt;:${port}에서 실행중...`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;EJS 템플릿 설정&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;code&gt;app.set()&lt;/code&gt; 메서드를 사용해 Express 애플리케이션에 EJS를 사용할 것임을 알려주고 템플릿 파일들이 위치할 디렉토리를 지정해줍니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// EJS를 템플릿 엔진으로 선언
app.set(&quot;view engine&quot;, &quot;ejs&quot;);
// 템플릿 파일들의 위치 설정 (views 폴더)
app.set(&quot;views&quot;, path.join(__dirname, &quot;views&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;b&gt;EJS 템플릿 렌더링 (파일 연결)&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;code&gt;res.render()&lt;/code&gt; 메서드를 사용하면 서버가 템플릿 파일에 데이터를 주입하여 렌더링한 후 클라이언트에게 보낼 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 서버에서 데이터를 생성
const userData = { name: &quot;게스트&quot;, isLoggedIn: false };

// 'index.ejs' 템플릿에 데이터를 전달하여 렌더링
res.render(&quot;index&quot;, { user: userData });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKS38g/btsPXUJSkFm/DYSzVrKTDa2KbLyE7QSFe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKS38g/btsPXUJSkFm/DYSzVrKTDa2KbLyE7QSFe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKS38g/btsPXUJSkFm/DYSzVrKTDa2KbLyE7QSFe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKS38g%2FbtsPXUJSkFm%2FDYSzVrKTDa2KbLyE7QSFe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;302&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;534&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;  EJS 문법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EJS의 핵심은 HTML 코드 내부에 삽입하는 &lt;b&gt;스크립팅 태그&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 태그들을 통해 HTML과 JavaScript 코드를 결합할 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 183px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;태그&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;&amp;lt;% ... %&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;JavaScript 코드를 실행합니다. if, for, forEach와 같은 제어 흐름 문법을 작성할 때 사용&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;&amp;lt;% if (user) { %&amp;gt; ... &amp;lt;% } %&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&amp;lt;%= ... %&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;변수나 표현식의 값을 &lt;b&gt;HTML로 안전하게 변환하여 출력&lt;/b&gt;합니다. &amp;lt;나 &amp;gt; 같은 특수 문자를 &amp;amp;lt;나 &amp;amp;gt;로 변환하여 &lt;b&gt;XSS 공격&lt;/b&gt;을 방지합니다.&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&amp;lt;h1&amp;gt;&amp;lt;%= user.name %&amp;gt;&amp;lt;/h1&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&amp;lt;%- ... %&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;변수나 표현식의 값을 &lt;b&gt;그대로 출력&lt;/b&gt;합니다. HTML 태그가 포함된 문자열을 출력할 때 사용합니다. 신뢰할 수 있는 데이터에만 사용해야 합니다.&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&amp;lt;%- post.content %&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&amp;lt;%- include(&amp;rdquo;경로&amp;rdquo;) %&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;다른 EJS 템플릿 파일을 현재 위치에 포함시킵니다. 헤더, 푸터와 같은 재사용 가능한 부분을 분리할 때 유용합니다. 경로는 views 폴더를 기준으로 합니다.&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&amp;lt;%- include('partials/header') %&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;  &lt;b&gt;문법 테스트 해보기&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;EJS 문법 테스트&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;% const fruits = [&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;]; %&amp;gt;

    &amp;lt;h2&amp;gt;1. 변수 출력 (&amp;lt;%= %&amp;gt;)&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;안녕하세요, &amp;lt;%= &quot;EJS&quot; %&amp;gt; 테스트입니다.&amp;lt;/p&amp;gt;

    &amp;lt;h2&amp;gt;2. forEach 반복문&amp;lt;/h2&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;% fruits.forEach(fruit =&amp;gt; { %&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;%= fruit %&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;% })%&amp;gt;
    &amp;lt;/ul&amp;gt;

    &amp;lt;h2&amp;gt;3. 조건문&amp;lt;/h2&amp;gt;
    &amp;lt;% if (fruits.length &amp;gt; 2) { %&amp;gt;
        &amp;lt;p&amp;gt;과일이 2개 이상 있습니다.&amp;lt;/p&amp;gt;
    &amp;lt;% } else { %&amp;gt;
        &amp;lt;p&amp;gt;과일이 부족합니다.&amp;lt;/p&amp;gt;
    &amp;lt;% } %&amp;gt;

    &amp;lt;h2&amp;gt;4. HTML 그대로 출력 (&amp;lt;%- %&amp;gt;)&amp;lt;/h2&amp;gt;
    &amp;lt;% const htmlContent = &quot;&amp;lt;b&amp;gt;굵게 표시된 텍스트&amp;lt;/b&amp;gt;&quot;; %&amp;gt;
    &amp;lt;div&amp;gt;&amp;lt;%- htmlContent %&amp;gt;&amp;lt;/div&amp;gt;
  &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; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/divQvd/btsP0Yw4UWr/SpXecKo9uDmxeJ6jTSbKx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/divQvd/btsP0Yw4UWr/SpXecKo9uDmxeJ6jTSbKx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/divQvd/btsP0Yw4UWr/SpXecKo9uDmxeJ6jTSbKx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdivQvd%2FbtsP0Yw4UWr%2FSpXecKo9uDmxeJ6jTSbKx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;257&quot; height=&quot;377&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  HTML 이스케이프&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 이스케이프는 &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;code&gt;&amp;lt;&lt;/code&gt; 를 HTML 태그의 시작과 &lt;code&gt;&amp;gt;&lt;/code&gt;를 끝으로 인식합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사용자가 입력한 데이터에 script 와 같은 태그가 포함되어 있다면 브라우저는 이를 실제 코드로 인식하고 실행해버릴 위험이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 이스케이프는 이러한 &lt;code&gt;&amp;lt;&lt;/code&gt; 같은 특수 문자를 브라우저가 &lt;b&gt;'HTML 태그'가 아닌 '일반 문자'&lt;/b&gt; 로 인식하도록 바꿔주는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  XSS 공격과 &amp;lt;%= %&amp;gt;의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;XSS(Cross-Site Scripting) 공격&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;code&gt;&amp;lt;script&amp;gt;alert('악성 스크립트');&amp;lt;/script&amp;gt;&lt;/code&gt; 와 같은 코드를 작성했다고 가정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;lt;%- %&amp;gt;&lt;/span&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;템플릿 엔진은 이 코드를 HTML로 변환하지 않고 &lt;b&gt;그대로&lt;/b&gt; 출력합니다. (이스케이프 하지 않고 그대로 출력)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;alert('악성 스크립트');&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;브라우저는 이 코드를 HTML 태그/스크립트로 인식해 실행될 수 있습니다. (XSS 위험)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;%= %&amp;gt;&lt;/span&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;code&gt;&amp;lt;&lt;/code&gt;와 &lt;code&gt;&amp;gt;&lt;/code&gt;를 일반 문자로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;lt;script&amp;amp;gt;alert('악성 스크립트');&amp;amp;lt;/script&amp;amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;브라우저는 &amp;lt;를 &lt;code&gt;&amp;lt;&lt;/code&gt;로, &amp;gt;를 &lt;code&gt;&amp;gt;&lt;/code&gt;로 인식하지만 이는 더 이상 실행 가능한 &lt;b&gt;스크립트 태그가 아닌 단순 텍스트&lt;/b&gt;가 됩니다.&lt;/li&gt;
&lt;li&gt;결과적으로 화면에는 &lt;code&gt;&amp;lt;script&amp;gt;alert('악성 스크립트');&amp;lt;/script&amp;gt;&lt;/code&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;  &lt;b&gt;XSS 상황 테스트&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;code&gt;server.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const path = require(&quot;path&quot;);
const app = express();
const port = 3000;

app.set(&quot;view engine&quot;, &quot;ejs&quot;);
app.set(&quot;views&quot;, path.join(__dirname, &quot;views&quot;));

app.get(&quot;/&quot;, (req, res) =&amp;gt; {
  // 사용자 입력 가정 (악성 스크립트 포함)
  const userInput = &quot;&amp;lt;script&amp;gt;alert('XSS 공격!');&amp;lt;/script&amp;gt;&quot;;

  res.render(&quot;index&quot;, { input: userInput });
});

app.listen(port, () =&amp;gt; {
  console.log(`서버 실행: &amp;lt;http://localhost&amp;gt;:${port}`);
});&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;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;views/index.ejs&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;EJS XSS 테스트&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h2&amp;gt;사용자 입력 테스트&amp;lt;/h2&amp;gt;

  &amp;lt;h3&amp;gt;1. &amp;lt;%= %&amp;gt; (이스케이프 O)&amp;lt;/h3&amp;gt;
  &amp;lt;div&amp;gt;&amp;lt;%= input %&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;h3&amp;gt;2. &amp;lt;%- %&amp;gt; (이스케이프 X, 실행됨)&amp;lt;/h3&amp;gt;
  &amp;lt;div&amp;gt;&amp;lt;%- input %&amp;gt;&amp;lt;/div&amp;gt;
&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; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjWK7d/btsPYzZSQbt/hF55r9s5kjKe9oHRIgiCV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjWK7d/btsPYzZSQbt/hF55r9s5kjKe9oHRIgiCV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjWK7d/btsPYzZSQbt/hF55r9s5kjKe9oHRIgiCV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjWK7d%2FbtsPYzZSQbt%2FhF55r9s5kjKe9oHRIgiCV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;258&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;%= input %&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;alert('XSS 공격!');&amp;lt;/script&amp;gt;
&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;li&gt;실행되지 않음 ✅ (안전)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;lt;%- input %&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저가 실제&lt;b&gt; &amp;lt;script&amp;gt; &lt;/b&gt;로 인식해서 &amp;rarr; &lt;b&gt;alert(&quot;XSS 공격!&quot;)&lt;/b&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;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ejs.co/&quot;&gt;https://ejs.co/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-yesung.tistory.com/167&quot;&gt;https://coding-yesung.tistory.com/167&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ccusean.tistory.com/entry/Express-템플릿-엔진-ejs-알아보기&quot;&gt;https://ccusean.tistory.com/entry/Express-템플릿-엔진-ejs-알아보기&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/163</guid>
      <comments>https://beomsic.tistory.com/entry/Express-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80%EB%A5%BC-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%97%94%EC%A7%84#entry163comment</comments>
      <pubDate>Wed, 20 Aug 2025 17:38:30 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 렌더링 방식 (SSR, CSR)</title>
      <link>https://beomsic.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D-SSR-CSR</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  브라우저 렌더링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 서버로부터 요청해 받은 내용을 브라우저의 화면에 표시하는 작업&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML, CSS, JavaScript 문서를 전달받아 브라우저 엔진이 각 문서를 해석해 브라우저 화면을 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 브라우저 렌더링과정을 학습을 했었는데 이제 브라우저 렌더링의 방식에 대해서 알아보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링 방식은 크게 서버 사이드 렌더링(SSR), 클라이언트 사이드 렌더링(CSR)로 구분됩니다.&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;↩️ 이전 히스토리 (MPA와 SPA)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1990s&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1990년 중반까지는 모두 &lt;code&gt;static sites&lt;/code&gt; 였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에는 만들어진 HTML 문서들이 존재했고 사용자가 브라우저에 접속하면 서버에 있는 HTML 문서를 가져와 보여주는 형식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt; MPA (Multi Page Application)&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;이동할 때마다 서버로부터 새로운 HTML을 새로 받아와 페이지 전체를 새로 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;문제점 - Blink&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;웹 페이지내에서 다른 링크를 클릭 시 다시 서버에서 해당 페이지의 HTML을 받아와서 페이지 전체가 업데이트가 됩니다. (새로고침 발생)&lt;/li&gt;
&lt;li&gt;사용성이 매우 떨어짐!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1996년 - iframe&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1996년 문서내에서 또 다른 문서를 담을 수 있는 &lt;code&gt;iframe&lt;/code&gt; 태그가 도입되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 페이지내에서 부분적으로 문서를 받아와 업데이트할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1998 - XMLHttpRequest&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fetch API&lt;/code&gt;의 원조인 &lt;code&gt;XMLHttpRequest&lt;/code&gt;가 개발되어 HTML 문서 전체가 아닌 JSON과 같은 포맷으로 서버에서 가볍게 필요한 데이터만 받아올 수 있게 되었습니다.&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;동적&lt;/b&gt;으로 &lt;b&gt;HTML 요소&lt;/b&gt;를 생성해 &lt;b&gt;페이지에 업데이트&lt;/b&gt;하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식이 2005년에 &lt;code&gt;AJAX&lt;/code&gt; 라는 이름을 가지게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AJAX를 이용해 웹 어플리케이션을 만들기 시작합니다. 이런 방식이 &lt;b&gt;SPA(Single Page Application)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 한 페이지 내에서 머무르면서 필요한 데이터를 서버에서 받아와 부분적으로 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA 와 PC 성능이 점차 좋아지면서 자바스크립트 표준화가 잘되어가면서 Angular, React, Vue 와 같은 프레임워크등이 나오면서 CSR 시대로 접어들게 됩니다.&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;  클라이언트 사이드 렌더링(CSR)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSR에서 사용되는 HTML 예제 (빈 뼈대만 있는 HTML)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;CSR&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script scr=&quot;app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;body에는 id 값만 들어가 있고 필요한 자바스크립트의 링크만 주어집니다.&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;따라서, HTML은 비어 있어 처음 접속시 빈 화면만 보이게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 링크된 자바스크립트를 서버로부터 다운로드 받아 이 자바스크립트에는 필요한 로직들뿐만 아니라 애플리케이션을 구동하는 프레임워크와 라이브러리의 소스 코드들도 다 포함되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;굉장히 사이즈가 커서 다운받는데도 시간이 소요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 필요한 데이터는 서버에 요청해 데이터를 받아와 동적으로 HTML을 생성해 사용자에게 최종 웹 페이지를 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;CSR 동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/skJPD/btsP0vvbBAB/t9SCKX70qfosYo8e475DV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/skJPD/btsP0vvbBAB/t9SCKX70qfosYo8e475DV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/skJPD/btsP0vvbBAB/t9SCKX70qfosYo8e475DV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FskJPD%2FbtsP0vvbBAB%2Ft9SCKX70qfosYo8e475DV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;202&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저가 JS를 다운로드 받고 DOM 을 생성한 후에 웹 페이지를 볼 수 있고 인터렉션이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 로딩 이후에 페이지 일부를 변경할 때에는 서버에 해당 데이터만 요청하면 되어서 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &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;서버가 빈 뼈대의 HTML만 넘겨주면 됩니다.&lt;/li&gt;
&lt;li&gt;클라이언트에서 연산, 라우팅등을 처리해 반응속도가 빠르고 사용자 경험이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &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;필요한 JS 파일을 다운받고 동적으로 DOM을 생성하는 시간을 기다려야해 초기 로딩 속도가 느립니다.&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;2️⃣ &lt;b&gt;SEO&lt;/b&gt;  &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;SEO&lt;/b&gt; &lt;b&gt;(Search Engine Optimization)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글, 네이버 같은 검색 엔진들은 서버에 등록된 웹사이트를 돌아다니면서 웹사이트의 HTML의 문서를 분석하여 검색을 빠르게 할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ CSR에서의 HTML body는 내용이 없어 검색엔진들이 웹페이지를 분석하는데 어렵습니다.&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; ️ 서버 사이드 렌더링(SSR)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSR&lt;/b&gt;에서의 문제점들 때문에 1990년 중반쯤에 사용한 Static Sites에 영감을 받은 &lt;b&gt;Server side Rendering&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;HTML 파일(렌더링 준비 끝)&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;HTML 파일을 동적으로 제어할 수 있는 소스 코드와 함께 보내줍니다.&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;SSR 동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDpeIT/btsPZjoJKGZ/JOHHT1CSVFxS5ohp83OKN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDpeIT/btsPZjoJKGZ/JOHHT1CSVFxS5ohp83OKN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDpeIT/btsPZjoJKGZ/JOHHT1CSVFxS5ohp83OKN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDpeIT%2FbtsPZjoJKGZ%2FJOHHT1CSVFxS5ohp83OKN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;208&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;완성된 html 파일을 받고나서 사용자는 웹 페이지를 볼 수 있지만 JS 파일을 다운받고 처리하지 않아 바로 인터렉션을 하지 못할 수 있습니다.&lt;/li&gt;
&lt;li&gt;따라서 사용자가 웹 사이트를 볼 수 있는 시간(TTV, Time To View)과 인터렉션(TTI, Time To Interact)을 할 수 있는 시간의 공백이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &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;서버로부터 완전하게 만들어진 HTML 파일을 받아와 화면에 표시하기 때문에 첫 화면 로딩 속도가 빠릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;SEO&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;모든 콘텐츠가 HTML에 담겨져 있어 효율적인 SEO가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;Blinking (화면 깜빡임)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;static sites&lt;/code&gt; 에서 발생했던 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 클릭시 전체적인 웹사이트를 다시 서버에서 받아와 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;서버 과부하&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 많은 서비스일 수록 사용자가 클릭을 할 때마다 서버에 요청을 해 서버에서 필요한 데이터를 가져와 HTML을 만들어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;인터렉션을 하기위한 시간이 필요&lt;/b&gt;  &lt;b&gt;(TTV &amp;ne; TTI)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 빠르게 웹 사이트를 확인할 수 있지만 동적으로 데이터를 처리하는 자바스크립트를 아직 다운로드 받지 못해서 사용자가 클릭을 해도 반응이 없을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CSR, SSR - SPA, MPA 와의 관계&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bldDQi/btsP0RSgjMB/PZaSx5Psnnqw5efvEtUYqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bldDQi/btsP0RSgjMB/PZaSx5Psnnqw5efvEtUYqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bldDQi/btsP0RSgjMB/PZaSx5Psnnqw5efvEtUYqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbldDQi%2FbtsP0RSgjMB%2FPZaSx5Psnnqw5efvEtUYqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;393&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SPA&lt;/b&gt;의 경우에는 웹 애플리케이션에 필요한 정적 리소스들을 처음에 모두 다운로드하고 새로운 페이지 요청시 필요한 데이터를 전달받아 클라이언트에서 페이지를 갱신하기 때문에 렌더링 방식으로 &lt;b&gt;CSR&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;MPA&lt;/b&gt;의 경우에는 새로운 요청이 있을 때마다 서버에서 새로운 정적 리소스를 받아오기 때문에 렌더링방식으로 &lt;b&gt;SSR&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ SPA &lt;code&gt;!=&lt;/code&gt; CSR , MPA &lt;code&gt;!=&lt;/code&gt; SSR&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두개의 개념은 다른 개념입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지가 몇개인지, 렌더링을 어디서 하는지에 따라 달라지는 개념입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 같은 프레임워크를 사용하면 &lt;b&gt;SPA 구조를 가지면서도 초기 페이지를 SSR로 렌더링하여 SEO(검색 엔진 최적화) 문제를 해결합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=iZ9csAfU5Os&amp;amp;t=115s&amp;amp;ab_channel=%EB%93%9C%EB%A6%BC%EC%BD%94%EB%94%A9&quot;&gt;https://www.youtube.com/watch?v=iZ9csAfU5Os&amp;amp;t=115s&amp;amp;ab_channel=드림코딩&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=YuqB8D6eCKE&amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&quot;&gt;https://www.youtube.com/watch?v=YuqB8D6eCKE&amp;amp;ab_channel=우아한테크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://v3-docs.vuejs-korea.org/guide/scaling-up/ssr.html&quot;&gt;https://v3-docs.vuejs-korea.org/guide/scaling-up/ssr.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/HTML</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/162</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D-SSR-CSR#entry162comment</comments>
      <pubDate>Wed, 20 Aug 2025 16:19:13 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 렌더링 과정</title>
      <link>https://beomsic.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  브라우저 렌더링 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;렌더링&lt;/b&gt;이란 브라우저가 HTML, CSS, Javascript를 해석해 우리가 보는 화면이 실제로 나타나는 과정입니다.&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;브라우저가 HTML, CSS, Javascript를 화면에 나타내는 과정은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z4zeA/btsPZakoCA2/prWP94fhKrBPGFMP40bb1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z4zeA/btsPZakoCA2/prWP94fhKrBPGFMP40bb1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z4zeA/btsPZakoCA2/prWP94fhKrBPGFMP40bb1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ4zeA%2FbtsPZakoCA2%2FprWP94fhKrBPGFMP40bb1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;418&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ HTML 파싱 및 DOM 트리 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 브라우저는 HTML 파일을 읽고 해석하여 &lt;span style=&quot;color: #ee2323;&quot;&gt;DOM (Document Object Model)&lt;/span&gt; 트리를 구성합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;HTML 파싱&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;웹 브라우저가 서버에서 받은 HTML 문서를 문자열로 받아 이를 파싱하여 이해 가능한 구조로 변환하는 과정&lt;/li&gt;
&lt;li&gt;브라우저는 HTML 문서를 위에서 아래로 읽어 태그와 내용을 파악하고 각 요소의 의미와 관계를 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  DOM Tree&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;HTML 문서의 계층적 구조를 표현한 객체 모델&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;브라우저가 HTML로 작성된 여러 요소들을 Javascript가 이해하고 조작할 수 있도록 변환시킨 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, HTML 문서를 객체로 표현해 Javascript 같은 스크립트 및 프로그래밍 언어가 웹 페이지에 접근할 수 있습니다.&lt;/p&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;DOM&lt;/b&gt;은 &lt;b&gt;HTML&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;HTML - DOM 트리 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
      &amp;lt;div class=&quot;div&quot;&amp;gt;
          &amp;lt;div class=&quot;first&quot;&amp;gt;hello&amp;lt;/div&amp;gt;
          &amp;lt;div class=&quot;second&quot;&amp;gt;world&amp;lt;/div&amp;gt;
          &amp;lt;div class=&quot;third&quot;&amp;gt;!&amp;lt;/div&amp;gt;
    &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;이 HTML을 DOM 트리 형태의 그림으로 나타내면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7s7Mh/btsPZVfYLQb/ufywJKu0afshJrXd12ZI5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7s7Mh/btsPZVfYLQb/ufywJKu0afshJrXd12ZI5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7s7Mh/btsPZVfYLQb/ufywJKu0afshJrXd12ZI5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7s7Mh%2FbtsPZVfYLQb%2FufywJKu0afshJrXd12ZI5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;486&quot; height=&quot;440&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;862&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;이렇게 DOM 트리는 HTML 문서를 계층적으로 표현한 트리구조입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 문서의 각 요소(태그), 속성, 텍스트(내용)은 DOM 트리에서 하나의 노드로 표현됩니다.&lt;/li&gt;
&lt;li&gt;이를 통해 부모-자식, 형제 노드간의 관계를 파악해 요소를 탐색하거나 수정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;♂️ DOM 생성 과정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbjlJO/btsPYBP5XfX/u5z0wIW8vLu5JLmcwCAQNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbjlJO/btsPYBP5XfX/u5z0wIW8vLu5JLmcwCAQNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbjlJO/btsPYBP5XfX/u5z0wIW8vLu5JLmcwCAQNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbjlJO%2FbtsPYBP5XfX%2Fu5z0wIW8vLu5JLmcwCAQNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;353&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt;바이트(Bytes) &amp;rarr; 문자(Characters)&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;브라우저가 서버로부터 HTML 문서를 &lt;b&gt;바이트(Bytes)&lt;/b&gt; 형태로 전달받습니다.&lt;/li&gt;
&lt;li&gt;이 바이트들을 인코딩(예: UTF-8) 정보에 따라 개별 문자(Characters)로 변환합니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;meta 태그의 charset 어트리뷰트로 지정해둔 인코딩 방식&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;문자(Characters) &amp;rarr; 토큰(Tokens)&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;문자열을 HTML 사양에 정의된 토큰(Tokens)으로 분해합니다.&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;토큰(Tokens) &amp;rarr; 노드(Nodes)&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;토큰들이 계층적인 구조를 가진 노드(Nodes)로 변환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;ex) &amp;lt;p class=&quot;title&quot;&amp;gt;Hello, World!&amp;lt;/p&amp;gt;
----
&amp;lt;p&amp;gt; 노드
class 속성 노드
&quot;title&quot; 속성 값 노드
&quot;Hello World!&quot; 텍스트 노드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;b&gt;노드(Nodes) &amp;rarr; DOM 트리(DOM Tree)&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;를 형성하며 하나의 DOM 트리(DOM Tree)를 구성합니다.&lt;/li&gt;
&lt;li&gt;이 트리의 가장 위에는 &lt;code&gt;document&lt;/code&gt; 객체가 존재하며 그 아래에 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 요소가 루트 노드로 위치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  HTML 파싱 도중 script 태그를 만나면??&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXkf5b/btsPXKtcyAk/7PM1r6m2yiJMR2OPufkSYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXkf5b/btsPXKtcyAk/7PM1r6m2yiJMR2OPufkSYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXkf5b/btsPXKtcyAk/7PM1r6m2yiJMR2OPufkSYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXkf5b%2FbtsPXKtcyAk%2F7PM1r6m2yiJMR2OPufkSYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;298&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 웹 페이지는 HTML 파서에 의해 DOM으로 변환됩니다.&lt;/p&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;script&lt;/code&gt; 태그를 만나면 어떻게 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; 파서는 DOM 생성을 중지하고 자바스크립트 엔진이 script에 정의된 파일 및 코드를 실행하고 스크립트 실행이 완료되면 다시 HTML 파서가 DOM 생성을 다시 시작합니다.&lt;/p&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;script&lt;/code&gt; 태그는 HTML 파싱에 &lt;b&gt;블로킹(blocking)&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;code&gt;script&lt;/code&gt; 태그를 만나면 현재까지 파싱한 HTML 문서의 DOM 구조가 변경될 수 있다고 가정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) &lt;code&gt;document.write()&lt;/code&gt; &amp;rarr; DOM 수정&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;즉, 브라우저는 동기적으로 HTML과 JavaScript를 처리하기 때문에 &lt;code&gt;script&lt;/code&gt; 태그의 위치에 따라 DOM 생성이 느려질 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로 &lt;code&gt;script&lt;/code&gt;태그 는 HTML 문서 하단에 넣어 HTML 파싱이 완료된 후에 스크립트가 실행되도록 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM 생성이 지연되지 않고 스크립트에 DOM으로 접근하는 코드가 있다면 제대로 실행되지 않기 때문입니다.&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;반면, link 태그 (CSS 파일)와 style 태그 (내부 CSS)는 script 태그와 다르게 &lt;b&gt;HTML 파싱을 차단하지 않습니다&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;브라우저는 CSS 파일을 다운로드하면서 동시에 HTML 파싱을 계속 진행합니다.&lt;/li&gt;
&lt;li&gt;CSS 파일이 다운로드되면 브라우저는 이를 파싱하여 CSSOM을 생성합니다.&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;2️⃣&amp;nbsp;CSS 파싱 및 CSSOM 트리 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML을 파싱한 후 DOM 트리를 생성했다면 브라우저는 CSS 파일을 읽어 CSSOM(CSS Objeect Model) 트리를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;CSSOM&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;브라우저가 CSS를 파싱해 트리구조로 표현한 객체모델&lt;/li&gt;
&lt;li&gt;HTML 문서의 각 요소에 적용된 CSS 스타일 규칙을 나타내고 DOM과 결합해 화면에 표현됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;nbsp;CSS - CSSOM 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;.body {
    padding: 10px;
}
.first {
    font-size: 10px;
}
.second {
    color: blue;
}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnMStS/btsPYHCE6u6/DSdtNGFuxMwDIqB85e1Ehk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnMStS/btsPYHCE6u6/DSdtNGFuxMwDIqB85e1Ehk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnMStS/btsPYHCE6u6/DSdtNGFuxMwDIqB85e1Ehk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnMStS%2FbtsPYHCE6u6%2FDSdtNGFuxMwDIqB85e1Ehk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;341&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣&amp;nbsp;렌더 트리 형성&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Style Computing&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;CSSOM 을 이용해 계산된 스타일 객체를 생성&lt;/li&gt;
&lt;li&gt;DOM을 순회하며 CSSOM의 셀렉터와 노드를 매칭하여 노드의 스타일을 확정&lt;/li&gt;
&lt;li&gt;하나의 객체로 구조화 &amp;rArr; 개선된 스타일 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM Tree와 CSSOM Tree가 만들어졌으면 그 다음으로는 이 둘을 이용하여 Render Tree를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수한 요소들의 구조와 텍스트만 존재하는 DOM Tree와는 달리 Render Tree에는&amp;nbsp;&lt;b&gt;스타일 정보가 설정&lt;/b&gt;되어 있으며&amp;nbsp;&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;DOM 트리 + 계산된 스타일 객체&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;table style=&quot;border-collapse: collapse; width: 100%; height: 48px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;display:none&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;CSS spec에서 레이아웃을 생성하지 않도록 규정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;&amp;lt;input type=&amp;rdquo;hidden&amp;rdquo; /&amp;gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;display:none 이 적용되어 있는 것으로 간주&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;&amp;lt;head /&amp;gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;실제로 화면에 그려지는 시각적 요소가 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lCMBs/btsPWV24vjA/7YeKLRloACdTtGYYSjyveK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lCMBs/btsPWV24vjA/7YeKLRloACdTtGYYSjyveK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lCMBs/btsPWV24vjA/7YeKLRloACdTtGYYSjyveK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlCMBs%2FbtsPWV24vjA%2F7YeKLRloACdTtGYYSjyveK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;211&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣&amp;nbsp;Layout&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리가 생성되면 브라우저는 각 요소의 위치와 크기를 계산하는 레이아웃 단계에 들어가게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면에 표시될 상자 크기를 결정&lt;/li&gt;
&lt;li&gt;렌더 트리를 순회하며 노드의 너비, 높이, 위치등을 결정&lt;/li&gt;
&lt;/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;5️⃣&amp;nbsp;Paint &amp;amp; Rasterization&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;Paint&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리를 순회하며 노드별 페이팅 정보를 담은 &lt;b&gt;페인트 레코드&lt;/b&gt; 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️&amp;nbsp;&lt;b&gt;Rasterization&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;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;페인트 레코드&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 화면에 그리기 위한 명령&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃 단계에서 계산된 요소들의 위치와 크기, 계산된 스타일이 적용된 렌더 트리를 이용해 스타일 속성을 시각적으로 표현합니다.(텍스트, 색, 그림자, 효과 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리해야 하는 스타일이 복잡할수록 소요되는 시간이 늘어나게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) &amp;nbsp;background-color의 경우 속도가 빠르지만 그라데이션이나 그림자 효과등은 painting 시간이 더 오래 걸립니다.&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;6️⃣&amp;nbsp;Compositing&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페인트 과정에서 생성된 여러 개의 레이어들은 컴포지팅 단계에서 하나의 화면으로 조합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 각 레이어를 순서대로 합성하여 화면에 정확히 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8VDuQ/btsPZWsrklK/6uZEzKeQrL00NNC21TpdN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8VDuQ/btsPZWsrklK/6uZEzKeQrL00NNC21TpdN1/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=heoTDzJx_Gw&amp;amp;amp;t=319s&amp;amp;amp;ab_channel=우아한테크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8VDuQ/btsPZWsrklK/6uZEzKeQrL00NNC21TpdN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8VDuQ%2FbtsPZWsrklK%2F6uZEzKeQrL00NNC21TpdN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;243&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=heoTDzJx_Gw&amp;amp;t=319s&amp;amp;ab_channel=우아한테크&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;⚠️&amp;nbsp;자바스크립트가 렌더링에 미치는 영향&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️&amp;nbsp;DOM 을 조작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 DOM을 조작해 화면의 요소를 수정하거나 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 조작 작업은 브라우저의 레이아웃과 페인트 단계를 반복하도록 해 성능에 영향을 미칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;Reflow&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이아웃 단계에서 발생하는 작업으로 요소의 크기, 위치, 레이아웃 등이 변경될 때 브라우저가 전체 레이아웃을 다시 계산하는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;reflow() {
  document.getElementById(&quot;content&quot;).style.width = &quot;100px&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;위와 같은 코드가 실행되면 해당 요소의 크기가 변경되면서 reflow가 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 해당 요소뿐만 아니라 그와 관련된 부모, 자식 요소들까지 다시 계산&lt;/li&gt;
&lt;li&gt;여러 요소에 대한 레이아웃을 재계산해야 하므로 브라우저가 처리해야 할 연산, 복잡도가 증가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;Repaint&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;reflow에 비해는 덜 복잡하지만 많이 발생하면 성능 저하 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐&amp;nbsp;따라서 DOM 조작이 필요할 경우에는 이런 작업을 최적화해 reflow, repaint의 발생을 최소화하는 것이 중요!!!&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; &amp;nbsp;스크립트 실행 차단 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 말한 것처럼 자바스크립트는 브라우저의 HTML 파싱 단계와 렌더 트리 생성단계를 차단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML을 파싱하면서 &amp;lt;script&amp;gt; 태그를 만나면 해당 스크립트를 동기적으로 실행하여 해당 스크립트를 다운로드하고 실행할 때까지 HTML 파싱을 중단하여 렌더 트리 생성이 지연되고 렌더링이 늦어지는 원인이 됩니다.&lt;/p&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;b&gt;&amp;lt;script&amp;gt;&lt;/b&gt; 태그의 &lt;b&gt;async&lt;/b&gt;, &lt;b&gt;defer&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;b&gt;async&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script async src=&quot;script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async 속성을 사용하면 스크립트를 비동기적으로 로드하기 때문에 HTMl 파싱과 동시에 스크립트를 다운로드 및 로드를 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 스크립트가 로딩되는 즉시 스크립트를 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIMVdF/btsPWsmfORn/Zg3rDDCh8cZUgDms1mRJtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIMVdF/btsPWsmfORn/Zg3rDDCh8cZUgDms1mRJtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIMVdF/btsPWsmfORn/Zg3rDDCh8cZUgDms1mRJtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIMVdF%2FbtsPWsmfORn%2FZg3rDDCh8cZUgDms1mRJtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;191&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️&amp;nbsp;&lt;b&gt;async&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;b&gt;defer&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;script defer src=&quot;script.js&quot;&amp;gt;&amp;lt;/script&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;b&gt;defer&lt;/b&gt;는 &lt;b&gt;async&lt;/b&gt;와 달리 스크립트를 &lt;b&gt;비동기적&lt;/b&gt;으로 로드해 HTML 파싱이 완료된 후 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwsyrW/btsPWnZvYxa/1qEWGV1ztwpTnJj4Rjujzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwsyrW/btsPWnZvYxa/1qEWGV1ztwpTnJj4Rjujzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwsyrW/btsPWnZvYxa/1qEWGV1ztwpTnJj4Rjujzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwsyrW%2FbtsPWnZvYxa%2F1qEWGV1ztwpTnJj4Rjujzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;209&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;async&lt;/b&gt; 와 달리 여러 스크립트가 있을 때 순서대로 실행되고 HTML 파싱과 스크립트 로드가 동시에 실행되어 렌더링 지연을 최소화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;async&lt;/b&gt;와 &lt;b&gt;defer&lt;/b&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; &amp;nbsp;렌더링 성능 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣&amp;nbsp;&lt;b&gt;리플로우와 리페인트 줄여보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플로우와 리페인트가 빈번하게 발생하면 CPU와 GPU에 높은 부하가 발생해 프레인 속도가 저하될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 스타일 변경을 최소화하는 것이 중요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 스타일 속성을 한 번에 변경해야 하는 경우 스타일을 모아서 변경하거나 class를 추가해 스타일을 조정&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;2️⃣&amp;nbsp;&lt;b&gt;이미지와 리소스 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지는 파일 크기가 커 로딩 속도에 큰 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 보지 않는 이미지를 미리 로드하게 되면 불필요한 네트워크 요청과 자원 소비로 인해 페이지 로딩 속도가 느려질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;Lazy Loading&lt;/span&gt;을 사용해 해결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;image.jpg&quot; alt=&quot;sample image&quot; loading=&quot;lazy&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 태그에 loading=&amp;rdquo;lazy&amp;rdquo; 속성을 추가해 브라우저는 사용자가 해당 이미지를 볼 때 이미지를 로드하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 네트워크 요청을 줄이고 초기 렌더링 속도를 높여 사용자 경험을 개선할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣&amp;nbsp;&lt;b&gt;CSS와 JS 로드 방식 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS, JS 파일 로드되고 실행되는 방식도 페이지 렌더링 성능에 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS는 HTML 문서를 파싱하기전에 로드되어야 하기 때문에 CSS 파일은 &lt;span style=&quot;color: #ee2323;&quot;&gt;rel = &amp;ldquo;stylesheet&amp;rdquo; &lt;/span&gt;속성을 통해 &amp;lt;head&amp;gt; 태그 내부에 포함되도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/dom&quot;&gt;https://docs.tosspayments.com/resources/glossary/dom&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/2909/&quot;&gt;https://yozm.wishket.com/magazine/detail/2909/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.dev/learn/performance/understanding-the-critical-path?hl=ko&quot;&gt;https://web.dev/learn/performance/understanding-the-critical-path?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=heoTDzJx_Gw&amp;amp;t=319s&amp;amp;ab_channel=우아한테크&quot;&gt;https://www.youtube.com/watch?v=heoTDzJx_Gw&amp;amp;t=319s&amp;amp;ab_channel=우아한테크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://boxfoxs.tistory.com/408&quot;&gt;https://boxfoxs.tistory.com/408&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=TZz9VHjJzMk&amp;amp;ab_channel=우아한테크&quot;&gt;https://www.youtube.com/watch?v=TZz9VHjJzMk&amp;amp;ab_channel=우아한테크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/2909/&quot;&gt;https://yozm.wishket.com/magazine/detail/2909/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>FE/HTML</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/161</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95#entry161comment</comments>
      <pubDate>Tue, 19 Aug 2025 22:46:18 +0900</pubDate>
    </item>
    <item>
      <title>Express.js</title>
      <link>https://beomsic.tistory.com/entry/Expressjs</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  Node.js&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Node.js 란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node.js&lt;/b&gt;는 &lt;b&gt;V8 자바스크립트 엔진&lt;/b&gt;으로 빌드된 &lt;b&gt;자바스크립트 런타임 환경&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js는 웹 브라우저 밖에서 자바스크립트를 실행할 수 있게 해주는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;비동기 I/O 처리에 강점이 있어 실시간 통신이 필요한 웹 서버나 네트워크 애플리케이션을 구축하는 데 주로 사용됩니다.&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;  Package.json 파일 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;은 프로젝트의 &lt;b&gt;메타데이터&lt;/b&gt;와 &lt;b&gt;의존성&lt;/b&gt;(dependencies)을 관리하는 파일입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트에서 어떤 패키지들을 사용하는지 프로젝트 이름과 버전은 무엇인지 등을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;my-test-app&quot;, 
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;A simple application.&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node index.js&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;beomsic&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.18.2&quot;
  }
}&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;code&gt;name&lt;/code&gt;: 프로젝트의 이름&lt;/li&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt;: 프로젝트의 버전&lt;/li&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;: 프로젝트에 대한 설명&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt;: 프로젝트의 진입점(entry point) 파일&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts&lt;/code&gt;: 프로젝트에서 실행할 수 있는 명령어들을 정의합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 npm start를 실행하면 node index.js 명령어가 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependencies&lt;/code&gt;: 프로젝트가 실행될 때 필요한 패키지들(라이브러리)을 나열합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  package.json과 package-lock.json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json은 모듈의 버전을 정의시 '~4.xx.1' 또는 '^2.xx.0' 같은 &lt;b&gt;version range&lt;/b&gt;를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package-lock.json은 정확한 버전을 명시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  version range&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버전의 이상, 미만 등을 나타낼 수 있는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  package-lock.json은 왜 필요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json에 명확한 버전을 입력하면 package-lock.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;⚠️ package.json에 항상 명확한 버전을 입력한다면 모든 개발자의 환경에서 버전 업데이트가 일어날 때마다 npm install을 해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 조금씩 다른 버전은 pacakge.json의 version range 이점을 사용해 개발하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 문제로 큰 버그가 생긴다면 package-lock.json으로 버전을 명확히 일치시켜 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package-lock.json이 존재한다면, npm install 시 packag.json이 아닌 package-lock.json의 버전으로 install&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;zwj;  npm(Node Package Manager) 이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;npm&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;Node.js 프로젝트에 필요한 다양한 모듈(패키지)을 설치, 관리, 삭제하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 세계 개발자들이 만든 수많은 패키지들을 npm 레지스트리에서 다운로드하여 손쉽게 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운 받은 모듈들은 &lt;code&gt;./node\_modules&lt;/code&gt; 에 설치&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;  Express.js&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Express.js&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;Node.js가 Javascript를 서버에서 실행할 수 있게 해주는 런타임 환경이라면 Express.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;복잡한 서버 로직을 간결하게 처리할 수 있도록 HTTP 요청 처리, 라우팅, 미들웨어 등 다양한 기능을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;express는 http 모듈을 이용하여 웹 서버를 구축하고 데이터를 표시하는 방식을 좀 더 추상화하여 웹 서비스나 웹 애플리케이션 개발을 더 편리하고 수월하게 하는 여러 API를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚙️ 설치&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm init -y&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;npm 초기화를 진행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;파일이 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm install -D express&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj;  Express.js 실행해보기&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();
const port = 3000;

app.get(&quot;/&quot;, (req, res) =&amp;gt; {
  res.send(&quot;Hello World!&quot;);
});

app.listen(port, () =&amp;gt; {
  console.log(port, &quot;번 포트에서 대기 중&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;$npx node express.js

-----------------
3000 번 포트에서 대기 중&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWIamT/btsPUhZyXN6/sJD6rpVN0jYuabssOCVbjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWIamT/btsPUhZyXN6/sJD6rpVN0jYuabssOCVbjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWIamT/btsPUhZyXN6/sJD6rpVN0jYuabssOCVbjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWIamT%2FbtsPUhZyXN6%2FsJD6rpVN0jYuabssOCVbjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;732&quot; height=&quot;166&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  미들웨어란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  미들웨어는 Express 의 핵심 개념입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미들웨어는 &lt;code&gt;요청과 응답 사이&lt;/code&gt;에 위치해 다양한 기능을 수행하는 함수들입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 요청과 응답에 대한 정보를 통해 필요한 처리를 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;미들웨어 함수 구성 요소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;req&lt;/code&gt;: 요청 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;res&lt;/code&gt;: 응답 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;next&lt;/code&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;code&gt;next()&lt;/code&gt;를 호출하지 않으면 요청-응답 주기가 중단됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next()&lt;/code&gt;: 다음 미들웨어 함수로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next('route')&lt;/code&gt;: 현재 라우트의 나머지 미들웨어를 건너뛰고 다음 라우터로 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;var express = require('express');
var app = express();

app.use(function (req, res, next) {
  req.requestTime = Date.now(); // req라는객체에 requestTime 키와 밸류를 래퍼로 등록. requestTime는 사용자가 정한 값이다.
  next(); // 다음 미들웨어 함수를 작동
});

app.get('/', function(req, res, next) {
  res.send(req.requestTime);
})

app.listen(3000);&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;code&gt;function(req, res, next) {}&lt;/code&gt; 부분이 바로 미들웨어&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.use()&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Express 에서 항상 실행하는 미들웨어 역할&lt;/li&gt;
&lt;li&gt;app.get(), app.post() 등과 달리 요청 URL을 지정하지 않아도 사용 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL에 상관없이 매번 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;사용 예시&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.use(미들웨어)&lt;/td&gt;
&lt;td&gt;모든 요청에서 해당 미들웨어 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.use(&amp;rsquo;/path&amp;rsquo;, 미들웨어)&lt;/td&gt;
&lt;td&gt;path로 시작하는 요청에서 미들웨어 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.post(&amp;rsquo;path&amp;rsquo;, 미들웨어)&lt;/td&gt;
&lt;td&gt;path로 시작하는 POST 요청에서 미들웨어 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;미들웨어의 역할&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 본문(body), 쿠키등의 데이터를 파싱&lt;/li&gt;
&lt;li&gt;요청에 대한 로깅을 수행&lt;/li&gt;
&lt;li&gt;인증(Authentication) 및 인가(Authorization) 처리&lt;/li&gt;
&lt;li&gt;오류(Error)를 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;에러처리 미들웨어&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express는 특별한 에러 처리 미들웨어를 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;app.use((err, req, res, next) =&amp;gt; {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러처리 미들웨어는 (err, req, res, next)와 같이 4개의 인수를 받는 함수로 정의됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 미들웨어 체인에서 오류가 발생하면 Express는 이 에러 처리 미들웨어를 호출하여 오류를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  본문 파싱 미들웨어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;express.urlencoded&lt;/code&gt;와 &lt;code&gt;express.json&lt;/code&gt; 미들웨어는 Express.js에서 요청 본문(body)의 데이터를 파싱하기 위해 사용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 보낸 데이터를 서버에서 사용할 수 있도록 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;express.urlencoded&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;app.use(express.urlencoded({ extended: true }));&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;HTML 폼을 통해 제출된 데이터를 파싱하는 미들웨어&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;code&gt;express.json&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;app.use(express.json());&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;application/json 형식으로 전송된 JSON 데이터를 파싱합니다&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;&lt;b&gt;라우팅&lt;/b&gt;은 클라이언트의 요청 URL에 따라 적절한 핸들러 함수를 연결하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express.js는 &lt;code&gt;app.get()&lt;/code&gt;, &lt;code&gt;app.post()&lt;/code&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;code&gt;app.get(path, handler)&lt;/code&gt;: 특정 path로 들어온 GET 요청 처리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app.post(path, handler)&lt;/code&gt;: 특정 path로 들어온 POST 요청 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// hello.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) =&amp;gt; {
    res.send('Hello, Router !');
});

module.exports = router;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;// express.js
const express = require(&quot;express&quot;);
const app = express();
const port = 3000;

const helloRouter = require(&quot;./hello&quot;);

app.use(&quot;/&quot;, helloRouter); // 경로에 미들웨어 장착

app.use((req, res, next) =&amp;gt; {
  // 다른곳 진입했을경우 실행
  res.status(404).send(&quot;Not Found&quot;);
});

app.listen(port, () =&amp;gt; {
  console.log(port, &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;b&gt;실행&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;$npx node express.js

----------

3000 번 포트에서 대기 중&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SWb5F/btsPYAis0a0/3DOeMpBAAWu3oOv1q9gYlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SWb5F/btsPYAis0a0/3DOeMpBAAWu3oOv1q9gYlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SWb5F/btsPYAis0a0/3DOeMpBAAWu3oOv1q9gYlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSWb5F%2FbtsPYAis0a0%2F3DOeMpBAAWu3oOv1q9gYlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;202&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;

&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@hbin12212/Express.js%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%84%9C%EB%B2%84-%EB%A7%8C%EB%93%A4%EA%B8%B0&quot;&gt;https://velog.io/@hbin12212/Express.js로-간단한-서버-만들기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@uxn00b/243&quot;&gt;https://brunch.co.kr/@uxn00b/243&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://expressjs.com/ko/guide/using-middleware.html&quot;&gt;https://expressjs.com/ko/guide/using-middleware.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://expressjs.com/ko/guide/error-handling.html&quot;&gt;https://expressjs.com/ko/guide/error-handling.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/EXPRESS-&quot;&gt;https://inpa.tistory.com/entry/EXPRESS-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/EXPRESS-%F0%9F%93%9A-%EC%9D%B5%EC%8A%A4%ED%94%84%EB%A0%88%EC%8A%A4-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0&quot;&gt; -익스프레스-설치-사용해보기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/EXPRESS-&quot;&gt;https://inpa.tistory.com/entry/EXPRESS-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/EXPRESS-%F0%9F%93%9A-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4-%F0%9F%92%AF-%EC%9D%B4%ED%95%B4-%EC%A0%95%EB%A6%AC&quot;&gt; -미들웨어- -이해-정리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://doozi0316.tistory.com/entry/Nodejs%EB%9E%80-NPM%EC%9D%B4%EB%9E%80-NVM%EC%9D%B4%EB%9E%80&quot;&gt;https://doozi0316.tistory.com/entry/Nodejs란-NPM이란-NVM이란&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Node.js</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/160</guid>
      <comments>https://beomsic.tistory.com/entry/Expressjs#entry160comment</comments>
      <pubDate>Mon, 18 Aug 2025 22:22:36 +0900</pubDate>
    </item>
    <item>
      <title>HTML 기본 정리</title>
      <link>https://beomsic.tistory.com/entry/HTML-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h1&gt;  HTML&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML (Hypertext Markup Language)&lt;br /&gt;- 웹 페이지와 그 내용을 구조화하기 위해 사용하는 코드&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML은 콘텐츠의 구조를 정의하는 &lt;b&gt;마크업 업어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마크업 언어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서의 구조와 서식을 표현하기 위해 사용되는 언어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태그등을 통해 문서나 데이터의 의미, 구조를 명시하는 언어.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML, XML, Markdown&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  HTML 기본 구조&lt;/h2&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;페이지 제목&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&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;모든 HTML 문서에서 가장 먼저 나와야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 페이지의 제목, 스타일(CSS), 스크립트(JavaScript) 등 &lt;b&gt;페이지에 보이지 않는 메타 정보&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&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;/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; ️ HTML 시맨틱 태그 (Semantic Tags)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Semantic&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;의미의, 의미론적인&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시맨틱 태그&lt;/b&gt;는 의미를 가진 태그로 포함된 콘텐츠의 목적이나 역할을 명확히 설명합니다.&lt;/p&gt;
&lt;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;&amp;lt;div&amp;gt;&lt;/code&gt;나 &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;처럼 의미가 없는 태그와 달리 시맨틱 태그를 사용해 문서의 구조와 의미를 더 쉽게 이해할 수 있습니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콘텐츠를 각 부분의 역할과 기능을 전달하는데 도움을 줍니다.&lt;/li&gt;
&lt;li&gt;좀 더 명시적이면서 직관적인 구조의 설계가 가능!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;시맨틱 태그 종류&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 186px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;HTML Semantic Tag&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;header&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;페이지의 머리글, 로고, 탐색 메뉴 등을 포함합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;nav&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;내비게이션(Navigation) 링크들을 모아 놓은 영역입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;main&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;문서의 주요 콘텐츠를 담는 영역으로 페이지당 한 번만 사용해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;article&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;독립적인 콘텐츠(예: 블로그 글, 뉴스 기사)를 나타냅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;section&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;관련 있는 콘텐츠들을 그룹화합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;aside&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;본문 콘텐츠와는 직접적인 관련이 적은 부가적인 콘텐츠(예: 광고, 사이드바)를 나타냅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;footer&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;페이지의 바닥글, 저작권 정보 등을 포함합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;lt;time&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;섹션이나 영역에 자동으로 날짜를 추가하는데 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ℹ️ HTML Form 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;HTML Form&lt;/code&gt;은 사용자로부터 데이터를 입력받기 위해 사용되는 요소들의 집합입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자와 웹사이트 / 애플리케이션이 서로 상호 작용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣&amp;nbsp; form 태그&lt;/h3&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;form name=&quot;test&quot; action=&quot;/test.html&quot; method=&quot;get&quot;&amp;gt;
  &amp;lt;input type=&quot;text&quot; name=&quot;id&quot;&amp;gt;
  &amp;lt;select&amp;gt;
    &amp;lt;option value=&quot;blue&quot;&amp;gt;first&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;blue&quot;&amp;gt;second&amp;lt;/option&amp;gt;
  &amp;lt;/select&amp;gt;
  &amp;lt;input type=&quot;submit&quot; value=&quot;Submit&quot;&amp;gt; 
&amp;lt;/form&amp;gt;&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;폼의 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;action&lt;/td&gt;
&lt;td&gt;폼 데이터를 전송할 서버의 URL 또는 html 링크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;method&lt;/td&gt;
&lt;td&gt;서버로 데이터를 전송할 HTTP Method 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;method&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;code&gt;GET&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL에 폼 데이터가 추가되어 전송됩니다.&lt;/li&gt;
&lt;li&gt;간단한 검색이나 데이터 요청에 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 메시지 본문에 폼 데이터가 담겨 전송됩니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ input 태그&lt;/h3&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;form&amp;gt;
  &amp;lt;input type=&quot;text&quot; name=&quot;firstname&quot; maxLength=&quot;3&quot; required&amp;gt;&amp;lt;br&amp;gt; 
  &amp;lt;input type=&quot;text&quot; name=&quot;lastname&quot;&amp;gt;&amp;lt;br&amp;gt;
  &amp;lt;input type=&quot;text&quot; name=&quot;alias&quot; placeholder=&quot;없을시 생략&quot;&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;&lt;b&gt;input 태그 속성&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;code&gt;type&lt;/code&gt; ⭐ : input 태그의 타입 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxLength&lt;/code&gt;: 텍스트 필드에 최대로 입력할 수 있는 문자 개수 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;placeholder&lt;/code&gt;: 입력 필드에 사용자가 입력해야 할 내용을 힌트처럼 보여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&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;type 속성 종류&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;한 줄짜리 일반 텍스트 입력 필드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;입력 내용이 가려지는 비밀번호 입력 필드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;checkbox&lt;/td&gt;
&lt;td&gt;여러 개를 선택할 수 있는 체크박스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;radio&lt;/td&gt;
&lt;td&gt;여러 옵션 중 하나만 선택할 수 있는 라디오 버튼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;submit&lt;/td&gt;
&lt;td&gt;폼을 제출하는 버튼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;td&gt;파일을 선택할 수 있는 버튼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣&amp;nbsp; textarea 태그&lt;/h3&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/test.html&quot; method=&quot;get&quot;&amp;gt;
    &amp;lt;textarea name=&quot;opinion&quot; cols=&quot;30&quot; rows=&quot;5&quot; placeholder=&quot;작성!&quot;&amp;gt;
    &amp;lt;/textarea&amp;gt;
    &amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;submit&quot;&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textarea 요소는 input 요소의 &quot;text&quot; 타입과 비슷한 형태이지만 &quot;&lt;b&gt;text&lt;/b&gt;&quot; 타입의 input 요소는 한 줄만 입력이 가능하지만 &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 태그는 여러 줄의 긴 내용을 입력하는 것이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣&amp;nbsp; button 태그&lt;/h3&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;button type=&quot;button&quot; onclick=&quot;...&quot;&amp;gt;
   Click!
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지에 버튼을 넣고 form을 전송하거나 리셋할 때 사용&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;button type 속성&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;submit&lt;/td&gt;
&lt;td&gt;form 제출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reset&lt;/td&gt;
&lt;td&gt;form 리셋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;button&lt;/td&gt;
&lt;td&gt;버튼의 형태 &amp;rarr; 함수를 연결해 눌렸을 때 다른 동작을 하도록 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  class 와 id&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class와 id는 HTML 요소에 이름을 부여하여 CSS로 스타일을 적용하거나 JavaScript로 특정 요소를 제어할 때 사용됩니다.&lt;/p&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;id&lt;/b&gt;: 문서 내에서 &lt;b&gt;유일한&lt;/b&gt; 식별자입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 페이지에 동일한 id는 단 하나만 존재해야 합니다.&lt;/li&gt;
&lt;li&gt;예시:&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;class&lt;/b&gt;: 여러 요소에 &lt;b&gt;동일한&lt;/b&gt; 이름을 부여할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 요소에 같은 스타일을 적용할 때 주로 사용됩니다.&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;&amp;lt;p class=&quot;highlight&quot;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;div class=&quot;card&quot;&amp;gt;&lt;/code&gt;&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;HTML 접근성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTML 접근성&lt;/b&gt;은 장애를 가진 사용자를 포함하여 모든 사용자가 웹 콘텐츠에 쉽게 접근하고 이용할 수 있도록 하는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시각 장애 사용자를 위한 스크린 리더, 키보드만 사용하는 사용자를 위한 기능 등을 고려해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시맨틱 태그 사용&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;alt&lt;/b&gt; &lt;b&gt;속성&lt;/b&gt;: 이미지에 대한 설명을 제공하여 시각 장애 사용자가 이미지의 내용을 파악할 수 있게 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시: &lt;code&gt;&amp;lt;img src=&quot;dog.jpg&quot; alt=&quot;귀여운 강아지가 풀밭에서 놀고 있는 사진&quot;&amp;gt;&lt;/code&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;aria&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;label&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;code&gt;&amp;lt;label for=&quot;name&quot;&amp;gt;이름&amp;lt;/label&amp;gt;&amp;lt;input type=&quot;text&quot; id=&quot;name&quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Accessibility/HTML&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Accessibility/HTML&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Semantics&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/Semantics&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/Accessibility/ARIA/Reference/Attributes&quot;&gt;https://developer.mozilla.org/ko/docs/Web/Accessibility/ARIA/Reference/Attributes&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mailchimp.com/resources/html-semantic-elements-and-webflow-the-essential-guide/&quot;&gt;https://mailchimp.com/resources/html-semantic-elements-and-webflow-the-essential-guide/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/HTML-&quot;&gt;https://inpa.tistory.com/entry/HTML-&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/HTML-%F0%9F%93%9A-%ED%8F%BCForm-%ED%83%9C%EA%B7%B8-%EC%A0%95%EB%A6%AC&quot;&gt; -폼Form-태그-정리&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;</description>
      <category>FE/HTML</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/159</guid>
      <comments>https://beomsic.tistory.com/entry/HTML-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A6%AC#entry159comment</comments>
      <pubDate>Mon, 18 Aug 2025 22:11:20 +0900</pubDate>
    </item>
    <item>
      <title>네이버 부스트캠프 웹・모바일 10기 챌린지 후기</title>
      <link>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-10%EA%B8%B0-%EC%B1%8C%EB%A6%B0%EC%A7%80-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  챌린지에 들어가며..&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVMiIQ/btsPQwIOMtn/qDaL7d6unGQOoSDV4BhH2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVMiIQ/btsPQwIOMtn/qDaL7d6unGQOoSDV4BhH2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVMiIQ/btsPQwIOMtn/qDaL7d6unGQOoSDV4BhH2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVMiIQ%2FbtsPQwIOMtn%2FqDaL7d6unGQOoSDV4BhH2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;412&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 부스트캠프 챌린지에 합류했을 때 저는 단순히 &lt;b&gt;&amp;lsquo;더 잘하는 개발자&amp;rsquo;&lt;/b&gt;가 되는 것보다 &lt;b&gt;&amp;lsquo;어떻게 배우고 성장할 것인가&amp;rsquo;&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;lsquo;겉핥기&amp;rsquo;에 그치고 있다는 생각이 들었고 &amp;lsquo;보여주기 위한 개발&amp;rsquo;을 하고 있다는 느낌이 계속 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 부스트캠프 과정을 통해 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;함께 성장하는 경험&lt;/span&gt;&lt;/b&gt;을 되찾고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자서는 놓치기 쉬운 문제의 본질을 동료와 함께 고민하고 단순히 코드를 작성하는 것을 넘어 &lt;b&gt;'왜' 이 코드가 필요한지&lt;/b&gt;를 고민하는 개발자로 성장하고 싶다는 간절한 바람을 안고 챌린지를 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  챌린지&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wCrc9/btsPQEUHYQP/ndz5Qx7ht2saGBZZ4STJk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wCrc9/btsPQEUHYQP/ndz5Qx7ht2saGBZZ4STJk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wCrc9/btsPQEUHYQP/ndz5Qx7ht2saGBZZ4STJk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwCrc9%2FbtsPQEUHYQP%2Fndz5Qx7ht2saGBZZ4STJk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;347&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;656&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;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;챌린지(Challenge)&lt;/b&gt;&lt;/span&gt;: 안전한 학습에서 벗어나 진짜 성장을 마주한 4주간의 이야기&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부스트캠프에 합류하기 전까지 저는 '챌린지'라는 단어를 단순히 어려움 미션에 도전하고 해결하는 것으로만 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 4주간의 과정을 거치며 그 단어에 담긴 진짜 의미를 이제야 이해하게 된 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4주간의 시간은 제가 유지해왔던 '안전한 학습 방식'에 대한 도전을 했던 시간이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학교에서 배운 지식, 그동안 혼자 학습한 CS 이론들이 실제 문제를 해결하는 상황 앞에서는 얼마나 단편적이고 표면적인지 깨달았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챌린지는 저에게 단순히 '새로운 것을 배우는' 과정이 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 &lt;b&gt;제가 알고 있다고 착각했던 것들을 진짜로 아는지 확인하는 과정&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일매일이 작은 도전의 연속이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 배운 내용을 미션에 곧바로 적용해야 했고 혼자 끙끙대던 문제를 동료들 앞에서 설명해야 하는 순간들도 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 알고 있다고 생각했던 CS 지식들이 막상 미션으로 주어지자 &lt;code&gt;이걸 알려면 저것도 알아야 하는데...&lt;/code&gt;라는 꼬리에 꼬리를 무는 학습의 연속에 빠졌고 결국 &lt;code&gt;내가 뭘 학습한 거지?&lt;/code&gt; 라는 혼란에 도달하기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 설정한 체크포인트의 절반도 채우기 어려운 날들이 많았고 처음에는 좌절도 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 무리하게 많은 기능을 구현하기보다 &lt;b&gt;하나를 만들더라도 의미 있게 만드는 것에 집중&lt;/b&gt;하기 시작하면서 &lt;code&gt;완벽함&lt;/code&gt;이라는 압박감에서 벗어나 &lt;code&gt;성장&lt;/code&gt;이라는 목표에 집중할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챌린지라는 이름이 주는 부담감 속에서도 저만의 속도로 나아가며 그 의미를 온전히 깨닫게 된 소중한 경험이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실패와 좌절을 통한 나의 변화&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Iwuk5/btsPS4REEK3/CEHRX0Qi1d7qI6Wz7TrLhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Iwuk5/btsPS4REEK3/CEHRX0Qi1d7qI6Wz7TrLhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Iwuk5/btsPS4REEK3/CEHRX0Qi1d7qI6Wz7TrLhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIwuk5%2FbtsPS4REEK3%2FCEHRX0Qi1d7qI6Wz7TrLhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;429&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &quot;아는 것&quot;과 &quot;알고 있다고 착각하는 것&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챌린지 기간 동안 저는 &lt;b&gt;'아는 것'과 '알고 있다고 착각하는 것'의 차이&lt;/b&gt;를 몸소 깨달았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 미션을 수행하며 CS 지식을 어느 정도 안다고 생각했지만 막상 직접 구현하려니 기본적인 문제들부터 막히는 경험을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 저는 이론으로만 알던 내용들이 실제 구현 과정에서는 &lt;b&gt;&quot;어떻게 활용되는지?&quot;&lt;/b&gt;, &lt;b&gt;&quot;어떤 의미인지?&quot;&lt;/b&gt;, &lt;b&gt;&quot;왜 필요한지?&quot;&lt;/b&gt; 등을 깊이 고민하고 있지 않았다는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 미션을 구현하며 '아, 이래서 이런 설계가 필요하구나'라는 깨달음을 얻는 &lt;b&gt;'&lt;span style=&quot;color: #ee2323;&quot;&gt;Learning By Doing&lt;/span&gt;'&lt;/b&gt; 을 체감할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 초반에는 모든 것을 완벽하게 이해하고 시작하려 했습니다. 하나를 배우려 하면 그것과 연결된 여러 가지를 더 알아야 할 것 같았고 결국 아무것도 제대로 해내지 못하는 느낌을 받기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 미션을 구현하는 과정에서 부족한 부분을 발견하고 이를 채워나가는 것이 훨씬 효과적인 학습 방식이라는 것을 깨달은 후 완벽해야 한다는 강박을 내려놓을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  혼자서는 불가능했을 관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 미션을 수행하고 피드백 시간에서 한 동료가 라이브러리를 가져다 사용하는 것이 아닌 기능을 직접 구현하는 걸 봤을 때의 충격이 아직도 생생합니다.&lt;/p&gt;
&lt;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;&lt;span style=&quot;color: #009a87;&quot;&gt;왜?&lt;/span&gt;라는 질문을 얼마나 깊이 파고드는가&lt;/b&gt; 라는 것이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 챌린지 기간 동안 &lt;code&gt;동료&lt;/code&gt;와 &lt;code&gt;AI&lt;/code&gt;라는 두 파트너가 저의 성장을 도왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;동료와 함께하며 배운 '관점의 확장'&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동료들과 함께 다양한 접근 방식과 생각을 공유하면서 사고는 크게 확장되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;AI와 함께하며 배운 '되돌아보기'&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;이전에는 AI를 단순한 답을 얻기 위한 도구로만 사용했습니다.&lt;/li&gt;
&lt;li&gt;하지만 챌린지 과정에서 AI를 &lt;b&gt;'대화'를 통해 함께 고민하고 성장하는 파트너&lt;/b&gt;로 사용해보려고 했습니다.&lt;/li&gt;
&lt;li&gt;제 생각을 정리하도록 도와주고 스스로 문제를 더 명확하게 바라볼 수 있게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경험들을 통해 저는 '내가 아직 잘 모르는 것이 많다'는 것을 겸허히 받아들이고 이를 해결해 나가는 과정 자체에 재미를 느끼게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;개발자의 '&lt;span style=&quot;color: #ee2323;&quot;&gt;문제 해결력&lt;/span&gt;'에 대한 새로운 정의&lt;/b&gt;를 생각해볼 수 있는 시간이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 단순히 CS 지식과 뛰어난 개발, 코딩 능력이 문제 해결력이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이제는 그것이 전부가 아니라는 것을 깨달았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챌린지에서 제가 배운 진짜 문제 해결력은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;문제 분석 및 구조화&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주어진 문제를 분석하고 한 번에 해결하려 하기보다 작은 단위로 쪼개고 우선순위를 정해 학습하고 구현.&lt;/li&gt;
&lt;li&gt;이를 통해 &amp;lsquo;내가 해야하는 작업이 무엇인지&amp;rsquo; '지금 막힌 지점이 어디인지'를 명확하게 파악하고 해결할 수 있는 능력&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;2️⃣ &lt;b&gt;기록과 정리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lsquo;어떤 방식으로 설계했는지&amp;rsquo;, &amp;lsquo;어떤 고민을 했는지&amp;rsquo; 를 기록&lt;/li&gt;
&lt;li&gt;자신만의 언어로 해결과정과 문제 상황을 구체화하고 동료에게 제 생각을 논리적으로 설명하는 능력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;소통을 통한 해결&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혼자서는 해결하지 못하는 문제를 동료와의 소통과 AI와의 대화를 통해 해결.&lt;/li&gt;
&lt;li&gt;동료의 피드백, 슬랙에서의 다양한 의견 공유등을 통해 적극적으로 문제해결을 할 수 있는 능력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 챌린지 과정은 &lt;code&gt;문제해결력&lt;/code&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;  챌린지를 마무리하며&amp;hellip;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VHQO6/btsPQDO4jT8/NJo3mI6mIFiYjKK3DqXxHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VHQO6/btsPQDO4jT8/NJo3mI6mIFiYjKK3DqXxHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VHQO6/btsPQDO4jT8/NJo3mI6mIFiYjKK3DqXxHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVHQO6%2FbtsPQDO4jT8%2FNJo3mI6mIFiYjKK3DqXxHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;271&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챌린지 기간 동안 모든 것이 완벽하진 않았다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 '내가 지금 왜 이걸 하고 있지?'를 스스로에게 끊임없이 물으며 저만의 기준과 학습 방식을 찾아가고자 했고 동료들의 뛰어난 학습 방식, 정리, 구현 코드를 보며 자극받고 AI와 함께 고민하며 사고를 확장하는 경험을 할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4주 전 챌린지를 시작하며 목표했던 내용들을 되돌아보면 어느 정도 답을 찾은 것 같습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;'어떻게 배울 것인가?'&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모르는 것을 두려워하지 않고 일단 시작하며 나만의 언어로 학습한 것을 정리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;'겉핥기 학습'에서 벗어나기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이론과 실제 구현 사이에서 진짜 '아는 것'과 '알고 있던 것'의 차이를 깨달았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;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;동료들의 다양한 접근법을 보며 시야가 확장되었고 AI와의 협업을 통해 사고 과정을 개선하고자 했다.&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;/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;처음에 품었던 '함께 성장하는 경험'은 동료들의 뛰어난 학습 방식과 코드를 보며 자극받고 AI와 함께 고민하며 사고를 확장하는 구체적인 경험으로 경험했으며 무엇보다 &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;code&gt;개발자는 평생 배우는 직업이다&lt;/code&gt; 라는 의미를 이제야 진정 알 것 같네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 챌린지 과정은 끝났지만 여기서 얻은 경험과 깨달음은 나의 개발 여정에 든든한 길잡이가 되어 방향성을 찾은 제가 더욱 개발자로 성장하는데 도움이 될 것 같습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 챌린지 과정에 참여했던 모든 동료분, 마스터님, 운영자분들 고생하셨습니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;The best time to plant a tree was 20 years ago. The second best time is now.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>네이버 부스트캠프 웹・모바일 10기</category>
      <category>네이버부스트캠프</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/158</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-10%EA%B8%B0-%EC%B1%8C%EB%A6%B0%EC%A7%80-%ED%9B%84%EA%B8%B0#entry158comment</comments>
      <pubDate>Wed, 13 Aug 2025 16:08:23 +0900</pubDate>
    </item>
    <item>
      <title>네이버 부스트캠프 웹・모바일 10기 챌린지 3주차 / 4주차 회고</title>
      <link>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-10%EA%B8%B0-%EC%B1%8C%EB%A6%B0%EC%A7%80-3%EC%A3%BC%EC%B0%A8-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3 - 4주차에 들어가며 마음가짐&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDGGt/btsPRDG0T4F/rBPka21b6GTu5s7kKEW7k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDGGt/btsPRDG0T4F/rBPka21b6GTu5s7kKEW7k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDGGt/btsPRDG0T4F/rBPka21b6GTu5s7kKEW7k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDGGt%2FbtsPRDG0T4F%2FrBPka21b6GTu5s7kKEW7k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;411&quot; height=&quot;366&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2주차 회고하면서 &amp;lsquo;이제 방향을 좀 잡아가야지!&amp;rsquo; 했던 게 바로 어제 같은데 막상 돌아보니&amp;hellip; 3, 4주차가 끝나고 회고를 작성하고 있네요..! 뭔가 정신없이 지나간 것 같기도 하네요.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저! 2주차 회고에서 세웠던 작은 목표들을 마음속에 새기고 3주차를 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;&quot;내가 고민하는 내용, 상황을 공유해보기&quot;&lt;/b&gt;, &lt;b&gt;&amp;ldquo;AI를 러닝 파트너로 활용하기&quot;&lt;/b&gt; 라는 두 가지 키워드를 더 의식하며 남은 3,4 주차를 진행하고자 했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3, 4주차는 이제 과정의 절반을 넘어선 시점이라 단순히 미션을 수행하는 것에 그치지 않고 '내가 이 과정을 통해 무엇을 얻고 있는지'를 한 번 더 점검해야 하는 시기라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 미션을 구현하는 것이 아닌 문제 해결 과정 속에서 성장하자  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 마음을 잃지 않기 위해 이번 2주간은 미션의 난이도와 관계없이 &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;b&gt;실제로 행동&lt;/b&gt;해보려고 노력했습니다.&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;  새로운 3 - 4주차&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3, 4주차는 &lt;b&gt;1, 2주차와는 꽤 다른 방식&lt;/b&gt;으로 진행됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;기존 코드 개선 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지는 매일 새로운 주제를 학습하고 구현하는 방식이였지만 이번에는 하나의 미션을 이틀에 걸쳐 진행되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫날에는 새로운 내용을 학습하고 기능을 구현하고 다음 날에는 코드를 리팩토링하거나 테스트를 보강하며 더 깊이 있는 개선 작업을 수행할 수 있었어요!@!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;동료와 함께하는 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 혼자서 미션 요구사항을 분석하고 설계하는 방식이였지만 이번에는 미션 시작 전에 동료와 함께 미션을 분석하고 설계하는 과정이 추가되었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 설계를 하는 과정에서의 대화를 통해 잘 모르는 개념을 자연스럽게 접하게 되기도 했고 제가 고민했던 내용, 설계 과정등을 공유하면서 &lt;b&gt;몰랐던 부분을 배우기도 하고 내가 알고 있는 걸 설명하며 다시 정리할 수도 있었어요.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 전체적인 흐름과 각 부분의 책임을 나누는 구조를 고민하며 훨씬 더 &lt;b&gt;구조적인 사고&lt;/b&gt;를 할 수 있었던 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;처음엔 이틀씩이나 한 미션을 한다고? 시간 좀 여유 있겠네~ &lt;/code&gt; 했는데 막상 해보니... 아니더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 이틀이어서 더 깊이 있게 고민해야 했고 &lt;b&gt;어제 짠 코드에 대해 오늘 다시 생각해보는 시간이 생기니까 '설마 내가 어제 저런 코드를?' 하며 부끄러워지기도 하고요.&lt;/b&gt;  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 좋았던 건 제가 구현을 하면서 더 해보고 싶었던 것을 추가해보고 &lt;b&gt;리팩토링을 실제로 적용해볼 시간이 주어졌다는 점.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 학습과 기능 완성에만 급급했다면 &lt;b&gt;&quot;이 구조 유지보수는 괜찮을까?&quot;, &quot;이게 실제로 CS지식에 맞는 구현방법일까?&quot;&lt;/b&gt; 같은 고민을 스스로에게 계속 던져보게 됐어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  나의 문제 해결 방식&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqfLmg/btsPSzqC8JO/dSInXtkgxeTKkL7jALxpzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqfLmg/btsPSzqC8JO/dSInXtkgxeTKkL7jALxpzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqfLmg/btsPSzqC8JO/dSInXtkgxeTKkL7jALxpzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqfLmg%2FbtsPSzqC8JO%2FdSInXtkgxeTKkL7jALxpzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;375&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주에는 기능 하나를 구현할 때도 이걸 &lt;code&gt;한 번에 다 하려 하지 말고 작게 나눠서 해보자!!&lt;/code&gt; 는 생각을 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 되는 대로 구현을 하다 보면 결국 &quot;이게 왜 안 되지??&quot; 하며 멘붕이 왔었는데  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작게 쪼개서 시도하면 어디서부터 잘못됐는지가 훨씬 파악하기 쉬웠던 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 문제 해결 방식의 저만의 기준은 다음과 같이 정하고 이처럼 진행하고자 했어요.&lt;/p&gt;
&lt;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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 &lt;b&gt;구조적인 접근&lt;/b&gt;은 제가 어떤 것을 우선순위로 처리를 해야하는지 불안감을 없애고 제가 진짜 문제를 해결하고 있다는 느낌을 받게 해줬어요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 저는 두 가지 경험을 통해 큰 성취감을 느낄 수 있었어요.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;동료의 칭찬&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제가 고민했던 문제를 피드백 시간에 공유했을 때 동료들이 &quot;정말 생각지도 못했던 부분인데 학습을 깊이 하신 것 같다&quot;는 의견을 주셨습니다.&lt;/li&gt;
&lt;li&gt;제가 고민했던 지점이 다른 동료들에게도 새로운 인사이트를 줄 수 있었다는 점이 뿌듯했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &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;이 경험은 제가 문제를 파악하고 올바른 방향으로 개선 작업을 수행했다는 뿌듯함을 엄청나게 느낄 수 있었던 것 같아요&amp;hellip;!!&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;지난 회고에서 스스로 해볼 내용으로&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습과 구현의 밸런스, &lt;br /&gt;내가 고민되는 내용이나 나의 문제상황을 정리해서 공유하고, &lt;br /&gt;AI를 동료처럼, 러닝 파트너로 활용해보기!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 세 가지를 체크리스트로 정했었는데요?!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚖️ 학습과 구현 밸런스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 이번 주 미션 방식 덕분에 훨씬 자연스럽게 이뤄졌어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1일차엔 &lt;b&gt;&amp;lsquo;필요한 내용을 학습하고 일단 만들어보는 것 + 고민한 내용, 문제 상황에 대해 동료들과 의견 나누기&amp;rsquo;&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2일차엔 &lt;b&gt;&amp;lsquo;동료들과 나눈 의견과 내가 학습한 내용을 바탕으로, 내 코드에서 무엇이 문제인지 깊이 파헤쳐 개선하기&amp;rsquo;.&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;이렇게 1일차는 학습과 구현, 2일차는 개선과 정리가 자연스러운 루틴이 되어 단순히 기능을 완성하는 데서 그치지 않고 &lt;b&gt;개선과 정리까지&lt;/b&gt; 과정을 적용할 수 있었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  생각 정리해서 공유하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 아직도 어렵긴 해요.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 부분이 왜 안 되는지 모르겠어..&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;뭔가 이상한데 정확히 어디서부터 이상한지도 모르겠어...&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 막상 슬랙에 글을 쓰려니 정리가 안 되는 거예요.&lt;/p&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;이걸 도대체 어떤 말로 설명해야 하지...?&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &lt;code&gt;음.. 내 고민을 공유하려다 또 고민이 생겼네?&lt;/code&gt; 하면서 그냥 덮고 말았던 순간이 꽤 있었어요.&lt;/p&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;ldquo;왜 이렇게 설계했는지&amp;rdquo;, &amp;ldquo;어떤 기준으로 개선의 우선순위를 두었는지&amp;rdquo; 등을 동료들한테 설명하면서 내 생각을 말로 꺼내보면서 내 생각을 정리하는 연습을 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 &amp;lsquo;당연하다&amp;rsquo;고 생각한 것들이 상대에겐 전혀 그렇지 않을 수도 있고 그 반대도 마찬가지였으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 연습을 하고나서는 요구사항을 파악하고 설계하는 과정에서도 슬랙을 통해서 많은 동료들에게 공유하고 많은 의견을 나눌 수 있었어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NqzoE/btsPPaTHaJG/7zXTmMkw5lKCSRQmm57gZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NqzoE/btsPPaTHaJG/7zXTmMkw5lKCSRQmm57gZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NqzoE/btsPPaTHaJG/7zXTmMkw5lKCSRQmm57gZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNqzoE%2FbtsPPaTHaJG%2F7zXTmMkw5lKCSRQmm57gZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;306&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;608&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유는 나를 위한 정리의 과정이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  AI를 러닝 파트너로 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-4주차에도 AI를 문제 해결과 학습 과정에서 적극적으로 활용했지만 이번에는 다른 방식으로 AI를 활용하고자 했어요. 이전에는 단순히 '답'을 얻기 위해 AI를 사용했다면 이제는 '대화'를 통해 함께 고민하고 성장하는 파트너로 사용해보려고 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  가장 큰 변화는 &lt;b&gt;질문의 방식&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 &lt;b&gt;&quot;이 부분이 왜 안 되는지 모르겠어&quot;, &quot;어디가 잘못됐는지 모르겠어&quot;&lt;/b&gt;와 같이 막연한 질문을 던지곤 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 질문에는 명확한 답을 얻기 어려웠고 AI도 저의 사고를 따라가지 못하고 있다는 생각이 들었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 불명확한 질문을 던질 때 AI는 &lt;b&gt;&quot;무엇을 하고 싶으신가요?&quot;, &quot;지금까지 시도한 과정은 어떻게 되나요?&quot;, &quot;왜 그렇게 접근하셨나요?&quot;&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;&amp;ldquo;내가 지금 해야하는게 뭐지?&amp;rdquo;, &amp;ldquo;어떤 부분을 확인해봐야하지?&amp;rdquo;&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;이런 과정을 통해 AI를 활용할 때 아래와 같은 방식으로 활용해보기로 했어요.&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;b&gt;지금까지 시도한 흐름을 간단히 기록하기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 그렇게 시도했는지 이유까지 설명하기&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥락을 명확히 전달할 수 있게 되자 받는 답변의 퀄리티도 높아졌고 무엇보다 저 스스로 문제를 더 명확하게 바라볼 수 있는 시간을 가질 수 있었어요.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;이렇게 챌린지의 3, 4주차가 마무리되었네요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챌린지 기간동안 완벽하진 않았지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;내가 지금 왜 이걸 하고 있지?&quot;&lt;/b&gt;&lt;/span&gt;를 스스로 자주 되묻고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 안에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;나만의 기준, 나만의 학습 방식&lt;/b&gt;&lt;/span&gt;을 조금씩 찾아가고 있다는 느낌이 들었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 슬랙에 고민을 올리는 건 여전히 어렵고 공부하다 보면 또 어김없이 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;야크 털&lt;/b&gt;&lt;/span&gt;  은 나타나고 밤을 새운 날도 있었지만...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이번 챌린지 기간동안 &lt;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; &amp;lsquo;어떻게 나만의 방식으로 성장해나갈지&amp;rsquo;&lt;/b&gt;를 계속 고민하며 걸어가고 싶어요.  &lt;/p&gt;</description>
      <category>네이버 부스트캠프 웹・모바일 10기</category>
      <category>네이버부스트캠프</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/157</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-10%EA%B8%B0-%EC%B1%8C%EB%A6%B0%EC%A7%80-3%EC%A3%BC%EC%B0%A8-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0#entry157comment</comments>
      <pubDate>Wed, 13 Aug 2025 13:25:04 +0900</pubDate>
    </item>
    <item>
      <title>HTTP Request / Response 구조</title>
      <link>https://beomsic.tistory.com/entry/HTTP-Request-Response-%EA%B5%AC%EC%A1%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  HTTP&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/odW37/btsPKSDFJJF/kO3Q4ckh4YdKtK9lSEIDZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/odW37/btsPKSDFJJF/kO3Q4ckh4YdKtK9lSEIDZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/odW37/btsPKSDFJJF/kO3Q4ckh4YdKtK9lSEIDZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FodW37%2FbtsPKSDFJJF%2FkO3Q4ckh4YdKtK9lSEIDZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;183&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 클라이언트와 서버 사이에 데이터를 주고받는 데 사용되는 통신 규약입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹에서 가장 널리 사용되며 사람이 읽을 수 있는 텍스트 기반의 형식을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 통신은 &lt;b&gt;요청(Request)&lt;/b&gt; 과 &lt;b&gt;응답(Response)&lt;/b&gt; 으로 이루어집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  HTTP Request&lt;/h2&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;GET / HTTP/1.1\\r\\n
Host: developer.mozilla.org\\r\\n
Accept-Language: fr\\r\\n&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;li&gt;&lt;code&gt;\r\n&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;줄바꿈을 표현하는 내용이 포함되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cERvlb/btsPHfU5LKR/xtgo1okWrwjszqrjy7NKTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cERvlb/btsPHfU5LKR/xtgo1okWrwjszqrjy7NKTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cERvlb/btsPHfU5LKR/xtgo1okWrwjszqrjy7NKTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcERvlb%2FbtsPHfU5LKR%2Fxtgo1okWrwjszqrjy7NKTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;332&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;Start line&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Method&lt;/td&gt;
&lt;td&gt;클라이언트가 수행하려는 작업 (GET, POST 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;요청한 리소스의 경로(URL) - 전체 URL에서 프로토콜, 도메인, 포트를 제외한 부분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol Version&lt;/td&gt;
&lt;td&gt;HTTP 프로토콜 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;헤더 (Headers)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청에 대한 추가 정보(메타데이터)를 담고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키(Key)와 값(Value)의 쌍&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Host&lt;/code&gt;: 요청 대상 서버의 도메인 이름을 명시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Content-Type&lt;/code&gt;: 요청 본문(Body)에 담긴 데이터의 형식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Accept-Language&lt;/code&gt;: 클라이언트가 선호하는 언어를 명시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;본문 (Body)&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;code&gt;POST&lt;/code&gt;나 &lt;code&gt;PUT&lt;/code&gt;과 같이 서버에 데이터를 보내는 요청에 포함됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 전송할 메시지 내용이나 데이터(예: JSON, XML)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;GET&lt;/code&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;  HTTP Response&lt;/h2&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
ETag: &quot;51142bc1-7449-479b075b2891b&quot;
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html

&amp;lt;!doctype html&amp;gt;&amp;hellip; (here come the 29769 bytes of the requested web page)&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;마찬가지로 \r\n 으로 줄바꿈&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvgXyj/btsPJCIkT3F/SZCPoWaTDkTfKggqwUkN71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvgXyj/btsPJCIkT3F/SZCPoWaTDkTfKggqwUkN71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvgXyj/btsPJCIkT3F/SZCPoWaTDkTfKggqwUkN71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvgXyj%2FbtsPJCIkT3F%2FSZCPoWaTDkTfKggqwUkN71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;290&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;Start line&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol version&lt;/td&gt;
&lt;td&gt;HTTP 프로토콜 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status code&lt;/td&gt;
&lt;td&gt;요청의 성공 또는 실패 여부와 그 이유를 나타내는 숫자 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status message&lt;/td&gt;
&lt;td&gt;응답 상태를 간결히 설명해주는 부분&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;헤더 (Headers)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에 대한 추가 정보(메타데이터)를 담고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;b&gt;본문 (Body)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 클라이언트에게 전달하려는 실제 데이터입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 페이지, 이미지, JSON 데이터 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;204 No Content&lt;/code&gt;와 같은 응답에는 본문이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/html/what-is-http/&quot;&gt;https://www.geeksforgeeks.org/html/what-is-http/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  computer science/  network</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/156</guid>
      <comments>https://beomsic.tistory.com/entry/HTTP-Request-Response-%EA%B5%AC%EC%A1%B0#entry156comment</comments>
      <pubDate>Wed, 6 Aug 2025 18:02:21 +0900</pubDate>
    </item>
    <item>
      <title>소켓 프로그래밍</title>
      <link>https://beomsic.tistory.com/entry/%EC%86%8C%EC%BC%93-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  소켓이란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소켓(Socket) 컴퓨터 네트워크에서 데이터를 주고받기 위한 통신 종단점(endpoint)입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버와 클라이언트가 특정 포트를 통해 양방향 통신을 가능&lt;/b&gt;하게 하며 소켓이 각 애플리케이션에 통신 인터페이스를 제공합니다.&lt;/p&gt;
&lt;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; IP 주소 + 포트 번호의 조합이 소켓의 고유 식별자&lt;/li&gt;
&lt;li&gt;일반적으로 &lt;b&gt;TCP/IP&lt;/b&gt;, &lt;b&gt;UDP/IP&lt;/b&gt; 등 표준 네트워크 프로토콜을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 소켓이 하는일&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;양방향 데이터 송수신&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;소켓을 통해 서버와 클라이언트가 데이터를 주고받습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;네트워크 통신 추상화&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;소켓은 복잡한 네트워크 계층(IP, TCP/UDP 등)을 추상화하여 비교적 &lt;b&gt;간단하게 통신 기능을 구현&lt;/b&gt;할 수 있도록 도와줍니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;IP 주소 및 포트 식별&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;소켓은 &lt;b&gt;어떤 IP 주소의 어떤 포트로 통신할 것인지&lt;/b&gt;를 지정하고 이를 통해 여러 애플리케이션이 동시에 네트워크를 사용할 수 있게 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;연결 관리(서버)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;서버는 클라이언트의 접속 요청을 수락할 때마다 새로운 소켓을 생성하고 이를 통해 &lt;b&gt;다수의 클라이언트와 동시에 통신&lt;/b&gt;할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  소켓 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소켓은 주로 통신 프로토콜에 따라 두 가지 종류로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 스트림 소켓&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;TCP&lt;/b&gt; 사용&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;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;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 데이터그램 소켓&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;UDP&lt;/b&gt;를 사용&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비연결 지향(Connectionless)&lt;/b&gt; 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 전송의 &lt;b&gt;신뢰성&lt;/b&gt;이나 &lt;b&gt;순서&lt;/b&gt;를 보장하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 패킷(데이터그램) 단위로 전송하며 각 패킷은 독립적으로 처리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 설정 과정이 없어 빠르지만 패킷이 유실되거나 순서가 뒤바뀔 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  소켓 통신 과정 (TCP / IP)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Server Socket&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;통신 연결 요청을 받아들이는 Socket&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client Socket&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;통신 연결 요청을 보내는 Socket&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7g7f/btsPKWzh5kg/Ygv1RKCwIajiJX6GB9aNok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7g7f/btsPKWzh5kg/Ygv1RKCwIajiJX6GB9aNok/img.png&quot; data-alt=&quot;https://recipes4dev.tistory.com/153&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7g7f/btsPKWzh5kg/Ygv1RKCwIajiJX6GB9aNok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7g7f%2FbtsPKWzh5kg%2FYgv1RKCwIajiJX6GB9aNok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;410&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://recipes4dev.tistory.com/153&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;Server Socket&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ 서버 소켓 생성&lt;/b&gt; - &lt;code&gt;socket()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 통신을 위한 빈 소켓(socket)을 생성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아직 특정 주소와 포트에 할당되지 않은 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;b&gt;바인딩&lt;/b&gt; - &lt;code&gt;bind()&lt;/code&gt; ⭐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 소켓에 서버의 &lt;b&gt;IP 주소와 포트 번호를 부여&lt;/b&gt;하여 네트워크 상의 고유한 '주소'를 할당합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNkICy/btsPJbj5CFU/uTFmSOnUWX6IhmgCmtkb7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNkICy/btsPJbj5CFU/uTFmSOnUWX6IhmgCmtkb7k/img.png&quot; data-alt=&quot;https://recipes4dev.tistory.com/153&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNkICy/btsPJbj5CFU/uTFmSOnUWX6IhmgCmtkb7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNkICy%2FbtsPJbj5CFU%2FuTFmSOnUWX6IhmgCmtkb7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;191&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://recipes4dev.tistory.com/153&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;&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;bind()는 소켓에 &lt;b&gt;서버의 IP 주소&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;혼란 방지&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 여러 서버 프로세스가 동일한 포트 번호를 사용하려 한다면 운영체제는 어떤 프로세스가 특정 포트로 들어온 데이터를 처리해야 할지 알 수 없습니다.&lt;/li&gt;
&lt;li&gt;bind()는 이 문제를 해결하기 위해 운영체제가 포트 번호를 사용하여 각 소켓을 유일하게 식별하고 관리할 수 있도록 합니다.&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;3️⃣ &lt;b&gt;연결 요청 대기&lt;/b&gt; - &lt;code&gt;listen()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바인딩된 소켓을 연결 요청 대기 상태로 전환합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트로부터의 연결 요청을 받을 준비 완료!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;listen() 함수는 동시에 처리할 수 있는 최대 클라이언트 연결 요청 수를 설정합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ &lt;b&gt;연결 수락&lt;/b&gt; - &lt;code&gt;accept()&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;listen() 상태에서 클라이언트의 연결 요청이 오면 &lt;b&gt;accept() 함수가 이를 수락&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accept()는 요청을 수락함과 동시에 &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;새로운 소켓이 클라이언트와 1:1로 연결되어 데이터를 주고받게 됩니다.&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;5️⃣ &lt;b&gt;데이터 송수신&lt;/b&gt; - &lt;code&gt;send()/recieve()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accept()를 통해 생성된 새로운 소켓을 사용하여 클라이언트와 데이터를 주고받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6️⃣ &lt;b&gt;소켓 종료&lt;/b&gt; - &lt;code&gt;close()&lt;/code&gt;&lt;/p&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;서버는 통신을 마친 소켓을 닫고 다시 accept()를 통해 다른 클라이언트의 연결을 기다릴 수 있습니다.&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;✅ Client 소켓&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ 소켓 생성&lt;/b&gt; - &lt;code&gt;socket()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버와 통신할 소켓을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 소켓의 포트 번호는 보통 운영체제가 자동으로 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2️⃣ 연결 요청&lt;/b&gt; - &lt;code&gt;connect()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 소켓을 이용해 &lt;b&gt;서버의 IP 주소&lt;/b&gt;와 &lt;b&gt;포트 번호&lt;/b&gt;로 연결을 요청합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;connect()&lt;/code&gt; 함수는 서버의 &lt;code&gt;listen()&lt;/code&gt;과 &lt;code&gt;accept()&lt;/code&gt; 과정에 응답해 연결이 성공적으로 이루어지면 통신 준비가 완료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3️⃣ 데이터 송수신&lt;/b&gt; - &lt;code&gt;send()/recieve()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결된 소켓을 통해 서버와 데이터를 주고받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4️⃣ 소켓 종료&lt;/b&gt; - &lt;code&gt;close()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신이 끝난 후 소켓을 닫아 리소스를 반환합니다.&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ibm.com/docs/ko/i/7.6.0?topic=communications-socket-programming&quot;&gt;https://www.ibm.com/docs/ko/i/7.6.0?topic=communications-socket-programming&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/networking/sockets/definition.html&quot;&gt;https://docs.oracle.com/javase/tutorial/networking/sockets/definition.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tyrionlife.tistory.com/781&quot;&gt;https://tyrionlife.tistory.com/781&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@rhdmstj17/%EC%86%8C%EC%BC%93%EA%B3%BC-%EC%9B%B9%EC%86%8C%EC%BC%93-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%A0%95%EB%A6%AC-1&quot;&gt;https://velog.io/@rhdmstj17/소켓과-웹소켓-한-번에-정리-1&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/api/system.net.sockets.socket?view=net-9.0&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/api/system.net.sockets.socket?view=net-9.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  computer science/  network</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/155</guid>
      <comments>https://beomsic.tistory.com/entry/%EC%86%8C%EC%BC%93-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D#entry155comment</comments>
      <pubDate>Wed, 6 Aug 2025 17:55:20 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 통신 방식 - 유니캐스트, 브로드캐스트, 멀티캐스트</title>
      <link>https://beomsic.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EC%8B%9D-%EC%9C%A0%EB%8B%88%EC%BA%90%EC%8A%A4%ED%8A%B8-%EB%B8%8C%EB%A1%9C%EB%93%9C%EC%BA%90%EC%8A%A4%ED%8A%B8-%EB%A9%80%ED%8B%B0%EC%BA%90%EC%8A%A4%ED%8A%B8</link>
      <description>&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  유니캐스트 (Unicast)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1:1 통신 방식으로 고유한 주소로 식별되는 특정 단일 네트워크 목적지에만 데이터를 전송하는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  유니캐스트 데이터 전송 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;송신자는 먼저 데이터 패킷에 목적지의 고유 주소(MAC 주소 또는 IP 주소)를 포함&lt;/li&gt;
&lt;li&gt;패킷은 네트워크 스위치나 라우터를 거쳐 목적지로 전달됩니다. 스위치나 라우터는 패킷의 목적지 주소를 확인하여 해당 주소를 가진 네트워크 장치로 패킷을 전달합니다&lt;/li&gt;
&lt;li&gt;목적지에 도달한 패킷은 해당 장치의 네트워크 인터페이스 카드(NIC)에서 목적지 주소를 확인합니다. 만약 목적지 주소가 자신의 주소와 일치하면 패킷을 받아 처리하고 그렇지 않으면 패킷을 무시&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;  IP 주소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;송신자와 수신자 각각의 고유한 IP 주소를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;송신자(클라이언트): &lt;code&gt;192.168.1.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;수신자(서버) : &lt;code&gt;192.168.1.10&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들 사이의 통신은 각자의 IP주소를 이용&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;  MAC 주소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니캐스트에서는 송신자가 수신자의 MAC 주소를 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷의 헤더에 송신자 MAC 주소와 수신자 MAC 주소를 포함합니다.&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;Brodcast)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 네트워크에 연결되어 있는 모든 호스트 에게 동시에 데이터를 전송하는 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;1 : 모두&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  브로드캐스트 데이터 전송 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 생성하고 해당 네트워크의 모든 장치에게 전달될 수 있도록 브로드캐스트 주소를 목적지 주소로 설정합니다.&lt;/li&gt;
&lt;li&gt;메시지는 네트워크 스위치나 라우터를 통해 네트워크 내의 모든 장치로 전달됩니다.&lt;/li&gt;
&lt;li&gt;각 장치는 메시지를 수신하여 자신의 주소와 비교하지 않고 브로드캐스트 주소로 온 메시지이므로 모두 수신합니다.&lt;/li&gt;
&lt;li&gt;모든 수신 장치는 메시지를 수신하여 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  IP 주소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트 주소 부분이 모두 1로 채워진 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) &lt;code&gt;192.168.1.0/24&lt;/code&gt; 네트워크의 브로드캐스트 주소는 &lt;code&gt;192.168.1.255&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  MAC 주소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 비트가 1로 설정된 &lt;code&gt;FF:FF:FF:FF:FF:FF&lt;/code&gt; (MAC 주소 - 48비트, 6바이트)를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크상 모든 장치들이 브로드캐스트 패킷을 인식해 수신하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;: 모든 장비가 데이터를 수신하므로 불필요한 트래픽을 유발하여 네트워크 효율을 떨어뜨릴 수 있습니다. 이로 인해 &lt;b&gt;충돌&lt;/b&gt; 발생 가능성이 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;: 특정 목적지를 모르는 상태에서 모든 장비에게 정보를 한 번에 전달해야 할 때 매우 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  멀티 캐스트 (Multicast)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티캐스트 그룹 주소를 이용하여 해당 그룹에 속한 호스트로 데이터를 전송하기 위한 통신 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;1 : N(Group)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  멀티캐스트 데이터 전송 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수신을 원하는 호스트들은 IGMP(Internet Group Management Protocol)와 같은 프로토콜을 이용해 특정 &lt;b&gt;멀티캐스트 그룹&lt;/b&gt;에 가입합니다.&lt;/li&gt;
&lt;li&gt;송신자는 해당 그룹의 주소로 한 번만 데이터를 보냅니다.&lt;/li&gt;
&lt;li&gt;네트워크 장비(라우터, 스위치)는 그룹에 가입된 호스트들에게만 데이터를 복제하여 전달합니다.&lt;/li&gt;
&lt;li&gt;불필요한 호스트에게는 데이터를 보내지 않아 브로드캐스트보다 효율적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  IP 주소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티캐스트에서는 특정 멀티캐스트 그룹의 주소를 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티캐스트 그룹의 멤버만 해당 주소로 보내진 데이터를 수신&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  MAC 주소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티캐스트의 MAC 주소는 IP 멀티캐스트 주소에 매핑됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IP 멀티캐스트 주소의 하위 23비트가 MAC 주소의 하위 23비트와 결합되어 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) IP 멀티캐스트 주소가 &lt;code&gt;224.0.0.5&lt;/code&gt; 인경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MAC 주소는 &lt;code&gt;01:00:5E:00:00:05&lt;/code&gt;&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;  애니 캐스트 (Anycast)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 수신자중 가장 가까운 수신자(최적 경로)로 데이터를 전달하는 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;1 : 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  애니캐스트 데이터 전송 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 IP 주소를 가진 &lt;b&gt;여러 서버&lt;/b&gt;가 분산되어 배치되어 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트가 해당 IP로 요청을 보내면 &lt;b&gt;BGP(Border Gateway Protocol)&lt;/b&gt; 같은 라우팅 프로토콜을 기반으로 &lt;b&gt;가장 가까운 서버로 트래픽이 전달&lt;/b&gt;됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장애 조치(Failover)&lt;/b&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  IP 주소&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;여러 호스트가 동일한 IP 주소&lt;/b&gt;를 가지며, 네트워크는 가장 가까운 IP 노드를 선택해 라우팅합니다.&lt;/li&gt;
&lt;li&gt;일반 유저는 하나의 IP로만 요청을 보내며, 라우팅 경로에 따라 최적화된 서버가 응답합니다.&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;  MAC 주소&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;단일 수신 서버의 MAC 주소&lt;/b&gt;만 사용됩니다.&lt;/li&gt;
&lt;li&gt;라우팅 경로 상에서 가장 가까운 호스트에 대한 MAC 주소가 적용되므로 수신자는 일반적인 유니캐스트처럼 보입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ipcisco.com/lesson/unicast-broadcast-multicast-anycast/&quot;&gt;https://ipcisco.com/lesson/unicast-broadcast-multicast-anycast/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/computer-networks/difference-between-unicast-broadcast-and-multicast-in-computer-network/&quot;&gt;https://www.geeksforgeeks.org/computer-networks/difference-between-unicast-broadcast-and-multicast-in-computer-network/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/cs/multicast-vs-broadcast-anycast-unicast&quot;&gt;https://www.baeldung.com/cs/multicast-vs-broadcast-anycast-unicast&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  computer science/  network</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/154</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EC%8B%9D-%EC%9C%A0%EB%8B%88%EC%BA%90%EC%8A%A4%ED%8A%B8-%EB%B8%8C%EB%A1%9C%EB%93%9C%EC%BA%90%EC%8A%A4%ED%8A%B8-%EB%A9%80%ED%8B%B0%EC%BA%90%EC%8A%A4%ED%8A%B8#entry154comment</comments>
      <pubDate>Wed, 6 Aug 2025 17:42:39 +0900</pubDate>
    </item>
    <item>
      <title>벡터 데이터베이스</title>
      <link>https://beomsic.tistory.com/entry/%EB%B2%A1%ED%84%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  벡터 데이터베이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 데이터베이스는 &lt;b&gt;벡터 임베딩&lt;/b&gt; 을 효율적으로 저장, 인덱싱하고, 이를 기반으로 유사성 검색(Similarity Search) 을 수행하는 데 특화된 데이터베이스입니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 데이터베이스에서는 유사성 지표를 적용하여 쿼리와 가장 유사한 벡터를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 알고리즘을 사용해 근사 최인접 이웃(ANN) 검색을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 알고리즘은 쿼리된 벡터의 이웃들을 빠르고 정확하게 검색하는 파이프라인으로 구성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/peJKw/btsPFftZsIh/UkdBS1NMko9H3DRDjG1et1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/peJKw/btsPFftZsIh/UkdBS1NMko9H3DRDjG1et1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/peJKw/btsPFftZsIh/UkdBS1NMko9H3DRDjG1et1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpeJKw%2FbtsPFftZsIh%2FUkdBS1NMko9H3DRDjG1et1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1318&quot; height=&quot;206&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ 인덱싱&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;벡터 데이터베이스는 PQ, LSH, HNSW와 같은 다양한 ANN 알고리즘을 사용해 벡터를 인덱싱&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;2️⃣ 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자, 애플리케이션으로부터 받은 쿼리 벡터에 대해 가장 유사한 벡터들을 데이터베이스에서 찾아내는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3️⃣ 후처리&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;ANN 검색으로 얻은 결과를 정제하고 최종 사용자에게 의미 있는 형태로 반환하는 것&lt;/li&gt;
&lt;li&gt;필터링이나 우선순위에 따른 정렬등의 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  인덱싱 과정이 왜 필요할까?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/evzoUL/btsPGvCHxnb/3lM3kkoc55c7x09ZDOIMb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/evzoUL/btsPGvCHxnb/3lM3kkoc55c7x09ZDOIMb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/evzoUL/btsPGvCHxnb/3lM3kkoc55c7x09ZDOIMb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FevzoUL%2FbtsPGvCHxnb%2F3lM3kkoc55c7x09ZDOIMb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;360&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;벡터 데이터베이스는 벡터를 색인화하여 유사도 검색의 속도와 효율성을 극대화합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 인덱싱이 없다면 쿼리가 들어올 때마다 데이터베이스에 저장된 &lt;b&gt;모든 벡터와 쿼리 벡터를 일일이 비교&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 양이 적을 때는 문제가 없지만 수백만, 수억 개의 벡터가 저장된 대규모 시스템에서는 이러한 방식으로는 실시간 검색이 불가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱싱은 이러한 선형적인 비교를 피하고 미리 구축된 효율적인 데이터 구조를 활용하여 검색 범위를 크게 줄여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 &lt;b&gt;근사적인 결과일지라도 훨씬 빠르게&lt;/b&gt; 찾아낼 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;여러 ANN 알고리즘을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확한 결과보다 유사한 값들을 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ Flat Index&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터를 수정하거나 특별한 복잡한 구조 없이 단순히 저장하는 가장 기본적인 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 시에는 저장된 &lt;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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 벡터를 데이터베이스에 추가해 순차적으로 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  검색 방식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 벡터가 입력되면 저장된 모든 벡터와 쿼리 벡터간의 유사도를 계산합니다.&lt;/li&gt;
&lt;li&gt;계산된 유사도 점수를 기준으로 가장 유사한 벡터를 반환합니다.&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;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 72px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;100% 정확한 결과 반환&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;대규모 데이터셋에서는 검색 속도가 매우 느릴 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모든 벡터를 비교하여 항상 정확히 가장 유사한 벡터를 찾습니다.&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;메모리 사용량이 단순 저장 방식이므로 높아질 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ Random Projection&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차원 벡터를 저차원 공간으로 투영(Project)해 검색 속도를 빠르게 하는 알고리즘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤하게 생긴 벡터(r)를 만들어 원래 벡터(v)와 내적(v * r)을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차원이 줄어들지만 원래 벡터 간의 유사성(거리)은 어느 정도 유지됩니다.&lt;/li&gt;
&lt;li&gt;빠른 탐색이 가능하지만 '근사적'인 값으로 정확도가 떨어질 수 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ Product Quantization&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLlWbB/btsPGHDgd7V/KckKmabnugmdcTRuVRQ5Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLlWbB/btsPGHDgd7V/KckKmabnugmdcTRuVRQ5Gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLlWbB/btsPGHDgd7V/KckKmabnugmdcTRuVRQ5Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLlWbB%2FbtsPGHDgd7V%2FKckKmabnugmdcTRuVRQ5Gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;251&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터를 여러개의 작은 서브 벡터로 쪼개고 각 서브 벡터을 &lt;code&gt;벡터 양자화&lt;/code&gt; 를 통해 크기를 줄입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;128차원 벡터 &amp;rArr; 4개의 32차원 벡터로 분할&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;code&gt;코드북&lt;/code&gt;에서 가장 가까운 값으로 매칭하여 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색시 압축된 값만 비교해 빠르게 유사도를 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;유사한 벡터를 압축해 표현할 때 참조되는 값들&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ LSH (Locality-Sensitive Hashing)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BfqsM/btsPIez72O3/AbmpYERYFoz1kkpgU1vAOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BfqsM/btsPIez72O3/AbmpYERYFoz1kkpgU1vAOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BfqsM/btsPIez72O3/AbmpYERYFoz1kkpgU1vAOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBfqsM%2FbtsPIez72O3%2FAbmpYERYFoz1kkpgU1vAOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;317&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유사한 벡터를 해시함수를 이용해 같은 버킷에 넣는 방식으로 검색 속도를 빠르게 하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  인덱스 생성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 셋의 각 벡터를 해시 함수로 변환해 해시값을 생성하고 해시값을 키로하는 인덱스에 저장&lt;/li&gt;
&lt;li&gt;동일한 해시값을 가지는 벡터들은 같은 버킷에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  검색&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 벡터 입력되면 쿼리 벡터를 해시 함수로 변환하여 해시값을 생성&lt;/li&gt;
&lt;li&gt;생성된 해시값에 해당하는 버킷을 찾습니다.&lt;/li&gt;
&lt;li&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  전체 데이터와 비교하는 것이 아니라 Bucket에 있는 데이터들 사이에서만 찾아 더 빠르게 검색 가능&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;4️⃣ HNSW (Hierarchical Navigable Small World)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwR6Td/btsPHfzCww1/PSqzS3KaeR5YUKhhrzwlP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwR6Td/btsPHfzCww1/PSqzS3KaeR5YUKhhrzwlP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwR6Td/btsPHfzCww1/PSqzS3KaeR5YUKhhrzwlP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwR6Td%2FbtsPHfzCww1%2FPSqzS3KaeR5YUKhhrzwlP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;319&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;효율적인 검색을 제공하는 그래프 기반 알고리즘&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 벡터들을 가지고 여러 개의 레이어(계층)를 가진 그래프를 생성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 벡터 간의 거리는 벡터들 사이의 유사도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;인덱스 생성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 벡터를 그래프의 노드로 추가&lt;/li&gt;
&lt;li&gt;각 노드는 주변 노드들과 연결. 노드 간의 연결 수 지정&lt;/li&gt;
&lt;li&gt;탐색 효율성을 높이기 위해 계층적 그래프 구조 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;검색&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 벡터 입력되면 최상위 레이어의 임의의 노드에서 탐색 시작&lt;/li&gt;
&lt;li&gt;현재 레이어에서 쿼리 벡터에 가장 가까운 곳으로 이동. 더 가까워질 수 없다면 하위 레이어로 이동&lt;/li&gt;
&lt;li&gt;모든 벡터가 존재하는 최하위 레이어에 도달할 때까지 반복&lt;/li&gt;
&lt;li&gt;최하위 레이어에 도달하면 주변 노드들을 추가로 탐색하여 쿼리 벡터와 유사도가 가장 높은 벡터들을 발견후 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  현재 가장 빠르고 정확한 ANN 알고리즘중 하나로 널리 사용&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;5️⃣ IVF&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 벡터 데이터셋의 검색 효율성을 높이기 위해 데이터를 미리 클러스터링하여 '역파일 인덱스' 구조를 만드는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;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;b&gt;K-means와 같은 군집화 알고리즘&lt;/b&gt;을 사용하여 여러 개의 &lt;code&gt;클러스터&lt;/code&gt;로 나눕니다.&lt;/li&gt;
&lt;li&gt;각 클러스터의 중심점(센트로이드)을 계산하고 저장합니다.&lt;/li&gt;
&lt;li&gt;각 벡터는 자신이 속한 클러스터의 '버킷' 또는 '리스트'에 등록됩니다. (마치 텍스트 검색의 역색인처럼 각 센트로이드를 키로 하고 해당 클러스터에 속한 벡터들의 목록을 값으로 가집니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;쿼리 벡터가 입력되면 쿼리 벡터와 가장 가까운 몇 개의 센트로이드(nprobe)를 찾습니다.&lt;/li&gt;
&lt;li&gt;선택된 센트로이드에 해당하는 클러스터 내의 벡터들만을 대상으로 유사도 계산을 수행하여 결과 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;/b&gt; 전체 데이터셋을 탐색하지 않고 관련된 클러스터만 탐색하므로 검색 속도가 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;ref-box&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://milvus.io/ko/blog/what-is-a-vector-database.md&quot;&gt;https://milvus.io/ko/blog/what-is-a-vector-database.md&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://milvus.io/docs/ko/index.md?tab=floating#FLAT&quot;&gt;https://milvus.io/docs/ko/index.md?tab=floating#FLAT&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ibm.com/kr-ko/think/topics/vector-database&quot;&gt;https://www.ibm.com/kr-ko/think/topics/vector-database&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.pinecone.io/learn/vector-database/&quot;&gt;https://www.pinecone.io/learn/vector-database/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/facebookresearch/faiss&quot;&gt;https://github.com/facebookresearch/faiss&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>  computer science/  database</category>
      <author>beomsic</author>
      <guid isPermaLink="true">https://beomsic.tistory.com/153</guid>
      <comments>https://beomsic.tistory.com/entry/%EB%B2%A1%ED%84%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4#entry153comment</comments>
      <pubDate>Mon, 4 Aug 2025 22:49:54 +0900</pubDate>
    </item>
  </channel>
</rss>