<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>uoaheu 님의 블로그</title>
    <link>https://uoaheu.tistory.com/</link>
    <description>uoaheu 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 5 Apr 2026 21:31:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>uoaheu</managingEditor>
    <image>
      <title>uoaheu 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7497085/attach/2834220cb2534209a859d0aba05e173d</url>
      <link>https://uoaheu.tistory.com</link>
    </image>
    <item>
      <title>'이 파일은 어디에 둬야 하지?'에서 벗어나기 위한 FSD 폴더 구조 정리</title>
      <link>https://uoaheu.tistory.com/60</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-002 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwBCyZ/dJMb996eZr5/zu92c4NMgcGE8HQ55ZAd3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwBCyZ/dJMb996eZr5/zu92c4NMgcGE8HQ55ZAd3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwBCyZ/dJMb996eZr5/zu92c4NMgcGE8HQ55ZAd3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwBCyZ%2FdJMb996eZr5%2Fzu92c4NMgcGE8HQ55ZAd3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-002 (7).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 커질수록 가장 자주 발생하는 문제는 '이 파일은 어디에 둬야 하지?'라는 질문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능이 늘어나면 컴포넌트, API, 훅, 타입이 여기저기 흩어지고, 작은 수정도 영향 범위를 예측하기 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 FSD(Feature-Sliced Design)를 이번 프로젝트에 맞춰 적용하면서 정리한 판단 기준과 폴더 구조를 기록한 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*Processes 레이어가 deprecated 된 이후의 방식을 기준으로 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;402&quot; data-start=&quot;366&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;0. 프로젝트 맥락 - 홈 화면이 가진 특성이 구조를 결정한다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;430&quot; data-start=&quot;404&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 홈 화면은 대략 이런 형태이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 5.45.22.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1xsOf/dJMcaaRCklT/Xnkkn7TvGGZvj8tiXqDWKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1xsOf/dJMcaaRCklT/Xnkkn7TvGGZvj8tiXqDWKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1xsOf/dJMcaaRCklT/Xnkkn7TvGGZvj8tiXqDWKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1xsOf%2FdJMcaaRCklT%2FXnkkn7TvGGZvj8tiXqDWKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1442&quot; height=&quot;838&quot; data-filename=&quot;스크린샷 2026-01-22 오후 5.45.22.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;656&quot; data-start=&quot;595&quot; data-ke-size=&quot;size16&quot;&gt;이 한 장의 화면만 봐도 '어디까지가 전역 골격이고, 어디부터가 기능이며, 무엇이 도메인 상태인지'가 갈린다.&lt;/p&gt;
&lt;p data-end=&quot;656&quot; data-start=&quot;595&quot; data-ke-size=&quot;size16&quot;&gt;- 하단 탭은 단순 UI가 아니라 &lt;b&gt;라우팅과 결합된 앱 골격&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;656&quot; data-start=&quot;595&quot; data-ke-size=&quot;size16&quot;&gt;- 링크 복사는 사용자 액션(동사)이므로 &lt;b&gt;feature&lt;/b&gt;로 분리하는 편이 자연스럽다.&lt;/p&gt;
&lt;p data-end=&quot;656&quot; data-start=&quot;595&quot; data-ke-size=&quot;size16&quot;&gt;- 값2/값3은 화면 컴포넌트가 아니라 &lt;b&gt;도메인 상태(명사)와 계산 로직&lt;/b&gt;으로 관리되어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;860&quot; data-start=&quot;815&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;860&quot; data-start=&quot;815&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 이 판단을 &lt;b&gt;FSD의 레이어 규칙&lt;/b&gt;으로 안정적으로 고정하는 과정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;346&quot; data-start=&quot;319&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 구조가 없을 때 벌어지는 일&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;348&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 프로젝트는 아래와 같은 구조로 시작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 5.46.44.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czpeer/dJMcagxvYTh/HChnToYw9BV2k39OHHDZRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czpeer/dJMcagxvYTh/HChnToYw9BV2k39OHHDZRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czpeer/dJMcagxvYTh/HChnToYw9BV2k39OHHDZRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fczpeer%2FdJMcagxvYTh%2FHChnToYw9BV2k39OHHDZRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;272&quot; data-filename=&quot;스크린샷 2026-01-22 오후 5.46.44.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;795&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 빠르고 직관적이다. 하지만 화면과 기능이 늘어나는 순간부터 문제가 시작된다.&lt;/p&gt;
&lt;p data-end=&quot;795&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;- 어떤 UI가 특정 페이지 전용인지, 여러 페이지에서 재사용되는지 구분이 흐려진다.&lt;/p&gt;
&lt;p data-end=&quot;795&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;- API 요청 코드가 apis/에 쌓이면서 어떤 기능에 필요한 요청인지 헷갈리며 도메인이 사라진다.&lt;/p&gt;
&lt;p data-end=&quot;795&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;- 공용 훅과 기능 훅이 섞여서 의존성이 꼬이기 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;795&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;- 신규 기능을 추가할 때마다 '일단 여기 넣고 보자'가 반복된다.&lt;/p&gt;
&lt;p data-end=&quot;795&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;- 수정할 때마다 '이거 다른 화면에도 쓰던데?'라는 불안이 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;682&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;682&quot; data-ke-size=&quot;size16&quot;&gt;이 상태가 되면 개발 생산성이 떨어지는 것보다 더 큰 문제가 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;682&quot; data-ke-size=&quot;size16&quot;&gt;팀이 구조를 믿지 못하게 되는 상황이 된다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;682&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;682&quot; data-ke-size=&quot;size16&quot;&gt;결국 '이 코드는 누가 제일 잘 아는가'가 중요해지고, 기존 작업자 의존이 심해진다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;682&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 FSD를 도입했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;862&quot; data-start=&quot;830&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. FSD란 무엇인가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FSD는 프론트엔드 애플리케이션 구조를 설계하기 위한 방법론이다. 핵심은 다음 두 가지이다.&lt;/p&gt;
&lt;p 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;&lt;b&gt;2. 의존성 방향(단방향)을 강제해 구조적 혼란을 막는다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;712&quot;&gt;&lt;a href=&quot;https://feature-sliced.design/docs/get-started/overview&quot; target=&quot;_blank&quot; title=&quot;FSD 공식문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1pVzF/dJMcacBTN1x/L2RiU1ojAz4gsIYoV8b3ck/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1pVzF%2FdJMcacBTN1x%2FL2RiU1ojAz4gsIYoV8b3ck%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;712&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;864&quot; data-ke-size=&quot;size16&quot;&gt;FSD는 코드를 &lt;b&gt;세 가지 축&lt;/b&gt;으로 나눈다.&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;864&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Layers (레이어) &lt;/b&gt;: 재사용 범위와 의존성 방향에 따라 수직으로 나누는 계층&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;864&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Slices (슬라이스) &lt;/b&gt;: 레이어 안에서 비즈니스 도메인별로 나누는 묶음&lt;/p&gt;
&lt;p data-end=&quot;918&quot; data-start=&quot;864&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Segments (세그먼트) &lt;/b&gt;: 슬라이스 안에서 기술 목적별로 나누는 폴더&lt;/p&gt;
&lt;p data-end=&quot;1093&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1093&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size16&quot;&gt;이 조합이 제공하는 가장 큰 장점은 하나다.&lt;/p&gt;
&lt;p data-end=&quot;1125&quot; data-start=&quot;1097&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드를 어디에 둬야 하는지 판단하는 기준&lt;/b&gt;이 생긴다는 점이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1167&quot; data-start=&quot;1132&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Layers - '어디까지 재사용되는가'로 나누는 기준&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1195&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;FSD에서 가장 중요한 규칙은 &lt;b&gt;단방향 의존성&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;1195&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;- 위 레이어는 아래 레이어를 import 할 수 있다&lt;/p&gt;
&lt;p data-end=&quot;1195&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;- 아래 레이어는 위 레이어를 import 할 수 없다&lt;/p&gt;
&lt;p data-end=&quot;1284&quot; data-start=&quot;1264&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1284&quot; data-start=&quot;1264&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 레이어 흐름은 다음과 같다.&lt;/p&gt;
&lt;p data-end=&quot;1284&quot; data-start=&quot;1264&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;app &amp;rarr; pages &amp;rarr; widgets &amp;rarr; features &amp;rarr; entities &amp;rarr; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;shared&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1366&quot; data-start=&quot;1348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1366&quot; data-start=&quot;1348&quot; data-ke-size=&quot;size16&quot;&gt;각 레이어는 다음 역할을 가진다. (진행하는 프로젝트 기준으로 설명)&lt;/p&gt;
&lt;p data-end=&quot;1366&quot; data-start=&quot;1348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1380&quot; data-start=&quot;1368&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. app - 앱 전역을 조립하는 레이어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;app은&amp;nbsp;&lt;b&gt;앱이 실행되기 위한 골격&lt;/b&gt;이 모이는 곳이다.&lt;/p&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;라우팅, 프로바이더, 전역 레이아웃, 전역 설정이 들어간다.&lt;/p&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서 app은 이런 모양이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.55.09.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HDPJQ/dJMcagjZpXo/K6uq3k3sZwz1l7ROHYPW61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HDPJQ/dJMcagjZpXo/K6uq3k3sZwz1l7ROHYPW61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HDPJQ/dJMcagjZpXo/K6uq3k3sZwz1l7ROHYPW61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHDPJQ%2FdJMcagjZpXo%2FK6uq3k3sZwz1l7ROHYPW61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;410&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.55.09.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;슬라이스가 없고 세그먼트만 존재하는 형태가 자연스럽다.&lt;/p&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1481&quot; data-start=&quot;1467&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. pages - 라우팅되는 화면 단위&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;pages는 URL에 매핑되는 화면이다.&lt;/p&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;이 레이어의 핵심은 무엇을 보여줄지가 아닌 무엇을 조립할지이다.&lt;/p&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트라면 이런 페이지들이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.55.35.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzMcj/dJMcacPrpZo/6YmbXUjgUpKR3fnitRdxMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzMcj/dJMcacPrpZo/6YmbXUjgUpKR3fnitRdxMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzMcj/dJMcacPrpZo/6YmbXUjgUpKR3fnitRdxMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzMcj%2FdJMcacPrpZo%2F6YmbXUjgUpKR3fnitRdxMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;808&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.55.35.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;페이지 내부에 로직이 과도하게 들어가면 다른 레이어의 역할이 무너진다.&lt;/p&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 페이지는 큰 덩어리들을 가져와 배치하는 곳으로 남겨두는 편이 구조가 오래간다.&lt;/p&gt;
&lt;p data-end=&quot;1579&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1597&quot; data-start=&quot;1581&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-3. widgets - 여러 페이지에서 조립되는 큰 UI 블록&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1701&quot; data-start=&quot;1598&quot; data-ke-size=&quot;size16&quot;&gt;widgets는 페이지에서 조립되는 &lt;b&gt;큰 블록 UI&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;1701&quot; data-start=&quot;1598&quot; data-ke-size=&quot;size16&quot;&gt;여러 페이지에서 재사용될 수 있는 덩어리가 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.56.37.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NFRPN/dJMb99Ztydp/nKgtdDKnLV4jKhjnbH5DlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NFRPN/dJMb99Ztydp/nKgtdDKnLV4jKhjnbH5DlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NFRPN/dJMb99Ztydp/nKgtdDKnLV4jKhjnbH5DlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNFRPN%2FdJMb99Ztydp%2FnKgtdDKnLV4jKhjnbH5DlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;492&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.56.37.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1701&quot; data-start=&quot;1598&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은 '위젯은 UI 덩어리'라는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;1701&quot; data-start=&quot;1598&quot; data-ke-size=&quot;size16&quot;&gt;위젯은 내부에서 feature/entities를 가져다 쓰되, &lt;b&gt;자기보다 위 레이어(app/pages)를 알면 안 된다&lt;/b&gt;는 규칙만 지키면 된다.&lt;/p&gt;
&lt;p data-end=&quot;1701&quot; data-start=&quot;1598&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1720&quot; data-start=&quot;1703&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-4. features - 사용자 액션(동사) 단위&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1818&quot; data-start=&quot;1721&quot; data-ke-size=&quot;size16&quot;&gt;features는 버튼 클릭, 모달 열기, 폼 제출처럼 동사 기준으로 나뉜다.&lt;/p&gt;
&lt;p data-end=&quot;1818&quot; data-start=&quot;1721&quot; data-ke-size=&quot;size16&quot;&gt;이 레이어는 'UI도 포함할 수 있지만, 본질은 사용자 액션'이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.57.01.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxWtZR/dJMcaaxjF2V/I3pPlnpKCRUycfckM2GKwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxWtZR/dJMcaaxjF2V/I3pPlnpKCRUycfckM2GKwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxWtZR/dJMcaaxjF2V/I3pPlnpKCRUycfckM2GKwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxWtZR%2FdJMcaaxjF2V%2FI3pPlnpKCRUycfckM2GKwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;622&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.57.01.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1820&quot; data-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;- auth/social-login: 소셜 로그인 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- auth/refresh-session: refresh로 세션 갱신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- profile/create-profile: 프로필 생성 제출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- profile/nickname-check: 닉네임 중복 검사(디바운스/검증 상태 포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- share/copy-share-link: 링크 복사(토스트 포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 화면 요구사항으로 보면 '링크 복사'는 대표적인 feature이다.&lt;br /&gt;반대로 '고정값/값2/값3의 계산'은 사용자 액션이 아니라 상태 모델에 가까우므로 entity 쪽으로 내려가야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1837&quot; data-start=&quot;1820&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-5. entities - 비즈니스 도메인(명사)의 기준점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1838&quot; data-ke-size=&quot;size16&quot;&gt;entities는 프로젝트가 다루는 명사 기준의 타입/상태/API가 모인다.&lt;/p&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1838&quot; data-ke-size=&quot;size16&quot;&gt;이 레이어가 잡히면 화면이 늘어나도 데이터의 기준점이 흔들리지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.58.04.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPCAeE/dJMcabwc7FE/GkVQ9gs59tcVqkfzkvY9ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPCAeE/dJMcabwc7FE/GkVQ9gs59tcVqkfzkvY9ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPCAeE/dJMcabwc7FE/GkVQ9gs59tcVqkfzkvY9ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPCAeE%2FdJMcabwc7FE%2FGkVQ9gs59tcVqkfzkvY9ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;268&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.58.04.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1838&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- session/{model,api}: access/refresh, 로그인 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- user/model: 사용자 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1962&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1962&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 랭킹 정책이 '1위는 고정, 2~10위만 갱신'이라면&lt;/p&gt;
&lt;p data-end=&quot;1962&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size16&quot;&gt;이 조건은 ranking/model의 selector/계산 로직에서 정해지고 UI는 결과만 받아 그리는 편이 안전하다.&lt;/p&gt;
&lt;p data-end=&quot;1962&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1962&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-6. shared - 완전 공용(비즈니스와 최대한 분리)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2073&quot; data-start=&quot;1963&quot; data-ke-size=&quot;size16&quot;&gt;shared는 특정 도메인에 속하지 않고 어디서든 쓰일 수 있는 코드가 모인다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.58.30.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vsBB1/dJMcahJYTdf/JlQYUYzH9cJFRMdVcoowkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vsBB1/dJMcahJYTdf/JlQYUYzH9cJFRMdVcoowkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vsBB1/dJMcahJYTdf/JlQYUYzH9cJFRMdVcoowkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvsBB1%2FdJMcahJYTdf%2FJlQYUYzH9cJFRMdVcoowkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2026-01-22 오후 4.58.30.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;2109&quot; data-start=&quot;2080&quot; data-ke-size=&quot;size16&quot;&gt;여기서 기준은 단순하다.&lt;br /&gt;ex) 이 코드가 방문자(guest)를 알면 shared가 아니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;2109&quot; data-start=&quot;2080&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. Processes가 빠진 이유와 대체 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2606&quot; data-start=&quot;2548&quot; data-ke-size=&quot;size16&quot;&gt;FSD의 레이어 설명에는 &lt;b&gt;Processes가 deprecated&lt;/b&gt;로 표기되어 있다.&lt;/p&gt;
&lt;p data-end=&quot;2606&quot; data-start=&quot;2548&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 페이지 간 시나리오를 담던 레이어였지만, 이제는 deprecated로 정리되어 있다.&lt;/p&gt;
&lt;p data-end=&quot;2606&quot; data-start=&quot;2548&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2699&quot; data-start=&quot;2614&quot; data-ke-size=&quot;size16&quot;&gt;이 변화는 현실적인 문제를 반영한 결과로 보였다.&lt;/p&gt;
&lt;p data-end=&quot;2699&quot; data-start=&quot;2614&quot; data-ke-size=&quot;size16&quot;&gt;- '페이지 간 시나리오'는 대부분 라우팅과 상태로 해결 가능하다.&lt;/p&gt;
&lt;p data-end=&quot;2699&quot; data-start=&quot;2614&quot; data-ke-size=&quot;size16&quot;&gt;- processes는 범위가 애매해 또 하나의 '여기 둬도 되나?' 폴더가 되기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;2699&quot; data-start=&quot;2614&quot; data-ke-size=&quot;size16&quot;&gt;- 복잡한 흐름을 한 폴더로 모으면 오히려 추적이 어려워진다.&lt;/p&gt;
&lt;p data-end=&quot;2699&quot; data-start=&quot;2614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2852&quot; data-start=&quot;2825&quot; data-ke-size=&quot;size16&quot;&gt;대신 다음과 같은 방식이 자연스럽게 자리 잡았다.&lt;/p&gt;
&lt;p data-end=&quot;2852&quot; data-start=&quot;2825&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 라우팅 / 가드 / 셸 구조&lt;/b&gt; &amp;rarr; app에서 해결&lt;/p&gt;
&lt;p data-end=&quot;2852&quot; data-start=&quot;2825&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 사용자 행동 단위&lt;/b&gt; &amp;rarr; features로 분리&lt;/p&gt;
&lt;p data-end=&quot;2852&quot; data-start=&quot;2825&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 상태의 기준점&lt;/b&gt; &amp;rarr; entities에 두기&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;2606&quot; data-start=&quot;2548&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Segments - components/hooks가 아니라 ui/api/model로 나누는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2930&quot; data-start=&quot;2890&quot; data-ke-size=&quot;size16&quot;&gt;폴더를 components, hooks, types로 나누면 처음에는 직관적이다.&lt;/p&gt;
&lt;p data-end=&quot;2930&quot; data-start=&quot;2890&quot; data-ke-size=&quot;size16&quot;&gt;하지만 시간이 지나면 '이 코드가 왜 존재하는지'가 사라진다. 기술 형태만 남고 도메인이 지워진다.&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;FSD에서는 세그먼트를 &lt;b&gt;목적 중심&lt;/b&gt;으로 나눈다.&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;- ui : 화면, 컴포넌트, 스타일&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;- api : 서버 통신&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;- model : 상태, 스키마, 비즈니스 로직&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;- lib : 슬라이스 내부 유틸&lt;/p&gt;
&lt;p data-end=&quot;3114&quot; data-start=&quot;3085&quot; data-ke-size=&quot;size16&quot;&gt;- config : 설정 파일, feature flag&lt;/p&gt;
&lt;p data-end=&quot;3246&quot; data-start=&quot;3231&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3246&quot; data-start=&quot;3231&quot; data-ke-size=&quot;size16&quot;&gt;폴더를 보는 순간 '이 파일이 무슨 목적으로 존재하는가'가 보이기 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;3246&quot; data-start=&quot;3231&quot; data-ke-size=&quot;size16&quot;&gt;이게 규모가 커질수록 결정적으로 크게 작동한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;4431&quot; data-start=&quot;4396&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 의문 - layouts는 왜 app에 있나&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 하단 탭도 공용 컴포넌트처럼 보였다. 그래서 shared/ui에 두려고 했었다.&lt;/p&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;하지만 폴더 구조를 공부하다 보니 이번 프로젝트의 하단 탭은 단순 UI가 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;1. /home, /guests, /ranking 라우팅 구조와 강하게 결합된다.&lt;/p&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;2. 현재 활성 탭은 URL과 동기화되어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;3. 방문자 탭 배지(새로 온 방문자 체크)는 전역 상태와 결합된다.&lt;/p&gt;
&lt;p data-end=&quot;4508&quot; data-start=&quot;4433&quot; data-ke-size=&quot;size16&quot;&gt;4. 어떤 페이지에서는 탭이 사라져야 한다.&lt;/p&gt;
&lt;p data-end=&quot;4770&quot; data-start=&quot;4671&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4770&quot; data-start=&quot;4671&quot; data-ke-size=&quot;size16&quot;&gt;즉, 탭은 &lt;b&gt;재사용 UI가 아니라 앱의 골격&lt;/b&gt;이다.&lt;br /&gt;그래서 app/layouts에 두는 것이 구조적으로 자연스럽다.&lt;/p&gt;
&lt;p data-end=&quot;4770&quot; data-start=&quot;4671&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4770&quot; data-start=&quot;4671&quot; data-ke-size=&quot;size16&quot;&gt;그러면서 'layouts에 두면 늘 고정되어 있는 것은 아닌가?'라는 의문이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;4615&quot; data-start=&quot;4604&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이에 대한 답으로는 layout에 둔다고 해서 모든 페이지에 항상 탭이 붙는 것은 아니라는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4615&quot; data-start=&quot;4604&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4697&quot; data-start=&quot;4660&quot; data-ke-size=&quot;size16&quot;&gt;라우트 구조로 &lt;b&gt;탭이 있는 구간 / 없는 구간&lt;/b&gt;을 나누면 된다.&lt;/p&gt;
&lt;p data-end=&quot;4985&quot; data-start=&quot;4945&quot; data-ke-size=&quot;size16&quot;&gt;이 차이를 라우트 구조로 표현하는 순간 layout의 의미가 명확해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;5030&quot; data-start=&quot;4992&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. 예시 - HomePage.tsx 하나로 시작한 상태에서의 분해 기준&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5098&quot; data-start=&quot;5032&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 HomePage.tsx 하나로 시작하는 것이 자연스럽다.&lt;/p&gt;
&lt;p data-end=&quot;5098&quot; data-start=&quot;5032&quot; data-ke-size=&quot;size16&quot;&gt;문제는 '언제 무엇을 빼낼지' 기준이 없을 때 생긴다. 기준만 있으면 분해는 어렵지 않다.&lt;/p&gt;
&lt;p data-end=&quot;5123&quot; data-start=&quot;5100&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;5123&quot; data-start=&quot;5100&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7-1. HomePage에 남길 것&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위젯을 조립하는 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 페이지 수준의 레이아웃 배치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 라우팅 이동 트리거 정도&lt;/p&gt;
&lt;p data-end=&quot;5203&quot; data-start=&quot;5179&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;5203&quot; data-start=&quot;5179&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7-2. HomePage에서 빠질 것&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;5203&quot; data-start=&quot;5179&quot; data-ke-size=&quot;size16&quot;&gt;- 홈 상단 요약(고정값/값2/값3 카드 묶음) &amp;rarr; widgets/home-summary&lt;/p&gt;
&lt;p data-end=&quot;5203&quot; data-start=&quot;5179&quot; data-ke-size=&quot;size16&quot;&gt;- 공유 링크 패널(복사 버튼, 토스트) &amp;rarr; widgets/share-link-panel&lt;/p&gt;
&lt;p data-end=&quot;5203&quot; data-start=&quot;5179&quot; data-ke-size=&quot;size16&quot;&gt;- 링크 생성/조회 보장 같은 액션 &amp;rarr; features/share/ensure-share-link&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 고정값/값2/값3의 계산 로직, 상태 기준점 &amp;rarr; entities/*/model&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- API 호출 정의 &amp;rarr; entities/*/api 또는 해당 feature/api&lt;/p&gt;
&lt;p data-end=&quot;5524&quot; data-start=&quot;5474&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5524&quot; data-start=&quot;5474&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 분리하면 HomePage는 화면으로 남고, 나머지는 각자의 역할로 이동한다.&lt;/p&gt;
&lt;p data-end=&quot;5524&quot; data-start=&quot;5474&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 수정할 때 '어디를 고쳐야 하는지'가 더 빠르게 보이게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;5570&quot; data-start=&quot;5531&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. 결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5660&quot; data-start=&quot;5635&quot; data-ke-size=&quot;size16&quot;&gt;FSD를 도입하며 가장 크게 달라진 점은 폴더 구조가 아니다.&lt;br /&gt;&lt;b&gt;결정 비용이 사라진 것&lt;/b&gt;이 핵심이다.&lt;/p&gt;
&lt;p data-end=&quot;5660&quot; data-start=&quot;5635&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;새 코드를 만들 때마다 이 질문만 하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드는 앱 전역 조립인가 &amp;rarr; app&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드는 라우팅되는 화면인가 &amp;rarr; pages&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드는 큰 덩어리 UI인가 &amp;rarr; widgets&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드는 사용자 액션인가 &amp;rarr; features&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드는 도메인 데이터의 기준점인가 &amp;rarr; entities&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5099&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드는 완전 공용인가 &amp;rarr; shared&lt;/p&gt;
&lt;p data-end=&quot;5930&quot; data-start=&quot;5842&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;231&quot; data-start=&quot;142&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-end=&quot;231&quot; data-start=&quot;144&quot; data-ke-size=&quot;size16&quot;&gt;FSD 아키텍처를 공부하면서 협업하기 좋은 폴더 구조란&lt;br /&gt;누가 코드를 추가하더라도 같은 기준으로 같은 위치를 선택할 수 있는 구조라는 결론에 도달했다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>FRONTEND</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/60</guid>
      <comments>https://uoaheu.tistory.com/60#entry60comment</comments>
      <pubDate>Thu, 22 Jan 2026 17:53:57 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 구조] 프로그램이 실행된다는 것의 의미</title>
      <link>https://uoaheu.tistory.com/59</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-001 (12).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JoJuB/dJMcahpyFUl/bySdD9TRRFkb4JVvpLEtLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JoJuB/dJMcahpyFUl/bySdD9TRRFkb4JVvpLEtLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JoJuB/dJMcahpyFUl/bySdD9TRRFkb4JVvpLEtLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJoJuB%2FdJMcahpyFUl%2FbySdD9TRRFkb4JVvpLEtLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-001 (12).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 실행 전의 프로그램은 just 데이터일 뿐이다&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;p data-ke-size=&quot;size16&quot;&gt;- SSD/HDD와 같은 보조기억장치에 저장된 .exe, .app, .jar 파일들&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;LIKE | 프로그램 파일 = 요리책&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;2. 실행의 첫 단계 : 메모리(RAM)로 올라간다(Load)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행하면 가장 먼저 일어나는 일은 보조기억장치에 있던 프로그램 데이터를 메모리(RAM)로 올리는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-009.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qbIhQ/dJMcajgx2X2/ykpUkVopKaZsKUZsXa0RQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qbIhQ/dJMcajgx2X2/ykpUkVopKaZsKUZsXa0RQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qbIhQ/dJMcajgx2X2/ykpUkVopKaZsKUZsXa0RQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqbIhQ%2FdJMcajgx2X2%2FykpUkVopKaZsKUZsXa0RQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-009.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;CPU는 초당 수십억 번 연산할 만큼 빠르지만, SSD나 HDD는 CPU의 속도를 따라가기엔 너무 느리다.&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;이때 소프트웨어의 이름이 바뀐다고 볼 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 전 : 프로그램 (Program, 정적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 후 : 프로세스 (Process, 동적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스(Process)란, '실행 중인 프로그램 + 필요한 모든 실행 정보'를 포함한 개념이다.&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;3. 프로세스의 탄생 : 운영체제가 할당하는 메모리 공간&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제(OS)는 프로그램을 메모리에 올릴 때 프로세스가 안정적으로 실행될 수 있도록 전용 공간을 나눠 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 바로 &lt;b&gt;프로세스 주소 공간&lt;/b&gt;이다. 이 공간은 역할에 따라 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. 코드 영역 (Code/Text)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 우리가 작성한 코드가 기계어로 변환되어 저장되는 곳&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. 데이터 영역 (Data/BSS)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전역 변수, 정적 변수 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로그램 시작과 동시에 할당되어 종료될 때까지 유지되는 값들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 처음부터 끝까지 필요한 값들을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-3. 힙 영역 (Heap)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자(개발자)가 직접 관리하는 메모리 공간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행 중에 크기가 결정되는 데이터들이 저장되며 메모리를 효율적으로 쓰기 위해 동적 할당할 때 사용 (Java의 객체, C의 malloc 등)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-4. 스택 영역(Stack)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수가 호출될 때 필요한 정보(지역 변수, 매개변수, 돌아갈 주소)가 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수 호출이 끝나면 자동으로 사라지며 LIFO 구조로 관리되어 재귀 함수 호출 등이 가능해짐&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;size14&quot;&gt;* 같은 프로세스 안에서도 스택은 각 작업의 흐름(Thread)마다 따로 할당되지만, 힙은 프로세스 전체가 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;4. 실행의 핵심 : CPU의 명령어 사이클&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 준비가 끝났다면 이제 주인공인 CPU가 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 메모리에 올라온 명령어들을 가져와 명령어 사이클을 무한 반복하며 실행한다. (Fetch - Decode - Execute)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 CPU는 시스템 버스라는 통로를 통해 메모리의 코드를 한 줄씩 가져와 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-010.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2WMZs/dJMcagRHwDL/bGec1TA3iqp0WD8v70WyWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2WMZs/dJMcagRHwDL/bGec1TA3iqp0WD8v70WyWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2WMZs/dJMcagRHwDL/bGec1TA3iqp0WD8v70WyWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2WMZs%2FdJMcagRHwDL%2FbGec1TA3iqp0WD8v70WyWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-010.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Fetch : 인출&lt;/b&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;&lt;b&gt;2. Decode : 해독&lt;/b&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;&lt;b&gt;3. Execute : 실행&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;size14&quot;&gt;* CPU가 메모리에서 명령어를 가져올 때는 시스템 버스라는 고속 통로를 이용한다. Fetch 단계에서 CPU 내의 PC라는 레지스터가 다음에 읽을 명령어의 주소를 가리키고 있으며 그 주소에 있는 데이터가 버스를 타고 CPU로 전달되는 것이다.&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;5. 정리 : 프로그램 &amp;rarr; 프로세스로 바뀌는 순간&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 83.0233%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.6589%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 23.5658%;&quot;&gt;프로그램 (Program)&lt;/td&gt;
&lt;td style=&quot;width: 53.9071%;&quot;&gt;프로세스 (Process)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.6589%;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 23.5658%;&quot;&gt;정적 (Static)&lt;/td&gt;
&lt;td style=&quot;width: 53.9071%;&quot;&gt;동적 (Dynamic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.6589%;&quot;&gt;위치&lt;/td&gt;
&lt;td style=&quot;width: 23.5658%;&quot;&gt;보조기억장치 (SSD/HDD)&lt;/td&gt;
&lt;td style=&quot;width: 53.9071%;&quot;&gt;주기억장치(RAM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.6589%;&quot;&gt;실행&lt;/td&gt;
&lt;td style=&quot;width: 23.5658%;&quot;&gt;불가능&lt;/td&gt;
&lt;td style=&quot;width: 53.9071%;&quot;&gt;CPU에 의해 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.6589%;&quot;&gt;구성&lt;/td&gt;
&lt;td style=&quot;width: 23.5658%;&quot;&gt;코드 데이터 파일&lt;/td&gt;
&lt;td style=&quot;width: 53.9071%;&quot;&gt;메모리 구조(Stack, Heap, .. ) + CPU 리소스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.6589%;&quot;&gt;비유&lt;/td&gt;
&lt;td style=&quot;width: 23.5658%;&quot;&gt;요리책&lt;/td&gt;
&lt;td style=&quot;width: 53.9071%;&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;프로그램이 실행된다는 것은&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;다음 글에서는 더 깊이 들어가서 CPU는 내부에서 어떻게 일을 나눠 처리하는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALU, CU, 레지스터는 각각 무슨 역할을 하는지 알아볼 예정이다.&lt;/p&gt;</description>
      <category>CS</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/59</guid>
      <comments>https://uoaheu.tistory.com/59#entry59comment</comments>
      <pubDate>Sat, 3 Jan 2026 16:35:25 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 구조] 컴퓨터를 구성하는 요소</title>
      <link>https://uoaheu.tistory.com/58</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-001 (11).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yrzL0/dJMcajgtEbG/rmv3m6atOXWIytK9V1ykU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yrzL0/dJMcajgtEbG/rmv3m6atOXWIytK9V1ykU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yrzL0/dJMcajgtEbG/rmv3m6atOXWIytK9V1ykU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyrzL0%2FdJMcajgtEbG%2Frmv3m6atOXWIytK9V1ykU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-001 (11).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;이번에는 컴퓨터를 구성하는 기본 요소를 중심으로 '컴퓨터가 어떻게 일을 나눠서 처리하는지'를 살펴보겠다.&lt;/p&gt;
&lt;p data-ke-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;1. 컴퓨터가 하는 일은 생각보다 단순하다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터가 수행하는 모든 작업은 크게 다음 4가지 흐름으로 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입력 - 처리 - 저장 - 출력&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-006 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3SkSl/dJMcaiPpH94/yxnzx1ajOQM4kow5C9tnP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3SkSl/dJMcaiPpH94/yxnzx1ajOQM4kow5C9tnP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3SkSl/dJMcaiPpH94/yxnzx1ajOQM4kow5C9tnP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3SkSl%2FdJMcaiPpH94%2Fyxnzx1ajOQM4kow5C9tnP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-006 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름은 메신저, 웹 브라우저, 게임 등 모든 프로그램에서 공통적으로 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 왜 컴퓨터는 하나의 장치가 아니라 여러 요소로 나뉘어 있을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'하나의 장치로 모든 작업을 처리하면 안 될까'라는 질문을 떠올릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 작업의 속도와 역할이 다르기 때문에 하나의 장치로 처리하기에는 무리가 있다.&lt;/p&gt;
&lt;p data-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;그래서 컴퓨터는 역할별로 부품을 나누는 구조로 설계되어 있다.&lt;/p&gt;
&lt;p data-ke-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;3. 컴퓨터를 구성하는 4가지 핵심 요소&lt;/b&gt;&lt;/h3&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;- CPU (중앙처리장치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 (주기억장치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보조기억장치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 입출력 장치&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-007 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca54QQ/dJMcabJzNGo/94xvCdHcq8gfKDBZKFvKHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca54QQ/dJMcabJzNGo/94xvCdHcq8gfKDBZKFvKHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca54QQ/dJMcabJzNGo/94xvCdHcq8gfKDBZKFvKHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca54QQ%2FdJMcabJzNGo%2F94xvCdHcq8gfKDBZKFvKHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-007 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4가지 요소는 각자의 역할을 분담하면서 협업한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. CPU (중앙처리장치)&lt;/b&gt;&lt;/h4&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는 스스로 판단하거나 생각하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 정해진 명령어를 매우 빠르게 실행하는 장치라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 일을 할지는 프로그램이 정해주고, CPU는 그 지시를 그대로 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, CPU는 레지스터라는 초고속 저장 공간을 가지고 있어서 실제 연산은 메모리가 아닌 레지스터에서 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. 메모리(주기억장치)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리는 실행 중인 프로그램과 데이터가 올라가는 공간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행하면 보조기억장치에 있던 파일이 메모리에 로드되고, 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;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-3. 보조기억장치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보조기억장치는 전원이 꺼져도 데이터를 보관하는 저장 공간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 파일이나 문서, 사진, 영상과 같은 데이터가 여기에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보조기억장치는 메모리보다 훨씬 느리기 때문에 CPU는 보조기억장치의 데이터를 직접 실행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신, 보조기억장치 &amp;rarr; 메모리 &amp;rarr; CPU라는 흐름으로 데이터를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-4. 입출력 장치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력장치는 컴퓨터가 외부와 데이터를 주고받는 창구다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-007.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMm0x2/dJMcaaYb2xg/wU8ePngc2nQRD5l5kmPF30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMm0x2/dJMcaaYb2xg/wU8ePngc2nQRD5l5kmPF30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMm0x2/dJMcaaYb2xg/wU8ePngc2nQRD5l5kmPF30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMm0x2%2FdJMcaaYb2xg%2FwU8ePngc2nQRD5l5kmPF30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-007.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 장치는 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;또한, 입출력 작업이 끝났을 때 CPU에게 알려주는 방식으로 인터럽트가 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;* 인터럽트란, CPU가 계속 장치를 감시하지 않아도 되도록 장치가 스스로 '작업이 끝났다'라고 알리는 신호를 의미한다.&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;4. 전체 구조 한 번에 정리하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 이 그림을 보면서 지금까지 살펴본 내용을 하나의 흐름으로 정리해 보겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-007 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca54QQ/dJMcabJzNGo/94xvCdHcq8gfKDBZKFvKHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca54QQ/dJMcabJzNGo/94xvCdHcq8gfKDBZKFvKHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca54QQ/dJMcabJzNGo/94xvCdHcq8gfKDBZKFvKHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca54QQ%2FdJMcabJzNGo%2F94xvCdHcq8gfKDBZKFvKHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-007 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램은 보조기억장치에 저장되어 있다가 실행 시 메모리로 적재된다.&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;이때 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;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터는 하나의 거대한 장치가 아니라 역할이 분리된 여러 구성 요소가 각자의 일을 맡아 협업하는 구조로 이뤄져 있다.&lt;/p&gt;
&lt;p data-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;</description>
      <category>CS</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/58</guid>
      <comments>https://uoaheu.tistory.com/58#entry58comment</comments>
      <pubDate>Tue, 23 Dec 2025 19:51:39 +0900</pubDate>
    </item>
    <item>
      <title>비동기 처리에서의 Race Condition 정리</title>
      <link>https://uoaheu.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;최근 비동기 처리와 관련해서 Race Condition(경쟁 상태)이라는 용어를 처음 접하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;JS는 싱글 스레드 기반이다. 근데 왜 이러한 문제가 생기는지 궁금해졌고, Race Condition이 무엇인지, 어떤 상황에서 발생하는지 정리해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Race Condition이란&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;: 둘 이상의 비동기 작업이 같은 자원(= 상태)을 갱신하려고 할 때, 실행 순서에 따라 결과가 달라지는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;핵심은 순서 제어 불능 !! (= 비동기 작업의 순서를 확실히 제어 X)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;비동기 작업은 요청한 순서대로 끝나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;따라서 먼저 보낸 요청의 응답이 나중에 보낸 요청보다 늦게 도착하면, 화면이나 데이터가 예상과 다른 상태를 보이게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이런 상황이 바로 Race Condition이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. JS는 싱글 스레드인데 Race Condition이 왜 생길까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;JavaScript는 싱글 스레드로 동작하지만, 비동기 로직은 별도 환경에서 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 네트워크 요청(fetch)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 타이머(setTimeout)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 브라우저 Web APIs&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 비동기 작업들의 결과는 이벤트 루프를 통해 메인 스레드로 전달되는데 이 과정에서 완료된 순서대로 콜백이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;즉, 논리적 병렬성이 존재하고, 이러한 이유로 싱글 스레드 환경에서도 Race Condition이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. React에서 Race Condition이 자주 발생하는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;React에서는 다음 두 요소 때문에 Race Condition이 쉽게 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1. 상태 업데이트는 비동기적&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;하나의 state 갱신이 완료되기 전에 또 다른 갱신이 들어올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2. UI는 state 하나만 잘못 들어와도 즉시 오염&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;늦게 도착한 비동기 결과가 최신 상태를 덮어쓰는 문제가 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실제 React에서 발생하는 Race Condition 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;어떤 상황에서 Race Condition이 발생하는지 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-1. 검색 요청 순서 뒤바뀜&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  fetch(`/api/search?q=${query}`)
    .then(res =&amp;gt; res.json())
    .then(setResult);
}, [query]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;&quot;ab&quot; 검색 &amp;rarr; 요청 A 전송&lt;/li&gt;
&lt;li&gt;&quot;abc&quot; 검색 &amp;rarr; 요청 B 전송&lt;/li&gt;
&lt;li&gt;B가 먼저 응답&lt;/li&gt;
&lt;li&gt;A가 늦게 도착하며 state를 덮어씀&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[결과]&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;화면에는 &quot;ab&quot; 검색 결과가 보임 &amp;rarr; 최신 상태가 사라짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;이것을 전형적인 Race Condition이라고 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-2. 컴포넌트 언마운트 후 setState 호출&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  let ignore = false;

  fetchUser().then(data =&amp;gt; {
    if (!ignore) setUser(data);
  });

  return () =&amp;gt; { ignore = true; };
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;탭 이동처럼 컴포넌트가 빠르게 교체되는 환경에서 이미 사라진 컴포넌트가 늦게 돌아온 응답으로 state(상태)를 갱신하려 하면 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-3. stale state 문제&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;setTimeout(() =&amp;gt; {
  setCount(count + 1); // count는 이미 오래된 값
}, 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;비동기 내부의 state는 렌더 시점의 캡처된 값이기 때문에 여러 번 클릭해도 기대와 다른 결과가 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Race Condition 해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;더 많은 방법이 존재하지만 대표적인 4가지 방법을 살펴보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-1. 이전 요청은 반드시 취소하거나 무효화하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;AbortController 사용 (권장)&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const controller = new AbortController();

fetch(url, { signal: controller.signal })
  .then(res =&amp;gt; res.json())
  .then(setResult);

return () =&amp;gt; controller.abort(); // 이전 요청 취소
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;요청 ID 비교 방식&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const idRef = useRef(0);
const id = ++idRef.current;

fetch(url)
  .then(res =&amp;gt; res.json())
  .then(data =&amp;gt; {
    if (id === idRef.current) setResult(data);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;최신 요청의 응답만 반영되도록 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-2. 비동기 내부에서 state를 직접 사용하지 않기&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;함수형 업데이트로 stale state 방지&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;setCount(prev =&amp;gt; prev + 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-3. useRef로 최신 값 보관하기&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const latestQuery = useRef(query);
useEffect(() =&amp;gt; {
  latestQuery.current = query;
}, [query]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;비동기 콜백 안에서 항상 최신 값을 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-4. React Query 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;대부분의 Race Condition이 라이브러리 내부에서 자동 관리된다.&lt;br /&gt;(중복 요청 제거, 최신 요청만 반영, 취소 처리 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;188&quot; data-start=&quot;154&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 더 중요한 비동기 작업을 먼저 처리하려면?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;232&quot; data-start=&quot;190&quot; data-ke-size=&quot;size14&quot;&gt;Race Condition을 이해하다 보니 다음 질문이 생각났다.&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;234&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;&quot;여러 개의 비동기 작업이 동시에 발생한다면, 어떻게 더 중요한 작업을 먼저 처리하도록 만들 수 있을까?&quot;&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;- 사용자의 가장 최근 입력값이 가장 중요할 때 (검색창)&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;- 화면에 바로 반영되어야 하는 긴급 UI 업데이트&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;아마 '중요도가 낮은 것보다 중요한 것의 결과가 우선 적용되도록 제어하는 것'이 핵심 아이디어라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;처음에는 단순하게 '더 중요한 작업이 걸리는 시간을 예측해서 먼저 실행시키고, 그 작업이 끝나고 일정 시간이 지나면 나머지 작업을 실행하는 방식'을 떠올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;하지만 이 방식은 실제 비동기 환경에서는 거의 불가능하다. 비동기 작업의 걸리는 시간을 미리 알 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;네트워크 속도나 서버 응답 시간은 매번 달라진다. 그리고 비동기 작업은 요청 순서와 상관없이 준비되는 순서대로 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;즉, '이 작업이 더 빨리 끝날 것이다'를 예측하는 것은 의미가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;따라서 시간을 기준으로 우선순위를 조절하는 방식은 실질적으로 적용하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇다면 실제 개발에서는 어떻게 더 중요한 비동기 작업을 먼저 반영할까?&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;찾아보니 다음 2가지 방식을 많이 사용한다고 한다.&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;236&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;588&quot; data-start=&quot;552&quot; data-ke-size=&quot;size16&quot;&gt;6-1. 최신 작업(= 더 중요한 작업)만 유효하게 만들기&lt;/p&gt;
&lt;p data-end=&quot;668&quot; data-start=&quot;590&quot; data-ke-size=&quot;size14&quot;&gt;요청 ID 또는 AbortController를 사용하면 자연스럽게 '더 중요한 작업 = 가장 최신 작업'이라는 규칙을 만들 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;668&quot; data-start=&quot;590&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;668&quot; data-start=&quot;590&quot; data-ke-size=&quot;size14&quot;&gt;- 최신 요청만 state 갱신&lt;/p&gt;
&lt;p data-end=&quot;668&quot; data-start=&quot;590&quot; data-ke-size=&quot;size14&quot;&gt;- 이전 요청은 취소하거나 응답이 와도 무시&lt;/p&gt;
&lt;p data-end=&quot;668&quot; data-start=&quot;590&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;754&quot; data-start=&quot;720&quot; data-ke-size=&quot;size14&quot;&gt;즉, 자연스럽게 가장 중요한 작업이 이기는 구조가 된다.&lt;/p&gt;
&lt;p data-end=&quot;823&quot; data-start=&quot;756&quot; data-ke-size=&quot;size14&quot;&gt;요청 ID 방식은 중요도(최신성)을 기준으로 비동기 작업의 우선순위를 부여하는 전략이다.&lt;/p&gt;
&lt;p data-end=&quot;823&quot; data-start=&quot;756&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;871&quot; data-start=&quot;825&quot; data-ke-size=&quot;size16&quot;&gt;6-2. React 18의 startTransition으로 우선순위 분리하기&lt;/p&gt;
&lt;p data-end=&quot;917&quot; data-start=&quot;873&quot; data-ke-size=&quot;size14&quot;&gt;React 18에서는 UI 업데이트를 긴급 / 비긴급으로 나눌 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1765437061056&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;startTransition(() =&amp;gt; {
  // 급하지 않은 상태 업데이트
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size14&quot;&gt;- 클릭, 입력 등 빨리 반영되어야 하는 업데이트 = 긴급 !!&lt;/p&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size14&quot;&gt;- 무거운 리스트 렌더링이나 데이터 패칭 = 비긴급&lt;/p&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size14&quot;&gt;이렇게 하면 React가 내부적으로 더 중요한 UI 업데이트를 먼저 반영하고,&lt;br /&gt;부하가 큰 작업은 뒤로 미뤄 자연스러운 우선순위가 만들어진다.&lt;/p&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1045&quot; data-ke-size=&quot;size14&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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Race Condition = 비동기 흐름 안에서 '이전 작업의 결과가 최신 상태를 덮어쓰는 것'을 막는 문제&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 가장 최신 요청만 반영되게 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 오래된 상태를 기반으로 계산하지 않기&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;비동기 로직이 많은 UI일수록 이 개념을 명확히 이해하는 것이 중요하다는 점을 배웠다 !!&lt;/p&gt;</description>
      <category>FRONTEND/React</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/57</guid>
      <comments>https://uoaheu.tistory.com/57#entry57comment</comments>
      <pubDate>Thu, 11 Dec 2025 16:18:51 +0900</pubDate>
    </item>
    <item>
      <title>알고리즘을 공부하면서 깨달은 점.zip</title>
      <link>https://uoaheu.tistory.com/56</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;size14&quot;&gt;- 이게 왜 개발자한테 필요할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 이걸 실제 개발에서 사용할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;프론트엔드와 백엔드를 모두 경험해 봤지만, 실제 서비스 개발에서는 단순히 API 연결, UI 개발, DB 설계처럼 눈에 보이는 구현이 대부분이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그런데 알고리즘 문제들을 풀다가 실제 서비스 설계나 성능 최적화에 동일하게 쓰일 수 있다는 사실을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그리고 문제를 구조적으로 분석하는 사고방식을 키우는 것이 중요하단 점을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이번 글에는 알고리즘을 공부하면서 실제 개발과 연결해 이해했던 내용을 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 성능과 속도를 결정하는 기반이다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;동일한 기능이라도 어떤 알고리즘을 선택하느냐에 따라 성능은 극적으로 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;길 찾기 문제에서 가장 기본적으로 사용되는 알고리즘은 &lt;b&gt;다익스트라 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 각 노드를 방문할 때 현재까지의 최단 비용을 유지하면서 불필요한 탐색을 하지 않도록 설계된 알고리즘이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;반대로, 다익스트라를 모르는 상태에서 &lt;b&gt;완전탐색&lt;/b&gt;으로 길찾기를 한다면 어떻게 될까를 고민해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 가능한 모든 경로를 전부 탐색&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 경로의 수는 그래프 크기가 조금만 커져도 기하급수적으로 증가&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 즉, O(노드수 ^ 경로길이) 수준의 비현실적인 연산량이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;팀 프로젝트를 진행할 때는 보통 1,000건, 10,000건 정도의 데이터를 다뤘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 정도면 사실 O(n&amp;sup2;)이라도 그냥 돌아가는 규모다. JOIN이 여러 개 있어도 눈에 띄게 느려지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 작은 규모에서는 비효율적인 알고리즘도 성능이 버텨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇지만, 실제 기업들은 1억, 10억 건, 매일 수백만 건씩 쌓이는 데이터, .. 이런 규모가 디폴트값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇기 때문에 작은 규모에서는 티가 안 나던 알고리즘 차이가 실제 서비스에서는 엄청난 성능 차이로 드러나게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;첫 개발 프로젝트에서 DB 설계를 할 때 '기능 별로 테이블을 최대한 나누는 게 좋다'라고 생각했다. 그래서 유저 관련 테이블만 3개가 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;그런데 개발을 진행하면서 보니 사실 하나로 묶을 수 있는 구조였고, 결과적으로 API 하나를 호출할 때마다 JOIN이 3~4개씩 발생했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;작은 데이터에서는 문제가 없었지만, 만약 데이터가 1억, 10억 건이었다면?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- JOIN이 많아질수록 연산량이 곱셈처럼 증가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- 인덱스가 없으면 풀스캔 발생&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;- 잘못 나눈 테이블 = 성능 병목&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 즉, 불필요하게 나눈 테이블은 JOIN 증가 &amp;amp; 성능 저하의 원인이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;물론, JOIN이 많다고 무조건 안 좋은 것은 아니며 이는 엔티티를 잘못 설계해서 발행하는 JOIN이 문제인 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 비즈니스 요구사항을 구조적으로 해결할 수 있게 된다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;실제 개발에서는 문제를 기술적으로 해결하기 위해서는 문제 자체를 구조화하는 사고방식이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;콘텐츠 저장 앱을 개발할 때, 오버레이 아이콘을 누르면&amp;nbsp;현재 보고 있던 웹사이트의 URL과 AI 요약 내용을 저장하는 기능을 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그런데 버튼을 누르면 10초가 넘도록 흰 화면이 유지되는 UI 프리징 현상이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;처음에는 '저장하는데 10초 정도 걸리는구나, 그냥 로딩 UI로 구현하면 되겠지'라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇지만, 10초는 너무 길고 사용자가 바로 불편함을 느끼는 수준이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그래서 다시 문제를 분석해 봤다. 알고 보니 메인 스레드에서 URL 저장, AI 요약 요청, DB 저장이 동시에 실행되는 구조였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이걸 해결하기 위해서 네트워크 요청을 비동기로 분리하고 작업을 순차 처리하는 작업 큐 구조로 재구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그 결과 오버레이 버튼을 누르자마자 다시 보고 있던 화면으로 돌아오는 수준으로 개선이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 과정을 다시 되돌아보면, 결국 문제를 분해하고 흐름을 정리하는 '알고리즘적 사고방식'이 적용된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이처럼 알고리즘은 문제를 쪼개고 핵심 원인을 찾는 능력을 길러준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 대규모 서비스에서는 효율이 곧 비용이 된다&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;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- DB 검색 속도 =&amp;gt; 서버 증설 여부 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- API 처리 속도 =&amp;gt; 동시 접속 처리량 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;100명, 1000명일 땐 괜찮더라도, 100만 명 이상이 접속한다면 비용이 수천만 원씩 차이가 날 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;개발 프로젝트를 진행할 때는 보통 나 혼자 또는 팀원 몇 명이 테스트하기 때문에 '수십만 명이 동시에 사용하는 상황'을 고려하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;하지만 기업은 항상 이 상황을 전제로 서비스를 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇기 때문에 효율이 낮은 코드는 곧 비용을 증가시키는 코드가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 데이터 구조를 이해해야 유지보수가 가능해진다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;실제 서비스는 대부분 자료구조 위에 동작한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 브라우저 렌더링 =&amp;gt; 큐/우선순위큐&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- React Fiber =&amp;gt; 트리 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- Redis =&amp;gt; 해시, 셋&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 기능은 스택 2개로 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 웹사이트 방문할 때마다 현재 페이지를 backStack에 push&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 뒤로 가기를 누르면 backStack.pop() -&amp;gt; forwardStack.push()&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 앞으로 가기를 누르면 forwardStack.pop() -&amp;gt; backStack.push()&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 새로운 페이지 방문할 때 forwardStack 초기화 -&amp;gt; 앞으로가기 버튼 비활성화&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;-&amp;gt; 스택 기반 구조이기 때문에 페이지 수와 상관없이 뒤로 가기/앞으로 가기 작업 수행 시 걸리는 시간은 O(1)로 일정하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;방문 기록이 10개든 10,000개든 뒤로 가기를 눌렀을 때 걸리는 시간은 동일하게 빠르다는 의미이며, 브라우저를 사용할 때 지연을 느끼지 않는 핵심 기반이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이것은 리스트(배열)를 사용해 순차적으로 검색하는 것보다 훨씬 좋은 성능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;알고리즘을 공부하면서 단순히 코테를 위한 기술이 아니라, 서비스를 이해하고 설계하고 최적화하는 사고방식이란 점을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/56</guid>
      <comments>https://uoaheu.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 24 Nov 2025 16:28:00 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] MySQL 기본 문법부터 트랜잭션, 락까지 한번에 정리</title>
      <link>https://uoaheu.tistory.com/54</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-004 (3).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx5UrR/btsQ62E5p7R/UhiUE2QFhgUBvIYqusG0Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx5UrR/btsQ62E5p7R/UhiUE2QFhgUBvIYqusG0Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx5UrR/btsQ62E5p7R/UhiUE2QFhgUBvIYqusG0Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx5UrR%2FbtsQ62E5p7R%2FUhiUE2QFhgUBvIYqusG0Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-004 (3).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번에는 MySQL에 대한 기본 문법부터 트랜잭션, 락에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. MySQL 기본 문법&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;SQL 문법은 &quot;무엇을, 어디서, 어떻게 가져올 것인가&quot;의 조합입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(1) SELECT (데이터 조회)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.31.32.png&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3BkLu/btsQ63qs31C/UYjRWWTAM6cuWG5nafMsX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3BkLu/btsQ63qs31C/UYjRWWTAM6cuWG5nafMsX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3BkLu/btsQ63qs31C/UYjRWWTAM6cuWG5nafMsX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3BkLu%2FbtsQ63qs31C%2FUYjRWWTAM6cuWG5nafMsX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;103&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.31.32.png&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- SELECT : 보고 싶은 컬럼&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- FROM : 어디서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- WHERE : 조건&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- ORDER BY : 정렬&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- LIMIT : 개수 제한&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(2) JOIN (테이블 연결)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.31.57.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7RfUl/btsQ5ktIaZH/iU7B1mVn23CTqhwq4BOeC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7RfUl/btsQ5ktIaZH/iU7B1mVn23CTqhwq4BOeC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7RfUl/btsQ5ktIaZH/iU7B1mVn23CTqhwq4BOeC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7RfUl%2FbtsQ5ktIaZH%2FiU7B1mVn23CTqhwq4BOeC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;368&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.31.57.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- INNER JOIN : 양쪽 모두에 존재하는 행만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- LEFT JOIN : 왼쪽 테이블은 다 포함, 오른쪽이 없으면 NULL&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ex) 직원, 부서 테이블을 연결하면, 직원 이름 + 소속 부서명까지 한 번에 조회 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(3) GROUP BY + HAVING (그룹 집계)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.32.19.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEilAd/btsQ5hKuKVA/rwNodzm8TCv2Hs0K4rqrfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEilAd/btsQ5hKuKVA/rwNodzm8TCv2Hs0K4rqrfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEilAd/btsQ5hKuKVA/rwNodzm8TCv2Hs0K4rqrfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEilAd%2FbtsQ5hKuKVA%2FrwNodzm8TCv2Hs0K4rqrfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;105&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.32.19.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- WHERE vs HAVING 구분 : WHERE는 집계 전, HAVING은 집계 후&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- NULL 주의 : COUNT(col)은 NULL 제외, COUNT(*)는 행 개수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(4) 서브쿼리/EXISTS&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.32.45.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZooMl/btsQ5jIg5u2/GDPOD6CYJyowANu86IohwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZooMl/btsQ5jIg5u2/GDPOD6CYJyowANu86IohwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZooMl/btsQ5jIg5u2/GDPOD6CYJyowANu86IohwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZooMl%2FbtsQ5jIg5u2%2FGDPOD6CYJyowANu86IohwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;254&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.32.45.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;254&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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 트랜잭션(Transaction) &amp;mdash; &quot;모두 성공하거나, 전부 취소하기&quot;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-1. 개념&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;트랜잭션이란 여러 개의 SQL 작업을 하나의 논리적 단위로 묶어서,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전부 성공하거나(Commit), 전부 실패하면 되돌리는(Rollback) 기능입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;은행에서 A가 B에게 10만원을 보낸다고 해봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. A 계좌에서 10만원 출금&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. B 계좌에 10만원 입금&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 두 과정 중 하나라도 실패하면, 돈이 사라지거나 생기겠죠 ?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 DB는 이 두 작업을 하나의 묶음으로 처리합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-2. 기본 사용&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.33.21.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3R4cx/btsQ3Xy08nZ/yLgGsK0ZWtJINKOyuawlW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3R4cx/btsQ3Xy08nZ/yLgGsK0ZWtJINKOyuawlW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3R4cx/btsQ3Xy08nZ/yLgGsK0ZWtJINKOyuawlW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3R4cx%2FbtsQ3Xy08nZ%2FyLgGsK0ZWtJINKOyuawlW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;107&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.33.21.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-3. Autocommit과 암묵적 커밋&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- MySQL 기본은 autocommit=1 (쿼리 한 번에 자동 커밋)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 명시적으로 여러 문장을 묶을 땐 START TRANSACTION으로 autocommit 구간을 잠깐 끄는 느낌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- DDL(CREATE/ALTER/DROP)은 암묵적 커밋 유발 &amp;rarr; 트랜잭션 블록 안에 DDL 섞지 말 것&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.38.45.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QwAmH/btsQ5chSMYu/VFV0XO4FsU5Pg7hi8Ts7r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QwAmH/btsQ5chSMYu/VFV0XO4FsU5Pg7hi8Ts7r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QwAmH/btsQ5chSMYu/VFV0XO4FsU5Pg7hi8Ts7r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQwAmH%2FbtsQ5chSMYu%2FVFV0XO4FsU5Pg7hi8Ts7r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;105&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.38.45.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-4. SAVEPOINT로 부분 되돌리기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.39.07.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPSNpr/btsQ5UHPwTY/KJDYHgiZ7a5AGTkrXg8UbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPSNpr/btsQ5UHPwTY/KJDYHgiZ7a5AGTkrXg8UbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPSNpr/btsQ5UHPwTY/KJDYHgiZ7a5AGTkrXg8UbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPSNpr%2FbtsQ5UHPwTY%2FKJDYHgiZ7a5AGTkrXg8UbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;127&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.39.07.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;부분 롤백 시나리오가 필요한 곳에서 유용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2-5. 송금 예시&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1) 비즈니스 키로 중복 방지(멱등성)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- transfer_id 같은 고유 요청 ID를 테이블에 UNIQUE로 저장&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 같은 요청이 재전송돼도 한 번만 처리&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.36.24.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXIJNx/btsQ5952nSJ/QTh8Fvkl6Z0YhKroYUN3VK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXIJNx/btsQ5952nSJ/QTh8Fvkl6Z0YhKroYUN3VK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXIJNx/btsQ5952nSJ/QTh8Fvkl6Z0YhKroYUN3VK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXIJNx%2FbtsQ5952nSJ%2FQTh8Fvkl6Z0YhKroYUN3VK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;64&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.36.24.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2) 잔액 충분성 체크 + 업데이트를 한 트랜잭션으로&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.36.47.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TljJD/btsQ5i3IuDR/I93J0DEDvd1uhM6M7IUbwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TljJD/btsQ5i3IuDR/I93J0DEDvd1uhM6M7IUbwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TljJD/btsQ5i3IuDR/I93J0DEDvd1uhM6M7IUbwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTljJD%2FbtsQ5i3IuDR%2FI93J0DEDvd1uhM6M7IUbwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;126&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.36.47.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3) 에러 처리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 예외 발생 시 반드시 ROLLBACK&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 애플리케이션 레벨에서 재시도 정책(특히 데드락/타임아웃)에 대비&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 락(Lock) &amp;mdash; &quot;다른 사람이 동시에 건드리지 못하게&quot;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3-1. 개념&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;락은 데이터 일관성을 지키기 위해, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;특정 데이터에 잠금을 걸어 동시 접근을 제한하는 기능입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;은행에서 A의 돈을 옮기는 중인데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다른 트랜잭션이 동시에 A의 잔액을 수정하면?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;rarr; 값이 꼬이기 때문에 MySQL은 잠금(lock)을 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3-2.&amp;nbsp;락의 종류&lt;/b&gt;&lt;/span&gt;&lt;/h4&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;font-family: 'Nanum Gothic';&quot;&gt;공유 락 (Shared Lock)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;읽기는 허용, 수정은 금지 (SELECT 시 발생)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;배타 락 (Exclusive Lock)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;읽기&amp;middot;수정 모두 금지 (UPDATE, DELETE 시 발생)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;테이블 락 / 행 락&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;테이블 전체 vs 특정 행만 잠금&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;3324&quot; data-start=&quot;3285&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3.3 락을 의도적으로 거는 읽기 (Locking Read)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;3324&quot; data-start=&quot;3285&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;- 갱신 전 선점해서 경합을 줄이는 패턴&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.37.42.png&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6F21h/btsQ6ZBBlln/zlcUyMXn5gZmXfXkeZmEKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6F21h/btsQ6ZBBlln/zlcUyMXn5gZmXfXkeZmEKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6F21h/btsQ6ZBBlln/zlcUyMXn5gZmXfXkeZmEKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6F21h%2FbtsQ6ZBBlln%2FzlcUyMXn5gZmXfXkeZmEKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;455&quot; height=&quot;126&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.37.42.png&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;3324&quot; data-start=&quot;3285&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;3607&quot; data-start=&quot;3560&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3.4 UPDATE/DELETE의 잠금 범위는 &quot;조건 + 인덱스&quot;에 달려 있다&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.38.01.png&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhjivN/btsQ6jAAz4D/DDOHzrmVaYjqb0XiLqZsQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhjivN/btsQ6jAAz4D/DDOHzrmVaYjqb0XiLqZsQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhjivN/btsQ6jAAz4D/DDOHzrmVaYjqb0XiLqZsQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhjivN%2FbtsQ6jAAz4D%2FDDOHzrmVaYjqb0XiLqZsQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;63&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.38.01.png&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;3842&quot; data-start=&quot;3774&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;핵심 : 인덱스를 타는 WHERE가 아니면, 불필요하게 넓은 범위를 잠가서 경합/대기/타임아웃의 원인이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3873&quot; data-start=&quot;3844&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;3873&quot; data-start=&quot;3844&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3.5 데드락(Deadlock) 이해 &amp;amp; 대처&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;3873&quot; data-start=&quot;3844&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 서로가 서로의 락을 기다리는 &quot;교착&quot; 상태&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3873&quot; data-start=&quot;3844&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- MySQL은 자동으로 한쪽을 희생(롤백) 시켜 풀어줌 &amp;rarr; 앱에서 재시도 필요&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;피하는 팁&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 항상 같은 순서로 행을 갱신 (예: id 오름차순으로 잠금)&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 큰 트랜잭션을 작게 쪼개기&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 불필요한 범위 잠금 줄이기(좋은 인덱스, 정확한 WHERE)&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 읽기-쓰기 혼합 시, 쓰기 대상은 먼저 잠그기(FOR UPDATE)&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3969&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3.6 테이블 락 (가급적 지양)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.38.22.png&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tjxnH/btsQ4kU0qlx/0SPuSqOlJG13WOcZfv1wk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tjxnH/btsQ4kU0qlx/0SPuSqOlJG13WOcZfv1wk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tjxnH/btsQ4kU0qlx/0SPuSqOlJG13WOcZfv1wk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtjxnH%2FbtsQ4kU0qlx%2F0SPuSqOlJG13WOcZfv1wk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;447&quot; height=&quot;63&quot; data-filename=&quot;스크린샷 2025-10-10 오후 8.38.22.png&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- InnoDB의 장점(행 단위 잠금)을 포기하게 되므로 특수한 상황에서만 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 유지보수성/동시성에 부정적 영향이 큼&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 글은 핵심 요약정리와 함께 마무리하겠습니다 !&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- SELECT/JOIN/GROUP BY/HAVING : 필요한 컬럼만 선택&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- WHERE - GROUP BY - HAVING 순으로 사고&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 트랜잭션 : START TRANSACTION ~ COMMIT/ROLLBACK으로 작업 단위 보장&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 락 : FOR UPDATE/FOR SHARE로 의도적 잠금&lt;/span&gt;&lt;/p&gt;</description>
      <category>SQL</category>
      <category>mysql</category>
      <category>MySQL 문법</category>
      <category>SQL</category>
      <category>sql 문법</category>
      <category>SQL 트랜잭션</category>
      <category>멀티캠퍼스it부트캠프</category>
      <category>부트캠프후기</category>
      <category>엘지유플러스유레카프론트엔드</category>
      <category>유레카3기</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/54</guid>
      <comments>https://uoaheu.tistory.com/54#entry54comment</comments>
      <pubDate>Fri, 10 Oct 2025 20:28:25 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] MySQL에서 피벗테이블 만들기</title>
      <link>https://uoaheu.tistory.com/53</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-004 (2).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuWkT8/btsQVCHaA3O/06h72o18cHqyr5qZ5kAGD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuWkT8/btsQVCHaA3O/06h72o18cHqyr5qZ5kAGD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuWkT8/btsQVCHaA3O/06h72o18cHqyr5qZ5kAGD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuWkT8%2FbtsQVCHaA3O%2F06h72o18cHqyr5qZ5kAGD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-004 (2).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 주에는 MySQL로 피벗을 만드는 방법을 배웠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;배운 내용을 복습할 겸, 엑셀&amp;middot;파이썬 없이 순수 SQL만으로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&amp;ldquo;월 &amp;times; 카테고리&amp;rdquo; 매출 표&lt;/b&gt;를 만들고 &lt;b&gt;월 합계/연도 합계/전체 합계&lt;/b&gt;까지 한 번에 붙이는 과정을 다시 정리해보겠습니다 !&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;특히 MySQL의 ONLY_FULL_GROUP_BY 모드에서도 깨끗하게 동작하도록 서브쿼리에서 연&amp;middot;월을 먼저 계산하는 패턴을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;이번 글에서는 다음과 같은 테이블을 MySQL로 만들게 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;671&quot; data-start=&quot;478&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;524&quot; data-start=&quot;478&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&quot;월 &amp;times; 카테고리(전자/의류/식품)&quot; 교차표를 열 피벗으로 만들기&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;587&quot; data-start=&quot;525&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;WITH ROLLUP으로 월 합계, 연도 합계, 전체 합계를 자동으로 붙이기&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;626&quot; data-start=&quot;588&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;GROUPING() 함수로 합계 행을 라벨링&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;671&quot; data-start=&quot;627&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ONLY_FULL_GROUP_BY 환경에서도 안전하게 동작&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-30 오전 1.03.53.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biNA2I/btsQVry0PHI/laJKeauHKDSarxJtkoX3Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biNA2I/btsQVry0PHI/laJKeauHKDSarxJtkoX3Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biNA2I/btsQVry0PHI/laJKeauHKDSarxJtkoX3Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiNA2I%2FbtsQVry0PHI%2FlaJKeauHKDSarxJtkoX3Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;215&quot; data-filename=&quot;스크린샷 2025-09-30 오전 1.03.53.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;0. 핵심 아이디어 한눈에 정리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(1) 피벗 (열로 펼치기)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;: &lt;span style=&quot;text-align: left;&quot;&gt;SUM(CASE WHEN category='전자' THEN amount ELSE 0 END) 형태로 조건부 집계를 사용합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(2) 합계 자동화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;: &lt;span style=&quot;text-align: left;&quot;&gt;GROUP BY ... WITH ROLLUP을 붙이면 소계/총계가 자동 생성됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(3) 합계 식별/라벨링&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;: GROUPING(col)이 합계 행이면 1, 일반 행이면 0을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;959&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(4) ONLY_FULL_GROUP_BY 대응&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;: DATE_FORMAT(sale_date, '%Y-%m')처럼 원본 컬럼을 SELECT에서 직접 참조하면 에러가 날 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서브쿼리에서 YEAR(sale_date) AS yr, MONTH(sale_date) AS mo를 먼저 만들어 두고, 바깥 쿼리에서는 yr, mo만 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;633&quot; data-start=&quot;616&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;633&quot; data-start=&quot;616&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1182&quot; data-start=&quot;1162&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;왜 서브쿼리(yr, mo)인가?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1489&quot; data-start=&quot;1184&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ONLY_FULL_GROUP_BY가 켜져 있으면, GROUP BY YEAR(sale_date), MONTH(sale_date)를 하면서 &lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1489&quot; data-start=&quot;1184&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;SELECT DATE_FORMAT(sale_date, '%Y-%m') 같은 원본 컬럼/함수를 함께 쓰면 함수적 종속성 위반으로 에러가 납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1489&quot; data-start=&quot;1184&quot; data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;따라서 안쪽 서브쿼리에서 YEAR(sale_date) AS yr, MONTH(sale_date) AS mo를 명시 컬럼으로 뽑아두고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1489&quot; data-start=&quot;1184&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;바깥쪽에선 yr, mo만으로 집계/라벨링을 합니다. 이 패턴이 가장 깔끔합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;691&quot; data-start=&quot;387&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;720&quot; data-start=&quot;698&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. [준비] 예제 스키마 &amp;amp; 더미 데이터&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1759160814885&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- (옵션) 예제 전용 DB
-- CREATE DATABASE IF NOT EXISTS sales_demo
--   DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
-- USE sales_demo;

-- 기존 테이블이 있다면 삭제(없어도 경고만 나오고 통과)
DROP TABLE IF EXISTS sales;

-- 매출 테이블
CREATE TABLE sales (
  id         INT PRIMARY KEY AUTO_INCREMENT,
  sale_date  DATE NOT NULL,
  region     ENUM('서울','부산','대전','대구','광주') NOT NULL,
  channel    ENUM('온라인','오프라인') NOT NULL,
  category   ENUM('전자','의류','식품') NOT NULL,
  product    VARCHAR(50) NOT NULL,
  quantity   INT NOT NULL,
  unit_price DECIMAL(12,2) NOT NULL,
  -- 금액 = 수량 * 단가 (생성 컬럼)
  amount     DECIMAL(14,2) AS (quantity * unit_price) STORED,

  KEY idx_date (sale_date),
  KEY idx_region_channel (region, channel),
  KEY idx_category (category),
  KEY idx_date_category (sale_date, category)
) ENGINE=InnoDB;

-- 2025-08, 2025-09 / 서울&amp;middot;부산 / 온&amp;middot;오프 / 전자&amp;middot;의류&amp;middot;식품 더미 데이터
INSERT INTO sales (sale_date, region, channel, category, product, quantity, unit_price) VALUES
('2025-08-05','서울','온라인','전자','노트북A', 5, 300000),
('2025-08-05','서울','온라인','의류','셔츠A',   8,  50000),
('2025-08-05','서울','온라인','식품','과자A',  20,   8000),
('2025-08-06','서울','오프라인','전자','노트북B', 3, 320000),
('2025-08-06','서울','오프라인','의류','셔츠B',   6,  60000),
('2025-08-06','서울','오프라인','식품','과자B',  15,   9000),

('2025-08-07','부산','온라인','전자','노트북C', 4, 280000),
('2025-08-07','부산','온라인','의류','셔츠C',   7,  45000),
('2025-08-07','부산','온라인','식품','과자C',  18,   7000),
('2025-08-08','부산','오프라인','전자','노트북D', 2, 310000),
('2025-08-08','부산','오프라인','의류','셔츠D',   5,  55000),
('2025-08-08','부산','오프라인','식품','과자D',  12,   8500),

('2025-09-10','서울','온라인','전자','노트북E', 6, 310000),
('2025-09-10','서울','온라인','의류','셔츠E',  10,  52000),
('2025-09-10','서울','온라인','식품','과자E',  22,   8200),
('2025-09-11','서울','오프라인','전자','노트북F', 4, 330000),
('2025-09-11','서울','오프라인','의류','셔츠F',   7,  61000),
('2025-09-11','서울','오프라인','식품','과자F',  16,   9200),

('2025-09-12','부산','온라인','전자','노트북G', 5, 285000),
('2025-09-12','부산','온라인','의류','셔츠G',   8,  46000),
('2025-09-12','부산','온라인','식품','과자G',  19,   7300),
('2025-09-13','부산','오프라인','전자','노트북H', 3, 315000),
('2025-09-13','부산','오프라인','의류','셔츠H',   6,  56000),
('2025-09-13','부산','오프라인','식품','과자H',  13,   8700);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-30 오전 9.15.01.png&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccIQdS/btsQUQeRwhA/UizmLVyu1XMe9USaczilmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccIQdS/btsQUQeRwhA/UizmLVyu1XMe9USaczilmk/img.png&quot; data-alt=&quot;상위 20개 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccIQdS/btsQUQeRwhA/UizmLVyu1XMe9USaczilmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccIQdS%2FbtsQUQeRwhA%2FUizmLVyu1XMe9USaczilmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;479&quot; height=&quot;330&quot; data-filename=&quot;스크린샷 2025-09-30 오전 9.15.01.png&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;상위 20개 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1759160877013&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*) AS rows, SUM(amount) AS total_amount FROM sales;
-- 기대: rows=24, total_amount=13,853,400&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.48.09.png&quot; data-origin-width=&quot;118&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9jZA0/btsQVAJksrj/dA6gN9OHcUudQZPV4VLSk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9jZA0/btsQVAJksrj/dA6gN9OHcUudQZPV4VLSk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9jZA0/btsQVAJksrj/dA6gN9OHcUudQZPV4VLSk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9jZA0%2FbtsQVAJksrj%2FdA6gN9OHcUudQZPV4VLSk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;118&quot; height=&quot;47&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.48.09.png&quot; data-origin-width=&quot;118&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;3145&quot; data-start=&quot;3107&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 월 &amp;times; 카테고리 피벗 + 월합계 + 연도합계 + 전체합계&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3156&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;패턴 요약&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3156&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 서브쿼리에서 YEAR(sale_date) AS yr, MONTH(sale_date) AS mo 뽑기&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3156&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 바깥에서 GROUP BY yr, mo WITH ROLLUP&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3156&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. SUM(CASE WHEN category=... THEN amount END)로 열 피벗&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3156&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. GROUPING(yr), GROUPING(mo)로 합계 행 라벨링&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3156&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5. 정렬은 ORDER BY GROUPING(yr), yr, GROUPING(mo), mo&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759160938017&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
  CASE
    WHEN GROUPING(yr)=1 AND GROUPING(mo)=1 THEN '전체 합계'   -- grand total
    WHEN GROUPING(mo)=1 THEN CONCAT(yr, '년 합계')           -- year subtotal
    ELSE CONCAT(yr, '-', LPAD(mo, 2, '0'))                   -- e.g. 2025-08
  END AS '월 \\ 카테고리',

  -- 카테고리별 금액(열 피벗)
  SUM(CASE WHEN category='전자' THEN amount ELSE 0 END) AS 전자,
  SUM(CASE WHEN category='의류' THEN amount ELSE 0 END) AS 의류,
  SUM(CASE WHEN category='식품' THEN amount ELSE 0 END) AS 식품,

  -- 월 합계
  SUM(amount) AS '월 매출 합계'
FROM (
  SELECT YEAR(sale_date) AS yr,
         MONTH(sale_date) AS mo,
         category,
         amount
  FROM sales
) s
GROUP BY yr, mo WITH ROLLUP
ORDER BY
  GROUPING(yr), yr,         -- 일반 연도 먼저, 마지막에 전체 합계
  GROUPING(mo), mo;         -- 각 연도 안에서 월들 먼저, 그 다음 연도 합계&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.49.09.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS1VKk/btsQTlzMA9d/eYV6JtPJJ3OZY5SrrMWsX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS1VKk/btsQTlzMA9d/eYV6JtPJJ3OZY5SrrMWsX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS1VKk/btsQTlzMA9d/eYV6JtPJJ3OZY5SrrMWsX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS1VKk%2FbtsQTlzMA9d%2FeYV6JtPJJ3OZY5SrrMWsX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;90&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.49.09.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;4993&quot; data-start=&quot;4907&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;연도 합계 행을 숨기고 싶으면 마지막에 아래를 추가합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;HAVING NOT (GROUPING(yr)=0 AND GROUPING(mo)=1);&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5086&quot; data-start=&quot;5064&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;옵션 : 출력 포맷/필터링/수량 피벗&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5086&quot; data-start=&quot;5064&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(1) 숫자 포맷(3자리 콤마) : 시각화를 위해 표시용으로만 FORMAT()을 씁니다(문자형 반환 주의)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759190963211&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ..., 
    FORMAT(SUM(amount), 0) AS '월 매출 합계(표시용)'
FROM ...&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;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(2) 특정 연도만 보고 싶을 때 : 인덱스 친화적으로 범위 조건을 권장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759191002968&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM sales 
WHERE sale_date &amp;gt;= '2025-01-01' AND sale_date &amp;lt; '2026-01-01'&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;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(3) 수량 기준 피벗 : amount 대신 quantity로 동일 패턴을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759191016485&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SUM(CASE WHEN category='전자' THEN quantity ELSE 0 END) AS 전자_수량&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-end=&quot;4766&quot; data-start=&quot;4732&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. [응용 예시 1] 지역 &amp;times; 채널 피벗 + 지역합계 + 전체합계&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1759160986419&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
  CASE WHEN GROUPING(region)=1 THEN '전체 합계' ELSE region END AS '지역 \\ 채널',
  SUM(CASE WHEN channel='온라인'  THEN amount ELSE 0 END) AS 온라인,
  SUM(CASE WHEN channel='오프라인' THEN amount ELSE 0 END) AS 오프라인,
  SUM(amount) AS '지역 매출 합계'
FROM sales
GROUP BY region WITH ROLLUP
ORDER BY GROUPING(region), region;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.49.59.png&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AxEao/btsQTSdhOaO/obSk1NQIKQ97vxBpLZ8Z41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AxEao/btsQTSdhOaO/obSk1NQIKQ97vxBpLZ8Z41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AxEao/btsQTSdhOaO/obSk1NQIKQ97vxBpLZ8Z41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAxEao%2FbtsQTSdhOaO%2FobSk1NQIKQ97vxBpLZ8Z41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;274&quot; height=&quot;76&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.49.59.png&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5277&quot; data-start=&quot;5240&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. [응용 예시 2] 카테고리 &amp;times; 채널 &amp;mdash; 건수와 매출을 동시에&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1759161020828&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
  CASE WHEN GROUPING(category)=1 THEN '전체 합계' ELSE category END AS '카테고리 \\ 채널',

  -- 주문 건수(행 수)
  COUNT(CASE WHEN channel='온라인'  THEN 1 END)  AS '온라인 건수',
  COUNT(CASE WHEN channel='오프라인' THEN 1 END) AS '오프라인 건수',

  -- 매출 금액
  SUM(CASE WHEN channel='온라인'  THEN amount ELSE 0 END) AS '온라인 매출',
  SUM(CASE WHEN channel='오프라인' THEN amount ELSE 0 END) AS '오프라인 매출',

  SUM(amount) AS '카테고리 합계 매출'
FROM sales
GROUP BY category WITH ROLLUP
ORDER BY GROUPING(category), category;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.50.42.png&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OJW9k/btsQTpJaVcW/ghobDiHzxWRFoWQZ1V4FLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OJW9k/btsQTpJaVcW/ghobDiHzxWRFoWQZ1V4FLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OJW9k/btsQTpJaVcW/ghobDiHzxWRFoWQZ1V4FLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOJW9k%2FbtsQTpJaVcW%2FghobDiHzxWRFoWQZ1V4FLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;90&quot; data-filename=&quot;스크린샷 2025-09-30 오전 12.50.42.png&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;5. 정렬 &amp;amp; 라벨링 핵심 정리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 합계 행 인지 : GROUPING(col) -&amp;gt; 합계행이면 1, 일반행이면 0.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 라벨링 규칙&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; &amp;nbsp;- 월 라벨 : CONCAT(yr, '-', LPAD(mo,2,'0'))&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; &amp;nbsp;- 연도 합계 : GROUPING(mo)=1&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp; &amp;nbsp;- 전체 합계 : GROUPING(yr)=1 AND GROUPING(mo)=1&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 정렬 추천 패턴일반 행 -&amp;gt; 연도합계 -&amp;gt; 전체합계 순서로 깔끔히 정렬됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5913&quot; data-start=&quot;5898&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- ORDER BY GROUPING(yr), yr, GROUPING(mo), mo&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;6236&quot; data-start=&quot;6226&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6. 성능/운영 팁&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(1) 인덱스&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 시계열 집계에 sale_date(날짜), 축약 피벗에 region(지역), channel(채널), 카테고리 집계에 sale_date(날짜), category(카테고리) 등이 유리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759191799996&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 월&amp;times;카테고리 피벗 (월 라벨은 ym 생성 컬럼 사용 시 최적)
CREATE INDEX idx_sales_ym_cat ON sales (/* ym 또는 sale_date */, category);

-- 지역&amp;times;채널 피벗
CREATE INDEX idx_sales_region_channel_ym ON sales (region, channel, /* ym 또는 sale_date */);

-- 드릴다운/세부 조회
CREATE INDEX idx_sales_date_category ON sales (sale_date, category);&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;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 대량 데이터라면 EXPLAIN으로 type/rows/key/Extra 확인 필수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* type은 range 이상, rows는 기간 제한으로 낮아져야 합니다. key가 의도한 인덱스인지 확인하고, Extra에 Using temporary/Using filesort가 잦으면 인덱스 순서와 GROUP BY/ORDER BY를 재검토합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759191944326&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN
SELECT YEAR(sale_date)*100+MONTH(sale_date) AS ym, category, SUM(amount)
FROM sales
WHERE sale_date &amp;gt;= '2025-08-01' AND sale_date &amp;lt; '2025-10-01'
GROUP BY ym, category;&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(2) 생성 컬럼(amount)&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 읽기 많은 집계에서 계산 비용을 줄이고 일관성을 확보합니다.&lt;br /&gt;* 읽기 많은 집계에서는 amount = quantity * unit_price를 생성(STORED) 컬럼으로 두면 매 쿼리마다 곱셈을 반복하지 않아 CPU 사용량과 변환 비용을 줄일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1759192049220&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 이미 테이블이 있다면
ALTER TABLE sales
  ADD COLUMN amount DECIMAL(14,2) AS (quantity * unit_price) STORED;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(3) 동적 피벗&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 카테고리/채널이 자주 바뀌면 고정 컬럼 피벗은 유지보수 비용이 큽니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* 동적 컬럼이 필요하면 애플리케이션 레이어에서 피벗하거나, MySQL에선 동적 SQL(프로시저)로 생성합니다.&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(4) ROLLUP 과한 합계 숨기기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- ROLLUP은 월 합계 + 연도 합계 + 전체 합계를 모두 만듭니다. 필요 없는 합계는 HAVING으로 정리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;* HAVING NOT (조건)으로 특정 합계 행을 제거해 시각적으로 단순화할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759192155852&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- ex) 연도 합계 행만 제거
HAVING NOT (GROUPING(yr)=0 AND GROUPING(mo)=1);

-- ex) 전체 합계 행까지 제거
HAVING GROUPING(yr)=0 AND GROUPING(mo)=0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 글의 핵심은 서브쿼리에서 집계 키(yr/mo)를 먼저 생성해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ONLY_FULL_GROUP_BY 환경에서도 안전하게 ROLLUP + 피벗을 구현하는 패턴입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 패턴 하나면 &quot;월&amp;times;카테고리&quot;, &quot;지역&amp;times;채널&quot;, &quot;상품군&amp;times;고객등급&quot; 등 대부분의 경영 리포트를&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;순수 SQL로 재사용 가능하게 만들 수 있습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>SQL</category>
      <category>mysql</category>
      <category>mysql pivot</category>
      <category>mysql 피벗테이블</category>
      <category>mysql로 피벗테이블만들기</category>
      <category>멀티캠퍼스it부트캠프</category>
      <category>부트캠프후기</category>
      <category>엘지유플러스유레카프론트엔드</category>
      <category>유레카3기</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/53</guid>
      <comments>https://uoaheu.tistory.com/53#entry53comment</comments>
      <pubDate>Tue, 30 Sep 2025 01:12:33 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 알고리즘 BFS &amp;amp; DFS 정리</title>
      <link>https://uoaheu.tistory.com/52</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-004 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/POPFJ/btsQKJmKpSR/Wi8Pqwkk0tq9EbGmLo4mo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/POPFJ/btsQKJmKpSR/Wi8Pqwkk0tq9EbGmLo4mo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/POPFJ/btsQKJmKpSR/Wi8Pqwkk0tq9EbGmLo4mo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPOPFJ%2FbtsQKJmKpSR%2FWi8Pqwkk0tq9EbGmLo4mo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-004 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;알고리즘 문제를 풀다 보면 격자, 네트워크, 지도, 트리 등 연결 구조를 다루는 경우가 많습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이때 필요한 것이 바로 DFS(깊이 우선 탐색)과 BFS(너비 우선 탐색)입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. DFS (Depth-First Search, 깊이 우선 탐색)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;출발점에서 한 방향을 정하면 끝까지 깊게 들어간 뒤, 더 이상 갈 수 없을 때 다시 되돌아와서 다른 경로를 탐색하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;재귀 함수나 스택(Stack)으로 구현할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ex) 미로에 들어간 쥐가 한쪽 길을 끝까지 파고 들어갔다가 막히면 다시 돌아와서 다른 길을 찾는 것과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/458152458&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/6JnnH/hyZJCrlbxv/63klG9gv9qN5rC4u4HKjA0/img.jpg?width=1010&amp;amp;height=808&amp;amp;face=0_0_1010_808,https://scrap.kakaocdn.net/dn/RUZbw/hyZJlYd7Zs/zZU3n1JYVlhZdh2NIEYKF0/img.jpg?width=1010&amp;amp;height=808&amp;amp;face=0_0_1010_808&quot; data-video-width=&quot;860&quot; data-video-height=&quot;688&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;688&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'uoaheu 님의 블로그'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/458152458?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;688&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 장점 : 구현이 간단하고 코드가 짧음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 단점 : 재귀 호출로 인해 입력 크기가 크면 스택 오버플로우 위험&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;size14&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. BFS (Breadth-First Search, 너비 우선 탐색)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;출발점에서 시작하여 가까운 칸부터 차례로 방문하고, 그다음 레벨로 확장해 나가는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;큐(Queue)를 사용하여 구현합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ex) 물을 한 방울 떨어뜨리면 파동이 퍼지듯 모든 방향으로 동시에 퍼져나가는 것과 같습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/458152455&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/e9lii/hyZJxj7YbM/V2J6IXqAEIK6pHHSEiD6t0/img.jpg?width=1008&amp;amp;height=806&amp;amp;face=0_0_1008_806,https://scrap.kakaocdn.net/dn/iEFuz/hyZJQCminP/IACCBRh2Xt7xrzS7cPOkK0/img.jpg?width=1008&amp;amp;height=806&amp;amp;face=0_0_1008_806&quot; data-video-width=&quot;860&quot; data-video-height=&quot;688&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;688&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'uoaheu 님의 블로그'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/458152455?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;688&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 장점 : 최단 거리/최소 시간문제 해결에 탁월&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 단점 : 구현이 DFS보다 다소 길어질 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 적합한 문제 : 최단 거리, 최소 시간 문제&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 구현 방식의 차이&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DFS : 깊게 들어가는 성격 &amp;rarr; 재귀 호출/스택 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;BFS : 가까운 곳부터 탐색하는 성격 &amp;rarr; 큐 사용&lt;/span&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;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;구분&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DFS&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;BFS&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자료구조&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스택(재귀/직접)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;큐&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;탐색 순서&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;깊이 우선&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;너비 우선&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최단거리 보장&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;O&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;대표 문제&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;알파벳, 연구소&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;토마토, 탈출&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHv0iS/btsQMFDgOUI/0pr3B0xMnIIshrMK0Z01ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHv0iS/btsQMFDgOUI/0pr3B0xMnIIshrMK0Z01ik/img.png&quot; data-origin-width=&quot;1049&quot; data-origin-height=&quot;913&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.12&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.5326%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHv0iS/btsQMFDgOUI/0pr3B0xMnIIshrMK0Z01ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHv0iS%2FbtsQMFDgOUI%2F0pr3B0xMnIIshrMK0Z01ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1049&quot; height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsRozU/btsQJyTIQnc/Kb27IAbj0SH7crnLxTTqd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsRozU/btsQJyTIQnc/Kb27IAbj0SH7crnLxTTqd1/img.png&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;877&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.88&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.3046%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsRozU/btsQJyTIQnc/Kb27IAbj0SH7crnLxTTqd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsRozU%2FbtsQJyTIQnc%2FKb27IAbj0SH7crnLxTTqd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1003&quot; height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;DFS / BFS&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. 수도코드&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;DFS (재귀)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;dfs(r, c):
  방문[r][c] = true
  size = 1
  for 방향 d:
    nr, nc = r+dr[d], c+dc[d]
    if (nr, nc)가 범위 안이고, 방문X이고, map[nr][nc]==1:
       size += DFS(nr, nc)
  return 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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;BFS (큐)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;bfs(sr, sc):
  방문[sr][sc] = true
  큐에 (sr, sc) 삽입
  size = 0
  while 큐가 비어있지 않음:
    (r, c) = 큐.pop()
    size += 1
    for 방향 d:
      nr, nc = r+dr[d], c+dc[d]
      if (nr, nc)가 범위 안이고, 방문X이고, map[nr][nc]==1:
         방문[nr][nc] = true
         큐에 (nr, nc) 삽입
  return size&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;5. 예시 문제 풀이&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;BOJ 1926 그림&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1926&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1926&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오후 2.00.50.png&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;1126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bquSwu/btsQMrLWeRG/Qr92XLygGwH8hqo19ZM8Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bquSwu/btsQMrLWeRG/Qr92XLygGwH8hqo19ZM8Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bquSwu/btsQMrLWeRG/Qr92XLygGwH8hqo19ZM8Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbquSwu%2FbtsQMrLWeRG%2FQr92XLygGwH8hqo19ZM8Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2102&quot; height=&quot;1126&quot; data-filename=&quot;스크린샷 2025-09-23 오후 2.00.50.png&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;1126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오후 2.01.21.png&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bllZXE/btsQMeTtpHE/JxDtlmMmAkD9yMuXkHKf1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bllZXE/btsQMeTtpHE/JxDtlmMmAkD9yMuXkHKf1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bllZXE/btsQMeTtpHE/JxDtlmMmAkD9yMuXkHKf1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbllZXE%2FbtsQMeTtpHE%2FJxDtlmMmAkD9yMuXkHKf1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2102&quot; height=&quot;508&quot; data-filename=&quot;스크린샷 2025-09-23 오후 2.01.21.png&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;풀이 과정&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 이중 for문으로 모든 칸 확인&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 아직 방문하지 않았고 1이면 새로운 그림 발견&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. DFS 또는 BFS로 그림 크기(size) 탐색&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 그림 개수 증가, 최대 크기 갱신&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Jg7Q/btsQMEqU8wC/MCsrLOw9O2BB2r4H2Xf5b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Jg7Q/btsQMEqU8wC/MCsrLOw9O2BB2r4H2Xf5b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Jg7Q/btsQMEqU8wC/MCsrLOw9O2BB2r4H2Xf5b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Jg7Q%2FbtsQMEqU8wC%2FMCsrLOw9O2BB2r4H2Xf5b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1306&quot; height=&quot;909&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(1) DFS 풀이&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758604343648&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Main {  
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};
    static int[][] arr;
    static int n, m;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        arr = new int[n][m];
        int count = 0;
        int max = 0;

        // 각자 방에서 갈 수 있는 값 확인
        for(int i = 0; i &amp;lt; n; i++) {
            for(int j = 0; j &amp;lt; m; j++) {
                arr[i][j] = sc.nextInt();
            }
        }

        for(int i = 0; i &amp;lt; n; i++) {
            for(int j = 0; j &amp;lt; m; j++) {
                if(arr[i][j] == 1) {
                    int sum = check(i, j);
                    count++;
                    if(max &amp;lt; sum) {
                        max = sum;
                    }
                }
            }
        }
        System.out.println(count);
        System.out.println(max);
    }
    
    static int check(int x, int y) {
        int xx = x;
        int yy = y;
        arr[xx][yy] = 0; // 방문 체크
        int size = 1;
        for(int i = 0; i &amp;lt; 4; i++) {
            int cx = xx + dx[i];
            int cy = yy + dy[i];
            if(cx &amp;gt;= 0 &amp;amp;&amp;amp; cy &amp;gt;= 0 &amp;amp;&amp;amp; cx &amp;lt; n &amp;amp;&amp;amp; cy &amp;lt; m &amp;amp;&amp;amp; arr[cx][cy] != 0) {
                arr[cx][cy] = 0;
                size += check(cx, cy);
            }
        }
        return 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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(2) BFS 풀이&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758604399235&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Main {
	static int N, M;
	static int[][] arr;
	static boolean[][] visited;
	static int[] dx = { 0, 0, -1, 1 };
	static int[] dy = { -1, 1, 0, 0 };

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		M = sc.nextInt();
		
		arr = new int[N][M];
		visited = new boolean[N][M];

		// 0은 색칠이 안된 부분, 1은 색칠이 된 부분
		for (int i = 0; i &amp;lt; N; i++) {
			for (int j = 0; j &amp;lt; M; j++) {
				arr[i][j] = sc.nextInt();
			}
		}

		int numPictures = 0; // 총 그림의 수
		int maxSize = 0; // 가장 큰 그림의 크기
		
		for (int i = 0; i &amp;lt; N; i++) {
			for (int j = 0; j &amp;lt; M; j++) {
				if (arr[i][j] == 1 &amp;amp;&amp;amp; !visited[i][j]) {
					numPictures++;
					int size = bfs(i, j);
					if (size &amp;gt; maxSize) {
						maxSize = size;
					}
				}
			}
		}
		
		System.out.println(numPictures);
		System.out.println(maxSize);

	}

	static int bfs(int x, int y) {
		Queue&amp;lt;int[]&amp;gt; queue = new ArrayDeque&amp;lt;&amp;gt;();
		queue.add(new int[] { x, y });

		visited[x][y] = true;
		int size = 1; // 현재 그림 크기

		while (!queue.isEmpty()) {
			int[] current = queue.poll();
			int cx = current[0];
			int cy = current[1];

			for (int i = 0; i &amp;lt; 4; i++) {
				int nx = cx + dx[i];
				int ny = cy + dy[i];

				if (nx &amp;gt;= 0 &amp;amp;&amp;amp; ny &amp;gt;= 0 &amp;amp;&amp;amp; nx &amp;lt; N &amp;amp;&amp;amp; ny &amp;lt; M &amp;amp;&amp;amp; !visited[nx][ny] &amp;amp;&amp;amp; arr[nx][ny] == 1) {
					visited[nx][ny] = true;
					queue.add(new int[] { nx, ny });
					size++;
				}
			}
		}
		return 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;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(3) 성능 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오후 2.18.02.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf6PgD/btsQLaqJvIv/ikPcmdmMK02VkAQCgS4Kwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf6PgD/btsQLaqJvIv/ikPcmdmMK02VkAQCgS4Kwk/img.png&quot; data-alt=&quot;DFS, BFS 순&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf6PgD/btsQLaqJvIv/ikPcmdmMK02VkAQCgS4Kwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf6PgD%2FbtsQLaqJvIv%2FikPcmdmMK02VkAQCgS4Kwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1052&quot; height=&quot;122&quot; data-filename=&quot;스크린샷 2025-09-23 오후 2.18.02.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DFS, BFS 순&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 문제에서는 BFS 풀이가 DFS보다 더 빠르게 동작하는 것을 확인했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 항상 BFS가 DFS보다 빠른 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- DFS: 재귀 호출을 사용해 코드가 간단하지만, 깊은 탐색에서는 스택 오버헤드 발생 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- BFS: 큐를 사용해 코드가 길어지지만, 재귀 호출이 없어서 대규모 입력에서도 안정적&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5133&quot; data-start=&quot;5019&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5133&quot; data-start=&quot;5019&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 성능 차이는 입력 크기와 구현 방식(재귀/반복, 자료구조 선택)에 따라 달라집니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 케이스에서는 BFS가 더 좋은 성능을 보였지만, 다른 상황에서는 DFS가 더 유리할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;5133&quot; data-start=&quot;5019&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;5133&quot; data-start=&quot;5019&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;알고리즘 문제를 풀기 앞서 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&quot;최단 거리/최소 시간문제냐?&quot; vs &quot;모든 경우/영역 탐색 문제냐?&quot;를 구분하세요 !&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그에 따라 DFS와 BFS 중 적절한 방법을 빠르게 선택하는 것이 실전 알고리즘 풀이의 핵심입니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>BFS</category>
      <category>DFS</category>
      <category>멀티캠퍼스it부트캠프</category>
      <category>부트캠프후기</category>
      <category>알고리즘</category>
      <category>엘지유플러스유레카프론트엔드</category>
      <category>유레카3기</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/52</guid>
      <comments>https://uoaheu.tistory.com/52#entry52comment</comments>
      <pubDate>Tue, 23 Sep 2025 14:37:12 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 코딩테스트에서 Scanner 대신 BufferedReader를 써야 하는 이유</title>
      <link>https://uoaheu.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-003 (2).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVWU9x/btsQnAp1OAj/iyOyMLkkAFYUq3kH9ZhLA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVWU9x/btsQnAp1OAj/iyOyMLkkAFYUq3kH9ZhLA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVWU9x/btsQnAp1OAj/iyOyMLkkAFYUq3kH9ZhLA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVWU9x%2FbtsQnAp1OAj%2FiyOyMLkkAFYUq3kH9ZhLA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을-입력해주세요_-003 (2).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;알고리즘 문제를 풀다 보면 Scanner를 사용하다가 &lt;b&gt;시간 초과&lt;/b&gt;를 겪는 경우가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 2.48.35.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciq3SY/btsQl4Z0nmS/t2sUuKDQOQkdRqwor9wTik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciq3SY/btsQl4Z0nmS/t2sUuKDQOQkdRqwor9wTik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciq3SY/btsQl4Z0nmS/t2sUuKDQOQkdRqwor9wTik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fciq3SY%2FbtsQl4Z0nmS%2Ft2sUuKDQOQkdRqwor9wTik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;194&quot; data-filename=&quot;스크린샷 2025-09-06 오후 2.48.35.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;테스트케이스에서는 통과했는데 제출하니 시간 초과 ???&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 2.51.41.png&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uj8Is/btsQmx1AoYo/KWwpbfvjP2mWWzdoMxtHbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uj8Is/btsQmx1AoYo/KWwpbfvjP2mWWzdoMxtHbk/img.png&quot; data-alt=&quot;좌측(Scanner 방식), 우측(BufferedReader 방식)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uj8Is/btsQmx1AoYo/KWwpbfvjP2mWWzdoMxtHbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuj8Is%2FbtsQmx1AoYo%2FKWwpbfvjP2mWWzdoMxtHbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2992&quot; height=&quot;1648&quot; data-filename=&quot;스크린샷 2025-09-06 오후 2.51.41.png&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;좌측(Scanner 방식), 우측(BufferedReader 방식)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이처럼 입력부만 Scanner -&amp;gt; BufferedReader로 바꾸니 바로 통과되는 경우가 많았는데요 !&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Scanner는 배우기 쉽고 편하지만, 이처럼 시간초과라는 문제를 초래하기도 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇다면, 왜 Scanner 대신 BufferedReader를 사용해야 하고,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어떻게 사용하는지에 대해 한번 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. Scanner vs BufferedReader 비교&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;구분&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Scanner&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;BufferedReader&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;속도&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;상대적으로 느림 (정규식 기반 파싱)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;빠름 (버퍼 단위 입력)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;입력 단위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;공백(next()) / 줄(nextLine())&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;줄 단위(readLine())&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파싱&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자동 변환 지원 (정수, 실수 등)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;문자열만 반환 &amp;rarr; 직접 파싱 필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코드 난이도&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간단&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;조금 복잡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;적합한 경우&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;소규모 입력, 간단 실습&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;대규모 입력, 코딩테스트, 파일 입출력&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;쉬운 예시를 통해 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10818&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10818&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.38.38.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCgBdn/btsQlUC2ezl/xzkcbktelXZ96wJKPEPT50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCgBdn/btsQlUC2ezl/xzkcbktelXZ96wJKPEPT50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCgBdn/btsQlUC2ezl/xzkcbktelXZ96wJKPEPT50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCgBdn%2FbtsQlUC2ezl%2FxzkcbktelXZ96wJKPEPT50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;954&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.38.38.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.30.58.png&quot; data-origin-width=&quot;3002&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uxzsN/btsQmc4AAF1/H24rBMagM3umfjP5P2WbK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uxzsN/btsQmc4AAF1/H24rBMagM3umfjP5P2WbK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uxzsN/btsQmc4AAF1/H24rBMagM3umfjP5P2WbK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuxzsN%2FbtsQmc4AAF1%2FH24rBMagM3umfjP5P2WbK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3002&quot; height=&quot;1050&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.30.58.png&quot; data-origin-width=&quot;3002&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Scanner 동작 방식&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 첫 줄 &lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5 읽기 : nextInt() 호출&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 내부에서 정규식 검사(정수인지) -&amp;gt; 정수 변환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 두 번째 줄 &quot;20 10 35 30 7&quot; 읽기 : nextInt() 5번 호출&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 호출할 때마다 정규식 검사 -&amp;gt; 정수 변환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 숫자 하나 읽을 때마다 정규식을 돌려서 시간과 메모리 낭비가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;BufferedReader 동작 방식&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 첫 줄 5 읽기 : Integer.parseInt(&quot;5&quot;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 두 번째 줄 &quot;20 10 35 30 7&quot; 통째로 읽기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. StringTokenizer로 20, 10, 35, 30, 7 분리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 정수로 변환 후 배열 저장&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 한 줄을 통째로 읽어 처리하므로 시간과 메모리 낭비를 줄일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. BufferedReader 원리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 데이터를 &lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;8KB(8192 byte) 버퍼에 먼저 모아둡니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 프로그램에서 readLinke()을 호출하면, 이미 버퍼에 쌓여있는 데이터를 줄 단위로 꺼냅니다. (정규식 검사 XX)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 정규식 검사가 없고, 문자열만 반환하기 때문에 원하는 타입으로 변환은 직접 수행해야 합니다. (Integer.parseInt 등)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 대신, 대용량 입력일수록 훨씬 빠르게 처리할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;버퍼(Buffer)의 개념&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;버퍼는 데이터를 임시로 저장하는 중간 창고입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;입출력 장치는 CPU보다 느리기 때문에, 버퍼에 데이터를 모았다가 한꺼번에 전달하면 호출 횟수가 줄어들어 속도가 빨라집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. BufferedReader 기본 사용법&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // 한 줄 입력
        String line = br.readLine();
        System.out.println(line);

        // 공백 단위 입력
        StringTokenizer st = new StringTokenizer(br.readLine());
        int a = Integer.parseInt(st.nextToken());
        int b = Integer.parseInt(st.nextToken());
        System.out.println(a + b);

        br.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- readLine() : 한 줄 통째로 문자열 반환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- StringTokenizer : 문자열 공백 단위 분리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- Integer.parseInt() : 정수 변환&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Tip. 입력이 여러 줄에 걸쳐 들어오는 경우&lt;/b&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1757139041993&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (!st.hasMoreTokens()) st = new StringTokenizer(br.readLine());​&lt;/code&gt;&lt;/pre&gt;
&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 패턴으로 토큰 고갈 대비를 해주면 안전합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. 출력 최적화: BufferedWriter&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;입력이 많을 때뿐 아니라 출력도 많다면 System.out.println 대신 BufferedWriter를 씁니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757139971616&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
for (int i = 1; i &amp;lt;= 10; i++) {
    bw.write(i + &quot;\n&quot;);
}
bw.flush();
bw.close();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;출력 내용을 버퍼에 모았다가 flush()로 한 번에 내보내기 때문에 대량 출력 시 효율적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;5. 알고리즘에서 자주 쓰는 패턴&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간단한 문제들을 통해 자주 쓰는 패턴에 대해 살펴보겠습니다 !&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;정수 1개 입력 &lt;a href=&quot;https://www.acmicpc.net/problem/10818&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10818&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.51.06.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daYM2N/btsQl0iZ2Kn/FsqPkEIPelRAEcIVJkLDzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daYM2N/btsQl0iZ2Kn/FsqPkEIPelRAEcIVJkLDzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daYM2N/btsQl0iZ2Kn/FsqPkEIPelRAEcIVJkLDzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaYM2N%2FbtsQl0iZ2Kn%2FFsqPkEIPelRAEcIVJkLDzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.51.06.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1757138269867&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int n = Integer.parseInt(br.readLine());&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;정수 여러 개 입력 &lt;a href=&quot;https://www.acmicpc.net/problem/10871&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10871&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.52.05.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vqKcs/btsQl97JISI/zyCvCWIMWp5bkqookMVIQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vqKcs/btsQl97JISI/zyCvCWIMWp5bkqookMVIQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vqKcs/btsQl97JISI/zyCvCWIMWp5bkqookMVIQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvqKcs%2FbtsQl97JISI%2FzyCvCWIMWp5bkqookMVIQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.52.05.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1757138284748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;정수 배열 입력 &lt;a href=&quot;https://www.acmicpc.net/problem/10807&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10807&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.52.59.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bza4OZ/btsQosdWFht/CnXqQVKxbkFUWi8uYSLAgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bza4OZ/btsQosdWFht/CnXqQVKxbkFUWi8uYSLAgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bza4OZ/btsQosdWFht/CnXqQVKxbkFUWi8uYSLAgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbza4OZ%2FbtsQosdWFht%2FCnXqQVKxbkFUWi8uYSLAgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;324&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.52.59.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1757138295103&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int n = Integer.parseInt(br.readLine());
int[] arr = new int[n];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i &amp;lt; n; i++) arr[i] = Integer.parseInt(st.nextToken());&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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2차원 배열 입력 &lt;a href=&quot;https://www.acmicpc.net/problem/10810&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10810&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.53.43.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2N3vw/btsQnAQ8XBz/SyhcKQ3hICIQKt2YXIY5qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2N3vw/btsQnAQ8XBz/SyhcKQ3hICIQKt2YXIY5qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2N3vw/btsQnAQ8XBz/SyhcKQ3hICIQKt2YXIY5qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2N3vw%2FbtsQnAQ8XBz%2FSyhcKQ3hICIQKt2YXIY5qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;434&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.53.43.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1757138310698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int m = Integer.parseInt(st.nextToken());
int[][] arr = new int[n][m];
for (int i = 0; i &amp;lt; n; i++) {
    st = new StringTokenizer(br.readLine());
    for (int j = 0; j &amp;lt; m; j++) {
        arr[i][j] = Integer.parseInt(st.nextToken());
    }
}&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;대량 출력 &lt;a href=&quot;https://www.acmicpc.net/problem/15552&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/15552&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.54.17.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B55nO/btsQoPGGDMA/v2PNqcD5waXja0Y4oyf5hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B55nO/btsQoPGGDMA/v2PNqcD5waXja0Y4oyf5hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B55nO/btsQoPGGDMA/v2PNqcD5waXja0Y4oyf5hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB55nO%2FbtsQoPGGDMA%2Fv2PNqcD5waXja0Y4oyf5hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;474&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.54.17.png&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1757138326166&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
for (int i = 1; i &amp;lt;= 100; i++) bw.write(i + &quot;\n&quot;);
bw.flush();
bw.close();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6. Scanner &amp;rarr; BufferedReader 변환 예시&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.00.24.png&quot; data-origin-width=&quot;2212&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/smrV0/btsQoInkooc/hAUCxrDv2NNlVk57FK3kb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/smrV0/btsQoInkooc/hAUCxrDv2NNlVk57FK3kb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/smrV0/btsQoInkooc/hAUCxrDv2NNlVk57FK3kb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsmrV0%2FbtsQoInkooc%2FhAUCxrDv2NNlVk57FK3kb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2212&quot; height=&quot;440&quot; data-filename=&quot;스크린샷 2025-09-06 오후 3.00.24.png&quot; data-origin-width=&quot;2212&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- Scanner&amp;nbsp;: 쉽고 간편하지만 대량 입력에서는&amp;nbsp;정규식 비용으로 느려짐&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- BufferedReader: 직접 파싱이 필요하지만&amp;nbsp;훨씬 빠르며 코테 표준&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- BufferedWriter: 출력이 많을수록 효과가 큼&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;따라서 알고리즘 문제에서는 BufferedReader + StringTokenizer + BufferedWriter 조합을 기본 템플릿으로 삼는 것이 가장 효율적입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;이제는 BufferedReader를 습관화해 시간 초과 걱정 없이 더 빠르게 문제를 풀어보세요 !&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>BufferedReader</category>
      <category>Scanner</category>
      <category>멀티캠퍼스it부트캠프</category>
      <category>부트캠프후기</category>
      <category>알고리즘</category>
      <category>엘지유플러스유레카프론트엔드</category>
      <category>유레카3기</category>
      <category>자바입출력</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/50</guid>
      <comments>https://uoaheu.tistory.com/50#entry50comment</comments>
      <pubDate>Sat, 6 Sep 2025 16:09:28 +0900</pubDate>
    </item>
    <item>
      <title>알고리즘 공부 정리</title>
      <link>https://uoaheu.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;문제를 풀면서 &quot;시간초과를 피하는 사고방식&quot;이 필요하다고 느껴&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;알고리즘 공부 로드맵 + 해야 할 것들&lt;/b&gt;을 정리해 보는 시간을 가져보고자 합니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;우선 간단한 문제 예시를 살펴보면서 알고리즘 공부법의 중요성을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13458&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/13458&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-29 오후 7.58.05.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;927&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckbSow/btsQbNc3Jd5/NtazTzd0QZE3VlmTIhDmM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckbSow/btsQbNc3Jd5/NtazTzd0QZE3VlmTIhDmM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckbSow/btsQbNc3Jd5/NtazTzd0QZE3VlmTIhDmM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckbSow%2FbtsQbNc3Jd5%2FNtazTzd0QZE3VlmTIhDmM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;927&quot; data-filename=&quot;스크린샷 2025-08-29 오후 7.58.05.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;927&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;해당 문제를 가장 처음에는 다음과 같은 코드로 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756466324551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int[] arr = new int[N];
    for(int i = 0; i &amp;lt; N; i++) {
      arr[i] = sc.nextInt();
    }

    int B = sc.nextInt();
    int C = sc.nextInt();

    int result = 0;
    for (int i = 0; i &amp;lt; N; i++) {
      // 총감독관 
      arr[i] = arr[i] - B; 
      result++;
      
      // 부감독관
      if(arr[i] &amp;gt; 0) {
        while(true) {
          if(arr[i] &amp;lt;= 0) {
            break;  
          }
          arr[i] = arr[i] - C;
          result++;
        }
      }
    }
    System.out.println(result);    
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&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 data-start=&quot;250&quot; data-end=&quot;287&quot;&gt;한 시험장 반복 횟수 &amp;asymp; &amp;lceil;max(0, Ai &amp;minus; B)/C&amp;rceil;&lt;/li&gt;
&lt;li data-start=&quot;288&quot; data-end=&quot;351&quot;&gt;최악(N=10^6, Ai=10^6, B=1, C=1) 일 때 전체 반복 &amp;asymp; 10^12회 &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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그다음에는 시간복잡도를 고려해 다음 코드를 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756466304784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int[] arr = new int[N];
    for(int i = 0; i &amp;lt; N; i++) {
      arr[i] = sc.nextInt();
    }

    int B = sc.nextInt();
    int C = sc.nextInt();

    int result = 0;
    for (int i = 0; i &amp;lt; N; i++) {
      // 총감독관 
      arr[i] = arr[i] - B; 
      result++;

      // 부감독관
      if(arr[i] &amp;gt; 0) {
        int add = 0;
        if( arr[i] % C !=0 ) {
          add = arr[i] / C + 1;
        } else {
          add = arr[i] / C;
        }
        result += add;
      }
    }
    System.out.println(result); 
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&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 data-end=&quot;1180&quot; data-start=&quot;1138&quot;&gt;남은 학생 수 / C &amp;rarr; 필요한 부감독관 수를 &lt;b&gt;한 번에 계산&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1206&quot; data-start=&quot;1181&quot;&gt;시간복잡도: O(N), 충분히 통과 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;분명 답은 다 맞았다고 생각했는데, 왜 틀렸나 살펴보니 result 변수의 오버플로우 문제를 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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 data-end=&quot;814&quot; data-start=&quot;768&quot;&gt;총 감독관 수의 합(결과값)은 &lt;b&gt;최대 10^12 수준&lt;/b&gt;까지 커질 수 있음&lt;/li&gt;
&lt;li data-end=&quot;859&quot; data-start=&quot;815&quot;&gt;int의 최댓값은 &lt;b&gt;2,147,483,647(&amp;asymp; 2.1 &amp;times;10^9)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;897&quot; data-start=&quot;860&quot;&gt;따라서 누적 변수 result는 &lt;b&gt;long&lt;/b&gt;이어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;수치적으로 다시 확인해 본다면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 최악(B=1, C=1)에서 각 시험장 필요 인원 = Ai (총 1명 + 부감독관 Ai&amp;minus;1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 총합 = &amp;Sigma;Ai&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- &lt;b&gt;&amp;Sigma;Ai &amp;gt; 2,147,483,647&lt;/b&gt;이면 int 오버플로 &amp;rarr; long 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;ex) N=1,000,000이면 평균 A &amp;ge; 2,148부터 이미 위험&lt;/p&gt;
&lt;pre id=&quot;code_1756466276761&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int N = sc.nextInt();
    int[] arr = new int[N];
    for(int i = 0; i &amp;lt; N; i++) {
      arr[i] = sc.nextInt();
    }

    int B = sc.nextInt();
    int C = sc.nextInt();

    long result = 0;
    for (int i = 0; i &amp;lt; N; i++) {
      // 총감독관 
      arr[i] = arr[i] - B; 
      result++;

      // 부감독관
      if(arr[i] &amp;gt; 0) {
        int add = 0;
        if( arr[i] % C !=0 ) {
          add = arr[i] / C + 1;
        } else {
          add = arr[i] / C;
        }
        result += add;
      }
    }
    System.out.println(result);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;입력 크기 &amp;rarr; 시간복잡도 감각 익히기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;문제 제약 조건(입력 크기와 제한 시간)을 보고 가능한 복잡도를 먼저 가늠합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;- N&amp;le; 10^3 &amp;rarr; O(N&amp;sup3;)까지 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- N&amp;le; 10^5 &amp;rarr; O(N log N) 정도 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- N&amp;le; 10^6 &amp;rarr; O(N), O(N log log N) 정도&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- N&amp;le; 10^8 &amp;rarr; O(N)도 위험, 보통 수학적 공식, 해시, 그리디 등 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;쿼리 M이 크면 &lt;b&gt;전처리(누적합) or 자료구조&lt;/b&gt; 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;문제 조건을 보면 &quot;이건 어떤 복잡도여야 한다&quot;부터 추측하는 습관 들이기 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;기초 알고리즘/자료구조 확실히 다지기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;- 누적합(1D, 2D prefix sum), 투포인터, 슬라이딩 윈도우&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 문자열 처리(KMP, Rabin-Karp, Z 알고리즘)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;- 스택/큐/덱 (O(1) push/pop)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 힙(우선순위큐) &amp;rarr; Dijkstra 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 해시맵/셋 (O(1) 평균 탐색)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 트리와 기본 그래프 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;p data-ke-size=&quot;size14&quot;&gt;- DFS/BFS, 백트래킹&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 최단거리 (Dijkstra, BFS, Bellman-Ford, Floyd-Warshall)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 정렬/탐색&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 기본 정렬 (퀵/병합/힙) &amp;rarr; O(N log N)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 이분 탐색 (parametric search 포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- LIS (이분 탐색 활용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;b&gt;(1) 누적합 (prefix sum)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 쿼리 많은 합 문제의 기본 (11660 같은 문제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 그리디 vs DP 판단&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 부분 최적해로 전체 최적? &amp;rarr; 그리디&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 겹치는 부분 문제? &amp;rarr; DP&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;p data-ke-size=&quot;size14&quot;&gt;- 구간 합/최댓값 쿼리 + 업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 그래프 알고리즘&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 다익스트라 / MST = 최소 스패닝 트리(Kruskal, Prim)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) Union-Find (Disjoint Set)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 집합 관리, 사이클 판별&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(6) 비트마스킹&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 부분집합, DP 최적화&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;p data-ke-size=&quot;size14&quot;&gt;- 최대공약수/최소공배수, 에라토스테네스 체, 모듈러 연산, 조합 DP&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;&lt;b&gt;(Java 기준)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 입력: BufferedReader + StringTokenizer&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 출력: StringBuilder&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Scanner는 편리하지만 느림 &amp;rarr; 입력이 많은 문제는 반드시 교체&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;알고리즘이 맞아도 I/O 병목이면 TLE &amp;rarr; &lt;b&gt;습관적으로 빠른 입출력 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. &lt;b&gt;문제 접근 프로세스 (시간초과 방지 습관)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 입력 크기 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 대략 허용 시간복잡도 예측&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;&amp;rarr; 안 되면 최적화 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 있으면 전처리/누적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 데이터가 계속 바뀌는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 세그먼트트리/펜윅트리&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) 그래프인가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; BFS/DFS/다익스트라 중 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(6) 최적화 여지 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 중복 연산 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- DP 메모이제이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 수학적 공식 유도&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(7) I/O 최적화까지 체크&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. &lt;b&gt;실전 대비 문제 유형&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;시간초과를 피하려면 이 유형들 반드시 익혀야 합니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;- BOJ 11659 (1D 합), 11660 (2D 합)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;- BOJ 1920 (수 찾기), 10816 (숫자 카드 2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;p data-ke-size=&quot;size14&quot;&gt;- BOJ 2042 (구간 합 구하기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 그래프 최단거리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- BOJ 1753 (다익스트라), 11404 (플로이드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) 그리디&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- BOJ 11047 (동전 0), 1931 (회의실 배정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(6) DP&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- BOJ 1003 (피보나치 함수), 1149 (RGB거리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;p data-ke-size=&quot;size14&quot;&gt;- BOJ 1929 (소수 구하기), 11401 (이항계수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. &lt;b&gt;공부 습관 들이기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 문제 풀 때마다 &lt;b&gt;시간복잡도 계산식&lt;/b&gt; 직접 적기&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 틀리면/시간초과 나면 &amp;rarr; &lt;b&gt;왜 TLE인지 분석 기록하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 같은 유형 반복해서 풀어보기 (프리픽스 합, 세그먼트 트리 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;알고리즘 공부할 때 &quot;시간초과를 피하는 사고&quot;를 위해서 다음 3가지를 습관화하고자 합니다.&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;I/O 최적화&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>알고리즘</category>
      <author>uoaheu</author>
      <guid isPermaLink="true">https://uoaheu.tistory.com/49</guid>
      <comments>https://uoaheu.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 29 Aug 2025 09:02:02 +0900</pubDate>
    </item>
  </channel>
</rss>