<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>no-glass-otacku.log</title>
        <link>https://velog.io/</link>
        <description>이제 개발해야지...</description>
        <lastBuildDate>Tue, 14 Apr 2026 02:16:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>no-glass-otacku.log</title>
            <url>https://velog.velcdn.com/images/no-glass-otacku/profile/baf194e2-1f83-4314-a853-1d5b5a30163c/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. no-glass-otacku.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/no-glass-otacku" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Query plan 비교]]></title>
            <link>https://velog.io/@no-glass-otacku/Query-plan-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@no-glass-otacku/Query-plan-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Tue, 14 Apr 2026 02:16:39 GMT</pubDate>
            <description><![CDATA[<h3 id="📊-사례별-코드-차이-및-실행-결과-분석">📊 사례별 코드 차이 및 실행 결과 분석</h3>
<h3 id="1-1번-vs-2번-비교-select-다이어트의-중요성">1. [1번 vs 2번] 비교: &quot;SELECT 다이어트의 중요성&quot;</h3>
<div style="display: flex; gap: 10px;">
<img src="https://velog.velcdn.com/images/no-glass-otacku/post/e21fde4f-a25a-42bf-abcc-9c05486a865a/image.png" width="60%">
<img src="https://velog.velcdn.com/images/no-glass-otacku/post/914e81cc-6545-48ae-a833-af94fce2fd02/image.png" width="80%">
</div>

<ul>
<li><strong>차이점:</strong> 1번은 <code>SELECT *</code> (모든 컬럼)을 가져오고, 2번은 인덱스에 있는 <code>name</code> 컬럼만 가져옵니다.</li>
<li><strong>Plan 변화:</strong> <strong>Bitmap Heap Scan</strong> (1번) → <strong>Index Only Scan</strong> (2번)</li>
<li><strong>결과 해석:</strong> 1번은 인덱스(<code>name</code>)에 없는 <code>id</code>, <code>email</code>, <code>signup_date</code>를 찾으러 실제 데이터 테이블(Heap)로 가야만 했습니다. 반면, 2번은 <strong>Planner(플래너)</strong>가 &quot;인덱스만 봐도 답이 다 있네?&quot;라고 판단해 테이블 근처에도 가지 않았습니다.</li>
<li><strong>학습 한 줄 정리:</strong> <strong>불필요한 컬럼을 버리는 &#39;SELECT 다이어트&#39;만으로도 디스크 방문(Heap Scan)을 막을 수 있습니다.</strong></li>
</ul>
<hr>
<h3 id="2-2번-vs-3번-비교-인덱스-범위를-벗어난-필터">2. [2번 vs 3번] 비교: &quot;인덱스 범위를 벗어난 필터&quot;</h3>
<div style="display: flex; gap: 10px;">
<img src="이미지 링크" width="n%" height="n%">
<img src="이미지 링크" width="n%" height="n%">
</div>

<ul>
<li><strong>차이점:</strong> 3번은 <code>WHERE</code> 절에 인덱스에 없는 <code>signup_date</code> 조건을 추가하고, <code>SELECT</code> 결과에도 포함했습니다.</li>
<li><strong>Plan 변화:</strong> <strong>Index Only Scan</strong> (2번) → <strong>Bitmap Heap Scan</strong> (3번)</li>
<li>[cite_start]<strong>결과 해석:</strong> 3번은 인덱스에 없는 <code>signup_date</code>를 확인하고 출력해야 하므로, <strong>Executor(실행기)</strong>가 실제 데이터 파일 영역을 뒤져야 하는 <strong>비싼 비용(Cost)</strong>을 지불하게 된 것입니다[cite: 324, 331].</li>
<li><strong>학습 한 줄 정리:</strong> <strong>인덱스에 없는 컬럼을 조건이나 결과에 넣는 순간, &#39;인덱스 전용 스캔&#39;의 마법은 풀립니다.</strong></li>
</ul>
<hr>
<h3 id="3-3번-vs-4번-비교-단일-인덱스-vs-복합-인덱스">3. [3번 vs 4번] 비교: &quot;단일 인덱스 vs 복합 인덱스&quot;</h3>
<div style="display: flex; gap: 10px;">
<img src="이미지 링크" width="n%" height="n%">
<img src="이미지 링크" width="n%" height="n%">
</div>

<ul>
<li><strong>차이점:</strong> 3번은 <code>name</code>만 있는 인덱스고, 4번은 <code>(name, signup_date)</code>가 묶인 <strong>복합 인덱스</strong>입니다.</li>
<li><strong>Plan 변화:</strong> <strong>Bitmap Heap Scan</strong> (3번) → <strong>Index Only Scan</strong> (4번)</li>
<li><strong>결과 해석:</strong> 4번은 자주 같이 쓰이는 두 컬럼을 하나로 묶어버렸습니다. [cite_start]플래너는 이제 <code>signup_date</code> 정보까지 인덱스 안에서 모두 찾을 수 있게 되어 다시 가장 빠른 경로를 선택했습니다[cite: 322, 324].</li>
<li><strong>학습 한 줄 정리:</strong> <strong>자주 함께 쓰이는 필터 조건들은 &#39;복합 인덱스&#39;로 묶어야 플래너가 가장 &#39;싼 비용&#39;의 계획을 세웁니다.</strong></li>
</ul>
<hr>
<h3 id="4--1번-vs-5번-비교-인덱스-전용-스캔의-마법-covering-index">4.  [1번 vs 5번] 비교: &quot;인덱스 전용 스캔의 마법, Covering Index&quot;</h3>
<div style="display: flex; gap: 10px;">
<img src="이미지 링크" width="n%" height="n%">
<img src="이미지 링크" width="n%" height="n%">
</div>

<ul>
<li><p><strong>차이점 (Condition):</strong> * <strong>1번:</strong> <code>SELECT</code>로 테이블의 모든 컬럼(<code>id</code>, <code>name</code>, <code>email</code>, <code>signup_date</code>)을 요구하지만, 인덱스는 <code>name</code> 하나만 가지고 있습니다.</p>
<ul>
<li><strong>5번:</strong> <code>SELECT id, name, email</code>을 요구하며, 인덱스 생성 시 <code>INCLUDE (id, email)</code>를 사용하여 결과에 필요한 데이터를 인덱스 안에 미리 복사해 두었습니다.</li>
</ul>
</li>
<li><p><strong>Plan 변화 (Query Plan):</strong> <strong>Bitmap Heap Scan</strong> → <strong>Index Only Scan</strong></p>
</li>
<li><p><strong>결과 해석 (Interpretation):</strong> * <strong>1번</strong>은 인덱스에 없는 나머지 정보를 찾기 위해 <strong>Planner(플래너)</strong>가 실제 <strong>Data Files(Heap)</strong> 영역을 뒤져야 한다고 판단했습니다. 이 과정에서 디스크 읽기(<code>read</code>)가 발생하며 비용이 급증합니다.</p>
<ul>
<li><strong>5번</strong>은 여러 컬럼을 요구함에도 불구하고, 필요한 모든 데이터가 인덱스라는 &#39;보조 주머니&#39;안에 다 들어있습니다. <strong>Executor(실행기)</strong>는 무거운 테이블 파일(<code>base/</code> 디렉토리) 근처에도 가지 않고 인덱스만으로 모든 응답을 끝냈습니다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>학습 한 줄 정리:</strong> <strong>검색 조건은 아니지만 결과로 자주 쓰이는 데이터는 <code>INCLUDE</code>로 인덱스에 태워두면, &#39;테이블 방문&#39; 없는 초고속 조회가 가능합니다.</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SQL] Pivot, 세로 데이터를 가로로!]]></title>
            <link>https://velog.io/@no-glass-otacku/SQL-Pivot-%EC%84%B8%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EA%B0%80%EB%A1%9C%EB%A1%9C</link>
            <guid>https://velog.io/@no-glass-otacku/SQL-Pivot-%EC%84%B8%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EA%B0%80%EB%A1%9C%EB%A1%9C</guid>
            <pubDate>Fri, 10 Apr 2026 05:30:05 GMT</pubDate>
            <description><![CDATA[<h2 id="📊-sql-세로-데이터를-가로로-pivot-완벽-가이드-case-when-vs-crosstab">📊 [SQL] 세로 데이터를 가로로! Pivot 완벽 가이드 (CASE WHEN vs CROSSTAB)</h2>
<p>데이터를 분석하다 보면, 아래로 길게 나열된 행(Row) 데이터를 엑셀 피벗 테이블처럼 옆으로(Column) 펼쳐야 할 때가 많습니다. SQL에서 이를 구현하는 두 가지 정석 방법을 정리합니다.</p>
<hr>
<h3 id="1-가장-클래식한-방법-case-when--sum">1. 가장 클래식한 방법: <code>CASE WHEN</code> + <code>SUM</code></h3>
<p>별도의 설치 없이 모든 SQL 엔진에서 사용할 수 있는 가장 범용적인 방식입니다.</p>
<h4 id="✅-작동-원리">✅ 작동 원리</h4>
<p>특정 조건일 때만 값을 남기고, 아니면 0을 주어 합산하는 방식입니다.</p>
<pre><code class="language-sql">SELECT 
    prod_month,
    SUM(CASE WHEN breeds_nm = &#39;Cornish&#39; THEN total_sum ELSE 0 END) AS &quot;Cornish_Total&quot;,
    SUM(CASE WHEN breeds_nm = &#39;Cochin&#39; THEN total_sum ELSE 0 END) AS &quot;Cochin_Total&quot;,
    SUM(total_sum) AS monthly_total
FROM breeds_prod
GROUP BY prod_month;</code></pre>
<h4 id="⚠️-주의할-점-삽질-포인트">⚠️ 주의할 점 (삽질 포인트)</h4>
<ul>
<li><strong>별칭 사용 주의</strong>: <code>SELECT</code> 절에서 만든 별칭(예: <code>prod_month</code>)은 엔진에 따라 <code>GROUP BY</code>에서 바로 쓸 수 있지만, <code>SUM</code>으로 만든 결과물 별칭은 <code>GROUP BY</code>에서 쓸 수 없습니다. (닭이 먼저냐 달걀이 먼저냐의 싸움 방지!)</li>
<li><strong>0 또는 NULL</strong>: 합계 시 <code>ELSE 0</code>을 써야 <code>NULL</code> 때문에 전체 합계가 <code>NULL</code>이 되는 대참사를 막을 수 있습니다.</li>
</ul>
<hr>
<h3 id="2-전문가의-도구-tablefunc와-crosstab">2. 전문가의 도구: <code>tablefunc</code>와 <code>crosstab</code></h3>
<p>PostgreSQL을 사용한다면 <code>tablefunc</code> 확장 기능을 사용하여 더 세련되게 피벗할 수 있습니다.</p>
<h4 id="🛠-준비-단계-import와-동일">🛠 준비 단계 (Import와 동일)</h4>
<pre><code class="language-sql">CREATE EXTENSION IF NOT EXISTS tablefunc;</code></pre>
<ul>
<li><strong>의미</strong>: DB에 &#39;피벗 전용 도구 상자&#39;를 들여놓는 것. 한 번만 실행하면 됩니다.</li>
</ul>
<h4 id="🍱-crosstab의-3단-도시락-규칙">🍱 crosstab의 &#39;3단 도시락&#39; 규칙</h4>
<p><code>crosstab</code> 함수는 반드시 <strong>딱 3개의 컬럼</strong>으로 구성된 쿼리를 입력받아야 합니다.</p>
<ol>
<li><strong>첫 번째 컬럼</strong>: 행의 기준 (예: 부화일자)</li>
<li><strong>두 번째 컬럼</strong>: 열(컬럼명)이 될 카테고리 (예: 성별)</li>
<li><strong>세 번째 컬럼</strong>: 실제 칸을 채울 값 (예: 마릿수)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/no-glass-otacku/post/da25ed3e-9446-4200-8b45-4dc983df81d9/image.png" alt=""></p>
<h4 id="✅-실행-코드-예시">✅ 실행 코드 예시</h4>
<pre><code class="language-sql">SELECT * FROM crosstab(
    --count는 bigint를 내뱉으므로 int로 형변환
    &#39;SELECT hatchday, gender, count(chick_no)::int 
     FROM chick_info 
     GROUP BY hatchday, gender 
     ORDER BY hatchday, gender&#39; -- 반드시 정렬(ORDER BY) 필요!
) AS 별칭(
    hatchday date,  -- 첫 번째 컬럼 (기준)
    male int,       -- 두 번째 컬럼 (값)
   female int       -- 세 번째 컬럼 (값)
); --열 이름의 타입이 아니라 내부 값의 타입을 적어야한단다</code></pre>
<h4 id="⚠️-주의할-점-삽질-포인트-1">⚠️ 주의할 점 (삽질 포인트)</h4>
<ul>
<li><strong>쿼리는 문자열이다</strong>: <code>crosstab</code>은 함수이므로 내부 쿼리를 통째로 <strong>작은따옴표(<code>&#39;</code>)</strong>로 감싸서 문자열 데이터로 넘겨야 합니다.</li>
<li><strong>설계도(<code>AS</code>) 필수</strong>: <code>crosstab</code>은 결과 모양을 스스로 알지 못하므로, <code>AS 별칭(컬럼명 타입, ...)</code>으로 결과표의 구조를 명시해줘야 합니다.</li>
<li><strong>따옴표 규칙</strong>: 설계도 안에서 컬럼 이름에 작은따옴표를 쓰면 에러가 납니다. (<code>&#39;male&#39;</code> ❌ → <code>male</code> ⭕)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SQL] 실행순서: SELECT 별칭을 GROUP BY에서 쓸 수 있는 이유?]]></title>
            <link>https://velog.io/@no-glass-otacku/SQL-%EC%8B%A4%ED%96%89%EC%88%9C%EC%84%9C-SELECT-%EB%B3%84%EC%B9%AD%EC%9D%84-GROUP-BY%EC%97%90%EC%84%9C-%EC%93%B8-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@no-glass-otacku/SQL-%EC%8B%A4%ED%96%89%EC%88%9C%EC%84%9C-SELECT-%EB%B3%84%EC%B9%AD%EC%9D%84-GROUP-BY%EC%97%90%EC%84%9C-%EC%93%B8-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Fri, 10 Apr 2026 02:07:11 GMT</pubDate>
            <description><![CDATA[<h3 id="1-의문의-시작">1. 의문의 시작</h3>
<p>SQL을 배울 때 우리는 분명 실행 순서를 다음과 같이 배웁니다.</p>
<ol>
<li><strong>FROM / JOIN</strong></li>
<li><strong>WHERE</strong></li>
<li><strong>GROUP BY</strong></li>
<li><strong>HAVING</strong></li>
<li><strong>SELECT</strong> (이때 별칭 <code>AS</code>가 생성됨)</li>
<li><strong>ORDER BY</strong></li>
</ol>
<p>그런데 아래 쿼리는 에러 없이 아주 잘 작동합니다.</p>
<pre><code class="language-sql">SELECT to_char(prod_date, &#39;YYYYMM&#39;) AS prod_month, -- 5번: 별칭 생성
       SUM(total_sum) AS monthly_total
FROM breeds_prod
GROUP BY prod_month; -- 3번: 그런데 어떻게 5번에서 만든 별칭을 미리 쓰지?</code></pre>
<p>논리적으로는 3번(<code>GROUP BY</code>) 단계에서 아직 존재하지도 않는 5번(<code>SELECT</code>)의 별명을 부르는 <strong>&#39;타임 패러독스&#39;</strong>가 발생합니다. 왜 그런 걸까요?
<img src="https://velog.velcdn.com/images/no-glass-otacku/post/b148a349-a1f0-4717-84f8-71f85fd5e1de/image.png" alt=""></p>
<h3 id="2-범인은-똑똑한-sql-엔진">2. 범인은 똑똑한 &#39;SQL 엔진&#39;</h3>
<p>우리가 코드를 던지면 데이터베이스 엔진(PostgreSQL, MySQL 등)은 실행 전 코드를 전체적으로 훑어보는 <strong>파싱(Parsing)</strong> 단계를 거칩니다.</p>
<ul>
<li><strong>엔진의 판단:</strong> &quot;<code>GROUP BY</code>에 쓰인 <code>prod_month</code>가 뭐지? 아, <code>SELECT</code> 절을 보니 단순 날짜 변환식이네? 내가 미리 계산해서 그룹을 묶어줄게!&quot;</li>
<li><strong>실제 실행:</strong> 논리적 순서는 지키되, 엔진이 개발자의 편의를 위해 <code>SELECT</code> 절의 단순 별칭을 <strong>미리 참조</strong>할 수 있게 설계되어 있기 때문입니다.</li>
</ul>
<h3 id="3-무조건-다-되는-건-아니다-중요한-차이">3. &quot;무조건&quot; 다 되는 건 아니다! (중요한 차이)</h3>
<p>여기서 가장 중요한 포인트는 <strong>&#39;어떤&#39;</strong> 별칭이냐는 것입니다.</p>
<h4 id="✅-가능한-경우-단순-변환-별칭">✅ 가능한 경우: 단순 변환 별칭</h4>
<ul>
<li><code>to_char</code>, <code>SUBSTRING</code>, <code>REPLACE</code> 등 <strong>행 하나하나에 바로 적용되는 식</strong>의 별칭은 <code>GROUP BY</code>에서 쓸 수 있습니다. 엔진이 미리 계산하기 쉽기 때문이죠.</li>
</ul>
<h4 id="❌-불가능한-경우-집계-함수의-별칭">❌ 불가능한 경우: 집계 함수의 별칭</h4>
<ul>
<li><code>SUM</code>, <code>AVG</code>, <code>COUNT</code> 등으로 만든 별칭은 <strong>절대로 <code>GROUP BY</code>에서 쓸 수 없습니다.</strong></li>
<li><strong>이유:</strong> 집계 함수는 &#39;그룹화가 완료된 후&#39;에야 값이 결정됩니다. 그룹을 묶기 위해 그룹이 묶여야 나오는 값을 가져다 쓰는 것은 불가능하기 때문입니다. (순환 참조 오류)</li>
</ul>
<h3 id="4-정리하며">4. 정리하며</h3>
<ul>
<li><strong>SQL의 논리적 순서</strong>와 <strong>엔진의 실제 최적화 순서</strong>는 다를 수 있다.</li>
<li><strong>PostgreSQL</strong> 같은 현대적인 DB는 <code>SELECT</code>의 별칭을 <code>GROUP BY</code>에서 쓸 수 있게 지원한다. (단순 식에 한함)</li>
<li>이 원리를 이해하면 쿼리 가독성을 높이면서도 성능 최적화를 고려하는 데이터 엔지니어로 성장할 수 있다!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hybrid RAG (Vector+Graph RAG) 의문과 답 기록]]></title>
            <link>https://velog.io/@no-glass-otacku/Hybrid-RAG-VectorGraph-RAG-%EC%9D%98%EB%AC%B8%EA%B3%BC-%EB%8B%B5-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@no-glass-otacku/Hybrid-RAG-VectorGraph-RAG-%EC%9D%98%EB%AC%B8%EA%B3%BC-%EB%8B%B5-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Sun, 29 Mar 2026 11:42:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>구조가 깨졌지만 메타데이터로 page 확인이 가능하다면 그냥 문서에서 다시 해당 page를 가져와서 분할을 시도하는게 더 낫지 않아? 그렇게 안하는 이유가 뭐야?</p>
</blockquote>
<p>실무적인 데이터 엔지니어링 환경에서 <strong>&#39;원천 페이지 재호출&#39;</strong>을 하지 않고 <strong>&#39;강제 분할&#39;</strong>을 선택하는 데에는 몇 가지 현실적인 이유가 있습니다.</p>
<h3 id="1-같은-기계parser는-같은-불량을-냅니다">1. &quot;같은 기계(Parser)는 같은 불량을 냅니다&quot;</h3>
<p>가장 결정적인 이유입니다. <code>utils.py</code>에서 8,000자가 넘는 노드가 발생했다는 건, 그 페이지를 읽어낸 <strong>PDF 파서(Parser)가 이미 해당 페이지의 구조를 해석하는 데 실패했다</strong>는 뜻입니다.</p>
<h3 id="2-비용과-속도-공정의-병목-현상">2. 비용과 속도: &quot;공정의 병목 현상&quot;</h3>
<p>데이터 파이프라인은 보통 <strong>[원천 파일 → 전체 텍스트 추출 → 노드 분할 → 필터링]</strong>의 단방향 흐름으로 설계됩니다.</p>
<ul>
<li><strong>IO 부하:</strong> 필터링 단계에서 갑자기 &quot;이 페이지 다시 읽어와!&quot;라고 역행하면, 닫혀 있던 PDF 파일을 다시 열고, 해당 페이지를 찾고, 다시 파싱하는 무거운 작업이 추가됩니다.</li>
<li><strong>비효율:</strong> 만약 1,000페이지 중 100페이지에서 이런 문제가 생기면, 전체 처리 속도가 기하급수적으로 느려지는 <strong>병목(Bottleneck)</strong> 현상이 발생합니다.</li>
</ul>
<h3 id="3-데이터-심폐소생술의-관점">3. &quot;데이터 심폐소생술&quot;의 관점</h3>
<p>우리가 하려는 <strong>강제 분할(Force-chopping)</strong>은 &#39;완벽한 복구&#39;라기보다는 <strong>&#39;최소한의 검색 가능성 확보&#39;</strong>에 가깝습니다.</p>
<ul>
<li><strong>구조는 포기, 키워드는 보존:</strong> 비록 문단은 깨졌어도 8,000자 안의 단어들은 살아있습니다. 1,000자씩 잘라두면, 나중에 사용자가 그 안의 키워드로 질문했을 때 검색 결과에는 걸리게 됩니다.</li>
<li><strong>LLM의 추론 능력:</strong> 요즘 LLM은 앞뒤가 조금 잘린 문맥이라도 검색된 조각들을 합쳐서 주면, 그 안에서 의미를 찾아내 답변을 생성합니다.</li>
</ul>
<p>정말로 &quot;원천 페이지에서 다시 제대로 가져오고 싶다&quot;면, 단순 재시도가 아니라 <strong>&#39;다른 공정(다른 파서)&#39;</strong>을 써야 합니다.</p>
<blockquote>
<p><strong>[개선된 공정 제안]</strong></p>
<ol>
<li>기본 파서로 전체 파싱 진행.</li>
<li>8,000자 초과 노드 발생 시 <strong>&#39;비상 공정&#39;</strong> 가동.</li>
<li>해당 페이지를 텍스트가 아닌 <strong>이미지(OCR)</strong>로 읽거나, 훨씬 정교한(하지만 느린) <strong>AI 기반 파서</strong>로 해당 페이지만 다시 읽기.</li>
</ol>
</blockquote>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[🐍 [Python] 왜 set과 dict는 똑같이 { }를 쓸까? (해시 테이블) + 헷갈리는 list method]]></title>
            <link>https://velog.io/@no-glass-otacku/Python-%EC%99%9C-set%EA%B3%BC-dict%EB%8A%94-%EB%98%91%EA%B0%99%EC%9D%B4-%EB%A5%BC-%EC%93%B8%EA%B9%8C-%ED%95%B4%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-list-method</link>
            <guid>https://velog.io/@no-glass-otacku/Python-%EC%99%9C-set%EA%B3%BC-dict%EB%8A%94-%EB%98%91%EA%B0%99%EC%9D%B4-%EB%A5%BC-%EC%93%B8%EA%B9%8C-%ED%95%B4%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-list-method</guid>
            <pubDate>Thu, 26 Mar 2026 05:46:26 GMT</pubDate>
            <description><![CDATA[<p>파이썬을 공부하다 보면 문득 드는 의문이 있습니다. <strong>&quot;집합(set)이랑 딕셔너리(dict)는 왜 둘 다 중괄호 <code>{}</code>를 쓰지?&quot;</strong> 단순히 우연일까요? 아니면 깊은 뜻이 숨겨져 있을까요? 오늘은 그 이유와 함께 리스트 메서드들의 네이밍 비화까지 정리해 보겠습니다.</p>
<hr>
<h3 id="1-중괄호-의-공통분모-해시-테이블hash-table">1. 중괄호 <code>{}</code>의 공통분모: 해시 테이블(Hash Table)</h3>
<p>두 자료형이 같은 괄호를 공유하는 가장 큰 이유는 내부적으로 <strong>데이터를 저장하는 메커니즘이 똑같기 때문</strong>입니다.</p>
<ol>
<li>해시 테이블 기반: 처음부터 끝까지 뒤지는 게 아니라, 주소를 계산해서 한 번에 찾아가는 방식을 사용 (Random Access)</li>
<li>중복 불허: 해시 테이블 특성상 set은 값의 중복을 허용하지 않고, dict는 키(key)의 중복을 허용하지 않습니다. 
데이터가 저장될 위치가 데이터 그 자체(값 또는 키)에 의해 이미 결정되어 있기 때문입니다. (Hash Function)*</li>
</ol>
<ul>
<li><strong>해시 함수(Hash Function):</strong> 어떤 데이터를 넣든 고유한 주소값(인덱스)으로 변환해주는 계산기입니다.</li>
</ul>
<h4 id="💡-여기서-잠깐-중복-불허와-해시의-상관관계">💡 여기서 잠깐! &quot;중복 불허&quot;와 &quot;해시&quot;의 상관관계</h4>
<p>해시 테이블은 데이터의 값 자체가 저장 위치를 결정합니다. </p>
<ol>
<li><code>hash(&quot;apple&quot;)</code>이 <code>5번 방</code>이라면, 사과는 무조건 5번에 들어갑니다.</li>
<li>또 다른 사과를 넣으려 해도 다시 <code>5번 방</code>을 가리킵니다.</li>
<li>가서 보니 이미 &quot;사과&quot;가 있네? <strong>그럼 새로 저장하지 않습니다.</strong>
이것이 바로 <code>set</code>과 <code>dict(key)</code>에서 중복이 불가능한 기술적 이유입니다.</li>
</ol>
<hr>
<h3 id="2-set-vs-dictionary-어떻게-구분할까">2. Set vs Dictionary: 어떻게 구분할까?</h3>
<p>파이썬은 중괄호 안의 <strong>콜론(<code>:</code>)</strong> 유무로 둘을 구분합니다.</p>
<table>
<thead>
<tr>
<th align="left">자료형</th>
<th align="left">표기법</th>
<th align="left">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Dictionary</strong></td>
<td align="left"><code>{&#39;key&#39;: &#39;value&#39;}</code></td>
<td align="left"><strong>키:값</strong> 쌍으로 저장 (데이터 중심)</td>
</tr>
<tr>
<td align="left"><strong>Set</strong></td>
<td align="left"><code>{&#39;value1&#39;, &#39;value2&#39;}</code></td>
<td align="left"><strong>값</strong>만 단독 존재 (존재 여부 중심)</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>⚠️ 주의:</strong> 빈 괄호 <code>{}</code>는 파이썬에서 <strong>Dictionary</strong>로 인식됩니다. 빈 집합을 만들고 싶다면 반드시 <code>set()</code>을 사용하세요!</p>
</blockquote>
<hr>
<h3 id="3-왜-list의-pop은-인덱스고-remove는-값일까">3. 왜 List의 <code>pop()</code>은 인덱스고, <code>remove()</code>는 값일까?</h3>
<p>이 질문의 답은 <strong>영어 단어의 본래 의미</strong>와 <strong>자료구조의 역사</strong>에 있습니다.</p>
<h4 id="①-pop-튀어나오다-값을-나에게-쥐어줌">① <code>pop()</code>: 튀어&#39;나오다&#39; (값을 나에게 쥐어줌)</h4>
<ul>
<li><strong>유래:</strong> 데이터가 쌓여 있는 <strong>스택(Stack)</strong>에서 맨 위의 것을 꺼내는 동작입니다.</li>
<li><strong>특징:</strong> &quot;어디에 있는 것을 꺼낼까?&quot;가 중요합니다. 그래서 <strong>인덱스</strong>를 받으며, 삭제한 값을 우리에게 다시 <strong>반환(return)</strong>해줍니다.</li>
</ul>
<h4 id="②-remove-무엇을-제거하다">② <code>remove()</code>: &#39;무엇을&#39; 제거하다</h4>
<ul>
<li><strong>유래:</strong> 방에 있는 쓰레기를 치우듯, 특정 대상을 지목해서 없애는 동작입니다.</li>
<li><strong>특징:</strong> &quot;어디에 있든 상관없으니 &#39;이 값&#39;을 지워줘!&quot;라는 의미입니다. 그래서 <strong>값(Value)</strong>을 인덱스 대신 받으며, 별도의 값을 반환하지 않습니다.</li>
</ul>
<hr>
<p><strong>마치며</strong>
언어의 문법과 메서드 이름에는 개발자들의 의도가 담겨 있습니다. 단어의 뜻과 내부 구조를 연결해서 이해해야 덜 헷갈리고 기억에 남습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pydantic (지정한 타입으로 변환해주는 데이터 검증 라이브러리)]]></title>
            <link>https://velog.io/@no-glass-otacku/Pydantic-%EC%A7%80%EC%A0%95%ED%95%9C-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%B4%EC%A3%BC%EB%8A%94-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%80%EC%A6%9D-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</link>
            <guid>https://velog.io/@no-glass-otacku/Pydantic-%EC%A7%80%EC%A0%95%ED%95%9C-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%B4%EC%A3%BC%EB%8A%94-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%80%EC%A6%9D-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</guid>
            <pubDate>Wed, 25 Mar 2026 07:46:40 GMT</pubDate>
            <description><![CDATA[<h2 id="1-pydantic이-왜-필요한가요">1. Pydantic이 왜 필요한가요?</h2>
<p>파이썬은 동적 타이핑 언어라 자료형이 자유롭다는 장점이 있지만, 데이터가 복잡해지면 사고가 나기 쉽습니다. </p>
<ul>
<li><strong>기존 방식:</strong> 데이터가 들어올 때마다 <code>if isinstance(age, int)</code>나 <code>try-except</code>를 수십 개씩 써서 검증해야 했습니다.</li>
<li><strong>Pydantic 방식:</strong> &quot;데이터는 이런 모양이어야 해!&quot;라고 <strong>모델(Model)</strong>을 딱 한 번 정의해두면, Pydantic이 알아서 검증하고 변환까지 해줍니다.</li>
</ul>
<hr>
<h2 id="2-코드-비교-일반-클래스-vs-pydantic">2. 코드 비교: 일반 클래스 vs Pydantic</h2>
<h3 id="일반적인-방식">일반적인 방식</h3>
<pre><code class="language-python">class User:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name

# 문자열 &quot;123&quot;을 넣으면? 그냥 문자열로 저장됨 (나중에 계산할 때 에러 날 수 있음)
user = User(id=&quot;123&quot;, name=&quot;가영&quot;) </code></pre>
<h3 id="pydantic-방식">Pydantic 방식</h3>
<pre><code class="language-python">from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str

# 문자열 &quot;123&quot;을 넣어도? 자동으로 정수 123으로 변환!
user = User(id=&quot;123&quot;, name=&quot;가영&quot;)
print(user.id) # 결과: 123 (int 타입)

# 만약 &quot;abc&quot;처럼 숫자로 못 바꾸는 걸 넣으면? 바로 에러(ValidationError) 발생!</code></pre>
<hr>
<h2 id="3-pydantic의-3가지-핵심-기능">3. Pydantic의 3가지 핵심 기능</h2>
<h3 id="①-데이터-검증-validation">① 데이터 검증 (Validation)</h3>
<p>정해진 타입이 아니거나 값이 범위를 벗어나면 즉시 에러를 발생시킵니다. 예를 들어 나이는 <code>int</code>여야 하고 <code>0</code>보다 커야 한다는 규칙을 아주 쉽게 정할 수 있습니다.</p>
<h3 id="②-데이터-변환-parsingcoercion">② 데이터 변환 (Parsing/Coercion)</h3>
<p>이게 정말 편리한 기능입니다! <code>&quot;100&quot;</code>이라는 문자열이 들어와도, 모델이 <code>int</code>를 원한다면 <strong>Pydantic이 알아서 숫자로 바꿔줍니다.</strong> (아까 우리가 <code>int(input())</code>으로 수동 변환했던 과정을 자동으로 해주는 거죠.)</p>
<h3 id="③-압도적인-성능">③ 압도적인 성능</h3>
<p>Pydantic V2부터는 핵심 로직이 <strong>Rust(러스트)</strong>라는 매우 빠른 언어로 작성되어, 파이썬에서 가장 빠른 데이터 검증 라이브러리로 평가받고 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[
Python 상기시키기]]></title>
            <link>https://velog.io/@no-glass-otacku/Python-%EC%83%81%EA%B8%B0%EC%8B%9C%ED%82%A4%EA%B8%B0</link>
            <guid>https://velog.io/@no-glass-otacku/Python-%EC%83%81%EA%B8%B0%EC%8B%9C%ED%82%A4%EA%B8%B0</guid>
            <pubDate>Wed, 25 Mar 2026 06:31:54 GMT</pubDate>
            <description><![CDATA[<h1 id="python-타입-시스템의-이해-dynamic-typing과-메모리-참조-모델">[Python] 타입 시스템의 이해: Dynamic Typing과 메모리 참조 모델</h1>
<p>타입 시스템을 비교 분석하고, 특히 파이썬(Python) 환경에서 변수가 메모리를 관리하는 내부 동작 원리를 살펴보는 글.</p>
<hr>
<h2 id="1-타입-시스템-전용-상자-vs-포스트잇">1. 타입 시스템: 전용 상자 vs 포스트잇</h2>
<p>변수의 자료형(Type)을 결정하는 방식에 따라 언어의 특성이 결정된다. 이를 비유를 통해 명확히 구분할 수 있다.</p>
<h3 id="11-정적-타이핑static-typing-전용-상자-모델">1.1 정적 타이핑(Static Typing): 전용 상자 모델</h3>
<ul>
<li><strong>비유:</strong> 변수는 특정 크기와 용도가 정해진 <strong>&#39;전용 상자&#39;</strong>와 같다.</li>
<li><strong>특징:</strong> 정수형(int) 상자를 만들면 오직 정수만 담을 수 있으며, 실행 전(Compile 타임)에 상자의 규격을 결정한다.</li>
<li><strong>언어:</strong> C, C++, Java 등.</li>
<li><strong>장점:</strong> 안정성이 높고 실행 속도가 빠르나, 유연성이 부족하다.</li>
</ul>
<h3 id="12-동적-타이핑dynamic-typing-포스트잇-모델">1.2 동적 타이핑(Dynamic Typing): 포스트잇 모델</h3>
<ul>
<li><strong>비유:</strong> 변수는 데이터에 붙이는 <strong>&#39;포스트잇(이름표)&#39;</strong>과 같다.</li>
<li><strong>특징:</strong> 데이터가 먼저 존재하고, 변수는 그 데이터가 무엇이든 상관없이 이름표만 붙인다. 자료형은 실행 시점(Runtime)에 결정된다.</li>
<li><strong>언어:</strong> Python, JavaScript 등.</li>
<li><strong>장점:</strong> 개발 속도가 빠르고 유연하지만, 실행 전까지 타입 에러를 발견하기 어렵다.</li>
</ul>
<p>-&gt; 아래는 파이썬(동적 타이핑)에 대한 내용</p>
<hr>
<h2 id="2-파이썬의-변수-할당과-재할당-메커니즘">2. 파이썬의 변수 할당과 재할당 메커니즘</h2>
<p>동적 타이핑 언어인 파이썬에서 <code>a = 5</code> 이후 <code>a = &quot;Hello&quot;</code>와 같이 자료형을 변경하며 재할당할 때의 내부 동작은 다음과 같다.</p>
<ol>
<li><strong>객체 생성:</strong> 메모리 공간에 <code>5</code>라는 정수 객체가 생성된다.</li>
<li><strong>참조(Binding):</strong> 변수 <code>a</code>라는 포스트잇이 <code>5</code> 객체에 부착된다.</li>
<li><strong>재할당:</strong> 새로운 <code>&quot;Hello&quot;</code> 문자열 객체가 생성되고, 포스트잇 <code>a</code>를 기존의 <code>5</code>에서 떼어내어 <code>&quot;Hello&quot;</code>에 다시 부착한다.</li>
<li><strong>가비지 컬렉션(GC):</strong> 어떠한 이름표도 붙어있지 않은(참조 카운트가 0인) 기존의 <code>5</code> 객체는 파이썬의 청소부인 가비지 컬렉터에 의해 메모리에서 삭제된다.</li>
</ol>
<hr>
<h2 id="3-별칭aliasing-현상과-mutable-객체의-주의점">3. 별칭(Aliasing) 현상과 Mutable 객체의 주의점</h2>
<p>하나의 객체에 여러 개의 이름표(변수)를 붙일 때 발생하는 현상을 <strong>Aliasing</strong>이라고 하며, 이는 데이터 무결성에 큰 영향을 미친다.</p>
<h3 id="31-수박watermelon-예시를-통한-고찰">3.1 수박(Watermelon) 예시를 통한 고찰</h3>
<p>두 명의 팀원이 하나의 수박을 공유하는 상황을 코드로 재현하면 다음과 같다.</p>
<pre><code class="language-python"># 1. 하나의 리스트 객체 생성 후 my_watermelon 이름표 부착
my_watermelon = [&quot;수박&quot;]

# 2. 새로운 이름표 our_food를 같은 객체에 부착 (Aliasing)
our_food = my_watermelon

# 3. id() 함수로 메모리 주소 확인 시 동일함 (id(my_watermelon) == id(our_food))
print(my_watermelon is our_food) # True</code></pre>
<h3 id="32-side-effect의-발생">3.2 Side Effect의 발생</h3>
<p>리스트와 같이 내부 수정이 가능한 <strong>Mutable(가변)</strong> 객체는 Aliasing 상태에서 의도치 않은 변경을 초래할 수 있다.</p>
<ul>
<li><strong>상황:</strong> 한 명이 <code>my_watermelon.append(&quot;꿀맛&quot;)</code>을 수행하면, 실제 데이터 자체가 수정된다.</li>
<li><strong>결과:</strong> 아무 작업도 하지 않은 다른 팀원의 <code>our_food</code>를 확인했을 때도 <code>[&quot;수박&quot;, &quot;꿀맛&quot;]</code>으로 변경되어 나타난다.</li>
</ul>
<p>이는 대규모 데이터 파이프라인 설계 시 원본 데이터를 보호하기 위해 반드시 <strong>깊은 복사(Deep Copy)</strong>와 같은 기법이 필요한 이유가 된다.</p>
<hr>
<h2 id="4-함수-인자-전달-메커니즘-키워드-인자keyword-argument">4. 함수 인자 전달 메커니즘: 키워드 인자(Keyword Argument)</h2>
<p>함수 호출 시 매개변수(Parameter)에 값을 직접 지정하여 전달하는 방식은 새로운 변수를 생성하는 것과는 구분되는 <strong>&#39;설정(Configuration)&#39;</strong>의 개념으로 이해해야 한다.</p>
<ul>
<li><p><strong>키워드 인자:</strong> 함수 정의 시 미리 지정된 매개변수 이름을 명시하여 인자(Argument)를 전달하는 방식이다. 인자의 순서와 상관없이 특정 매개변수에 값을 할당할 수 있다.</p>
</li>
<li><p><strong>주요 매개변수 분석 (<code>print</code> 함수 예시):</strong></p>
<ul>
<li><strong><code>end</code> (종료 문자):</strong> * <code>print</code> 함수 호출 종료 시 출력할 문자를 결정한다. <ul>
<li>기본값은 줄바꿈 문자(<code>\n</code>)이나, <code>end=&#39;&#39;</code>와 같이 명시함으로써 다음 출력문과의 연결 방식을 제어할 수 있다.</li>
</ul>
</li>
<li><strong><code>sep</code> (구분자, Separator):</strong> * 쉼표(<code>,</code>)로 나열된 여러 개의 출력 인자들 사이를 채울 문자를 결정한다.<ul>
<li>기본값은 공백(<code>&#39; &#39;</code>)이며, 이를 변경하여 간격을 없애거나 특정 기호(예: <code>-</code>, <code>/</code>)로 대체할 수 있다.<pre><code class="language-python"># sep 매개변수 활용 예시
print(&quot;010&quot;, &quot;1234&quot;, &quot;5678&quot;, sep=&quot;-&quot;) # 출력: 010-1234-5678</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>작동 원리:</strong> 위 매개변수들은 새로운 메모리 공간에 영구적인 변수를 생성하는 행위가 아니다. 함수 내부에 정의된 <strong>설정값(Default Value)을 사용자 정의 값으로 일시적으로 치환</strong>하는 행위이며, 해당 함수의 실행이 종료됨과 동시에 해당 설정은 소멸한다.</p>
</li>
</ul>
<hr>
<h2 id="5-결론-및-요약-mutable-vs-immutable-구별법">5. 결론 및 요약: Mutable vs Immutable 구별법</h2>
<p>효율적인 디버깅을 위해 자료형의 가변성을 파악하는 것이 필수적이다.</p>
<ol>
<li><strong>가변성 테스트:</strong> <code>x[0] = 1</code>과 같이 인덱스를 통한 직접 수정을 시도했을 때 에러가 나면 <strong>Immutable</strong>(str, tuple 등), 성공하면 <strong>Mutable</strong>(list, dict 등)이다.</li>
<li><strong>ID 추적:</strong> 값을 변경했을 때 <code>id()</code> 값이 바뀌면 Immutable(새 집으로 이사), 그대로이면 Mutable(현 거주지에서 수리)이다.</li>
</ol>
<table>
<thead>
<tr>
<th align="left">분류</th>
<th align="left">특징</th>
<th align="left">주요 자료형</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Immutable (불변)</strong></td>
<td align="left">값 변경 시 기존 객체를 수정하지 않고 새로운 객체를 생성함</td>
<td align="left"><code>int</code>, <code>str</code>, <code>tuple</code>, <code>bool</code></td>
</tr>
<tr>
<td align="left"><strong>Mutable (가변)</strong></td>
<td align="left">기존 객체의 메모리 주소를 유지한 채 내부의 값을 변경 가능함</td>
<td align="left"><code>list</code>, <code>dict</code>, <code>set</code></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stable Diffusion (이미지 생성)]]></title>
            <link>https://velog.io/@no-glass-otacku/Stable-Diffusion-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@no-glass-otacku/Stable-Diffusion-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Wed, 25 Mar 2026 05:45:35 GMT</pubDate>
            <description><![CDATA[<h3 id="서버를-다시-켤-때">서버를 다시 켤 때</h3>
<pre><code># webui.sh 파일이 있는 메인 폴더로 이동
cd ~/stable-diffusion-webui-forge

#가상 환경 입장
. ./venv/bin/activate

#서버 실행(엔진 켜기)
./webui.sh --share --enable-insecure-extension-access --gradio-auth 아이디:비번</code></pre><p>&lt;주의&gt;
Gradio 주소는 매번 바뀝니다: --share 옵션을 쓰면 실행할 때마다 <a href="https://xxxx.gradio.live">https://xxxx.gradio.live</a> 주소가 새로 생성됨. 이전에 썼던 주소로 접속하면 안열림.</p>
<h3 id="storage-만들기">Storage 만들기</h3>
<p>Azure에서 &#39;리소스 만들기&#39; &gt; &#39;스토리지 계정&#39;&gt; &#39;만들기&#39;&gt; 기본 옵션으로 생성</p>
<p>생성한 스토리지 계정으로 들어가서 왼쪽에 있는 바에서 &#39;데이터 스토리지&#39;&gt; &#39;컨테이너&#39;&gt; &#39;컨테이너 추가&#39;&gt; 기본 옵션으로 생성</p>
<h3 id="blob-storage와-동기화하기">Blob Storage와 동기화하기</h3>
<p>&lt;주의&gt; 
이미지를 하나라도 생성하고 동기화 진행해야 함.</p>
<blockquote>
<p>파일을 동기화 시키기 위해서는 인증 관련 정보가 필요합니다.
액세스 키를 이용해 접근하는 방법, Entra ID 를 통해 진행하는 방법 등 여러가지 방법이 있지만, 보안이 강화되어 있는 SAS 토큰을 통해 파일을 동기화 시킵니다.
<img src="https://velog.velcdn.com/images/no-glass-otacku/post/f30b5948-40b9-44df-bd6f-72556b31df6f/image.png" alt=""> -&gt; 3개 다 선택해야 함.
아래로 스크롤하여 &#39;SAS 및 연결 문자열 생성&#39; 버튼 누르고 아래에 있는 SAS 토큰을 복사!</p>
<blockquote>
<p>SAS란? Shared Access Signature 
임시 출입증, 호텔 카드키처럼 일시적으로 권한을 줄때 사용</p>
</blockquote>
</blockquote>
<p>cmd로 돌아와서 아래 위치로 이동</p>
<pre><code>cd ~/stable-diffusion-webui-forge</code></pre><p>해당 위치에서 upload라는 이름의 sh파일 생성</p>
<pre><code>vi upload </code></pre><p>그리고 sh파일 내부에 다음 내용을 넣기</p>
<pre><code>#!/bin/bash
# Azure Storage 계정 이름
STORAGE_ACCOUNT_NAME=&quot;fimtrusstorage4&quot; --&gt; 아까 생성한 storage 이름
# Azure Blob 컨테이너 이름
CONTAINER_NAME=&quot;stable-diffusion-images&quot; --&gt; storage 내부에 생성한 컨테이너 이름

# 동기화할 로컬 디렉토리 (현재 위치)
LOCAL_PATH=&quot;/home/azureuser/stable-diffusion-webui-forge/outputs/&quot;

# SAS TOKEN 정보 입력
SAS_TOKEN=&quot;&lt;SAS_TOKEN&gt;&quot; --&gt; 발급한 SAS 토큰 넣기
# Azure Blob Storage URL

# Azure Blob Storage URL
BLOB_URL=&quot;https://${STORAGE_ACCOUNT_NAME}.blob.core.windows.net/${CONTAINER_NAME}?${SAS_TOKEN}&quot;

# azcopy 명령어 실행 (동기화)
echo &quot;현재 위치의 모든 파일을 Azure Blob Storage &#39;${CONTAINER_NAME}&#39; 컨테이너와 동기화 시작...&quot;
azcopy sync &quot;$LOCAL_PATH&quot; &quot;$BLOB_URL&quot; --delete-destination=false --recursive --include-pattern=&quot;*.png&quot;
#azcopy sync &quot;$LOCAL_PATH&quot; &quot;$BLOB_URL&quot; --delete-destination=false --recursive --include-pattern=&quot;*.png;*.jpg;*.jpeg;*.webp;*.bmp;*.tiff;*.gif;*.mp4;*.mov;*.avi;*.mkv&quot;

if [ $? -eq 0 ]; then
  echo &quot;동기화 완료.&quot;
else
  echo &quot;동기화 실패.&quot;
fi</code></pre><p>마지막으로 sh uplaod를 입력하면 동기화 완료.</p>
<h3 id="storage에서-사진-확인">Storage에서 사진 확인</h3>
<h3 id="여러-모델-사용하기">여러 모델 사용하기</h3>
<p><a href="https://stable-diffusion-art.com/models/">https://stable-diffusion-art.com/models/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ ch14 chains]]></title>
            <link>https://velog.io/@no-glass-otacku/ch14-chains</link>
            <guid>https://velog.io/@no-glass-otacku/ch14-chains</guid>
            <pubDate>Sat, 21 Feb 2026 19:33:46 GMT</pubDate>
            <description><![CDATA[<h2 id="문서요약-체인">문서요약 체인</h2>
<h4 id="📋-문서-요약-방식별-선택-가이드">📋 문서 요약 방식별 선택 가이드</h4>
<table>
<thead>
<tr>
<th align="left">요약 방식</th>
<th align="left">선택 기준</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Stuff</strong></td>
<td align="left">문서가 짧고 한 번에 끝내고 싶을 때</td>
</tr>
<tr>
<td align="left"><strong>Map-Reduce</strong></td>
<td align="left">문서가 매우 길고 빠른 처리가 필요할 때</td>
</tr>
<tr>
<td align="left"><strong>Map-Refine</strong></td>
<td align="left">문서의 전체 맥락 연결이 중요할 때</td>
</tr>
<tr>
<td align="left"><strong>Chain of Density</strong></td>
<td align="left">가장 정보 밀도가 높은 완벽한 요약이 필요할 때</td>
</tr>
<tr>
<td align="left"><strong>Clustering</strong></td>
<td align="left">내용이 중복되는 방대한 자료를 정리할 때</td>
</tr>
</tbody></table>
<h4 id="1-stuff-몽땅-집어넣기">1. Stuff (몽땅 집어넣기)</h4>
<p>원리: 모든 문서를 하나의 프롬프트에 통째로 넣고 &quot;요약해줘&quot;라고 시키는 가장 단순한 방식입니다.</p>
<p>장점: 가장 빠르고 비용이 저렴하며, 문맥을 한 번에 파악하기 좋습니다.</p>
<p>단점: 문서가 너무 길면 AI가 읽을 수 있는 한계를 초과하여 에러가 발생합니다.</p>
<pre><code>stuff_chain = create_stuff_documents_chain(llm, prompt)</code></pre><h4 id="2-map-reduce-분할-요약-후-병합">2. Map-Reduce (분할 요약 후 병합)</h4>
<p>원리: 문서를 여러 조각으로 나눠서 각각 요약한 뒤(Map), 그 요약본들을 다시 모아서 최종 요약을 만듭니다(Reduce).</p>
<p>장점: 아주 방대한 양의 문서도 처리할 수 있고, 각 조각을 동시에 요약하므로 속도가 빠릅니다.</p>
<p>단점: 전체를 관통하는 미묘한 맥락이 요약 과정에서 사라질 수 있습니다.</p>
<pre><code>from langchain_core.runnables import chain


@chain
def map_reduce_chain(docs):
    map_llm = ChatOpenAI(
        temperature=0,
        #단순 반복 작업이 많으므로, 속도가 빠르고 가격이 *저렴한 가성비 모델*
        model_name=&quot;gpt-4o-mini&quot;,
    )

    # map prompt 다운로드
    map_prompt = hub.pull(&quot;teddynote/map-prompt&quot;)

    # map chain 생성
    map_chain = map_prompt | map_llm | StrOutputParser()

    # *병렬 처리 (batch)*
    doc_summaries = map_chain.batch(docs)

    # reduce prompt 다운로드
    reduce_prompt = hub.pull(&quot;teddynote/reduce-prompt&quot;)
    reduce_llm = ChatOpenAI(
        #문맥 파악 능력 위해 성능이 더 좋은 *고성능 모델*
        model_name=&quot;gpt-4o&quot;,
        temperature=0,
        callbacks=[StreamingCallback()],
        streaming=True,
    )

    reduce_chain = reduce_prompt | reduce_llm | StrOutputParser()

    return reduce_chain.invoke({&quot;doc_summaries&quot;: doc_summaries, &quot;language&quot;: &quot;Korean&quot;})
</code></pre><p>Map-Reduce에서 체인을 분리하는 이유는 각 단계의 특성에 맞는 AI 모델을 선택하여 비용을 절감하고, 병렬 처리를 통해 대량의 문서를 빠르게 요약하기 위함입니다.</p>
<h4 id="3-map-refine-점진적-보완-요약">3. Map-Refine (점진적 보완 요약)</h4>
<p>원리: 첫 번째 조각을 요약한 뒤, 그 요약본을 다음 조각과 함께 넘겨서 내용을 업데이트합니다. 이 과정을 마지막 조각까지 반복하며 요약을 완성해 나갑니다.</p>
<p>장점: 앞뒤 맥락이 잘 이어지며, 요약의 디테일이 살아있습니다.</p>
<p>단점: 순차적으로 작업해야 해서 속도가 느리고, 뒤로 갈수록 초기 내용이 희미해질 수 있습니다.</p>
<pre><code>ed!from langchain_core.runnables import chain


@chain
def map_refine_chain(docs):

    # map chain 생성
    map_summary = hub.pull(&quot;teddynote/map-summary-prompt&quot;)

    map_chain = (
        map_summary
        | ChatOpenAI(
            model_name=&quot;gpt-4o-mini&quot;,
            temperature=0,
        )
        | StrOutputParser()
    )

    input_doc = [{&quot;documents&quot;: doc.page_content, &quot;language&quot;: &quot;Korean&quot;} for doc in docs]

    # 첫 번째 프롬프트, ChatOpenAI, 문자열 출력 파서를 연결하여 체인을 생성합니다.
    doc_summaries = map_chain.batch(input_doc)

    refine_prompt = hub.pull(&quot;teddynote/refine-prompt&quot;)

    refine_llm = ChatOpenAI(
        model_name=&quot;gpt-4o-mini&quot;,
        temperature=0,
        callbacks=[StreamingCallback()],
        streaming=True,
    )

    refine_chain = refine_prompt | refine_llm | StrOutputParser()

    previous_summary = doc_summaries[0]

    for current_summary in doc_summaries[1:]:

        previous_summary = refine_chain.invoke(
            {
                &quot;previous_summary&quot;: previous_summary,
                &quot;current_summary&quot;: current_summary,
                &quot;language&quot;: &quot;Korean&quot;,
            }
        )
        print(&quot;\n\n-----------------\n\n&quot;)

    return previous_summary</code></pre><p>-&gt; 원래 Refine 방식은 <strong>이전 요약 + 다음 원문</strong>을 결합하는 것이 정석입니다. 
하지만 본 실습 코드에서는 토큰(입력량) 제한을 고려하여, <strong>각 페이지를 먼저 요약(batch)한 뒤 요약본끼리 Refine 하는 구조</strong>를 취하고 있습니다. 요약본 + 요약본</p>
<h4 id="4-chain-of-density-밀도-보완-반복-요약">4. Chain of Density (밀도 보완 반복 요약)</h4>
<p>&quot;Chain of Density&quot; (CoD) 프롬프트는 GPT-4를 사용한 요약 생성을 개선하기 위해 개발된 기법입니다.</p>
<p>원리: 요약을 한 번으로 끝내지 않고 여러 번 반복 실행합니다. 이때 핵심 정보(Entity)가 누락되지 않았는지 체크하며 요약문의 밀도를 점점 높여갑니다.</p>
<p>장점: 인간이 작성한 요약과 비슷한 밀도를 가진 고품질 요약본이 나옵니다. 원문의 앞부분에 치우치는 경향(lead bias)이 덜합니다.</p>
<p>단점: 여러 번 반복 실행하므로 비용과 시간이 많이 듭니다.</p>
<h4 id="5-clustering-map-refine-군집화-기반-정예-요약">5. Clustering-Map-Refine (군집화 기반 정예 요약)</h4>
<p>원리: 문서 조각(Chunk)들을 비슷한 내용끼리 그룹(클러스터)으로 묶습니다. 각 그룹에서 가장 대표적인 중심 문서들만 골라 Refine 방식으로 요약합니다.</p>
<p>장점: 중복된 내용은 과감히 생략하고 방대한 양의 핵심 주제를 효율적으로 요약할 수 있습니다.</p>
<p>단점: 그룹을 나누는 &#39;클러스터링&#39; 과정이 추가되어 기술적으로 더 복잡합니다.</p>
<h2 id="sql쿼리-만드는-체인-create_sql_query_chain">SQL쿼리 만드는 체인: create_sql_query_chain</h2>
<p>사용자의 질문(자연어)을 LLM이 분석하여, 데이터를 추출하기 위한 최적의 <strong>SQL 쿼리를 스스로 생성</strong>합니다. 이렇게 생성된 쿼리를 통해 데이터베이스를 실시간으로 조회하여 정확한 정보를 찾아냅니다.</p>
<pre><code>from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool

# SQLite 데이터베이스에 연결
db = SQLDatabase.from_uri(&quot;sqlite:///data/finance.db&quot;)
llm = ChatOpenAI(model=&quot;gpt-3.5-turbo&quot;, temperature=0)

# 생성한 쿼리를 실행하는 도구
execute_query = QuerySQLDataBaseTool(db=db)

# SQL 쿼리 생성
# 기본 프롬프트가 내장되어 있지만 매개변수로 Prompt를 추가해서 엉뚱한 쿼리 생성방지에 도움을 줄 수 있음 (아래에 서술)
write_query = create_sql_query_chain(llm, db)

# 생성한 쿼리를 실행하기 위한 체인을 생성합니다.
chain = write_query | execute_query</code></pre><blockquote>
<p> Prompt를 직접 작성해서 넣는 법
prompt = PromptTemplate.from_template(&quot;프롬프트 작성&quot;).partial(dialect=db.dialect) 
chain = create_sql_query_chain(llm, db, prompt)</p>
</blockquote>
<pre><code>chain.invoke({&quot;question&quot;: &quot;테디의 이메일을 조회하세요&quot;})</code></pre><p>위 방법으로는 답변이 <strong>단답형 형식으로 출력</strong>되므로 더 친절한 답변을 받기 위해서는 답변을 LLM으로 증강생성 하면 된다.</p>
<blockquote>
<p>-965.7 -&gt; &#39;테디의 transaction의 합계는 -965.7 입니다.&#39;</p>
</blockquote>
<pre><code>#LCEL 문법의 체인 사용
#친절한 답변 생성
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough

answer_prompt = PromptTemplate.from_template(
    &quot;&quot;&quot;Given the following user question, corresponding SQL query, and SQL result, answer the user question.

Question: {question}
SQL Query: {query}
SQL Result: {result}
Answer: &quot;&quot;&quot;
)

# 지시서(prompt)를 읽고, 모델(llm)이 생각해서, 문자열(Parser)로 답해라
answer = answer_prompt | llm | StrOutputParser()

# 생성한 쿼리를 실행하고 결과를 출력하기 위한 체인을 생성합니다.
chain = (
  RunnablePassthrough
  .assign(query=write_query)
  .assign(result=itemgetter(&quot;query&quot;) | execute_query)
  | answer
)</code></pre><p>RunnablePassthrough.assign(...): 기존에 있던 데이터는 그대로 두고, 옆에 새로운 정보를 <strong>&#39;추가&#39;</strong>해서 다음 단계로 넘김</p>
<pre><code>assign(query=write_query) 실행 결과: {&quot;question&quot;: &quot;...&quot;, &quot;query&quot;: &quot;SELECT...&quot;}

result=itemgetter(&quot;query&quot;) | execute_query
#itemgetter로 query를 가져와서 execute_query로 전달 후 result를 가져옴
실행결과: {&quot;question&quot;: &quot;...&quot;, &quot;query&quot;: &quot;...&quot;, &quot;result&quot;: &quot;[(&#39;...&#39;)]&quot;} 
-&gt; answer에 저장</code></pre><h3 id="sql-agent">SQL Agent</h3>
<p>Agent를 활용하여 Sql 쿼리를 생성하고 실행 결과를 답변으로 출력이 가능합니다.</p>
<p>&#39;A와 B를 비교해줘&#39; 같이 질문이 복잡해서 한 번의 쿼리로 안 끝날 때, 에이전트는 스스로 계획을 세워 여러 번 DB를 뒤져보고 최종 답을 냅니다.</p>
<p> <strong>[생각 → 행동 → 관찰]</strong>의 과정을 반복합니다.</p>
<pre><code>from langchain_openai import ChatOpenAI
from langchain_community.utilities import SQLDatabase
from langchain_community.agent_toolkits import create_sql_agent

llm = ChatOpenAI(model=&quot;gpt-3.5-turbo&quot;, temperature=0)
db = SQLDatabase.from_uri(&quot;sqlite:///data/finance.db&quot;)

# Agent 생성
agent_executor = create_sql_agent(llm, db=db, agent_type=&quot;openai-tools&quot;, verbose=True)
agent_executor.invoke(
    {&quot;input&quot;: &quot;테디와 셜리의 transaction 의 합계를 구하고 비교하세요&quot;}
)</code></pre><p>실행 과정</p>
<pre><code>Entering new SQL Agent Executor chain...
---1 어떤 테이블 있나 확인
Invoking: `sql_db_list_tables` with `{}`
accounts, customers, transactions

---2 &#39;transactions&#39; 태이블 구조 파악
Invoking: `sql_db_schema` with `{&#39;table_names&#39;: &#39;transactions&#39;}`

CREATE TABLE transactions (
    transaction_id INTEGER, 
    account_id INTEGER, 
    amount REAL, 
    transaction_date TEXT, 
    PRIMARY KEY (transaction_id), 
    FOREIGN KEY(account_id) REFERENCES accounts (account_id)
)

/* 예시 데이터 3줄
3 rows from transactions table:
transaction_id  account_id  amount  transaction_date
1   1   74.79   2024-07-13
2   1   -224.1  2024-05-13
3   1   -128.9  2024-01-25
*/

---3 정확한 쿼리 생성 및 조회
Invoking: `sql_db_query` with `{&#39;query&#39;: &#39;SELECT account_id, SUM(amount) AS total_amount FROM transactions WHERE account_id IN (1, 2) GROUP BY account_id&#39;}`

[(1, -965.7), (2, 743.13)] 
테디의 거래 합계는 -965.7이고, 셜리의 거래 합계는 743.13입니다.

Finished chain.</code></pre><h4 id="🛠️-실무-적용-시-반드시-고려할-점">🛠️ 실무 적용 시 반드시 고려할 점</h4>
<p>LLM 기반의 SQL 에이전트는 편리하지만, 실제 서비스에 적용하려면 안정성과 효율성을 꼭 따져봐야 합니다.</p>
<ol>
<li><p><strong>보안: &quot;읽기 전용&quot;은 필수</strong>
AI가 실수로 데이터를 지우거나 수정하지 않도록, DB 접속 시 <strong>읽기 전용(Read-Only) 계정</strong>을 연결해야 합니다. 또한, 위험한 명령어(DROP, DELETE 등)가 포함되었는지 검사하는 <strong>Query Validator</strong> 단계를 추가하는 것이 일반적입니다.</p>
</li>
<li><p><strong>비용과 속도: 하이브리드 전략</strong>
에이전트는 스스로 판단하는 과정에서 AI 모델을 다회 호출(Multi-turn)하므로 API 비용 상승과 응답 지연이 발생합니다. 단순한 질문은 <strong>Chain(고정)</strong>으로, 복잡한 분석은 <strong>Agent(자율 판단)</strong>로 처리하는 설계가 효율적입니다.</p>
</li>
<li><p><strong>컨텍스트 최적화 (Context Management)</strong>
DB의 모든 구조를 AI에게 넘기면 오히려 헷갈려 할 수 있습니다. 꼭 필요한 테이블 정보와 컬럼 설명만 골라 전달하여 AI가 엉뚱한 답변(환각)을 하지 않도록 관리해야 합니다.</p>
</li>
</ol>
<h4 id="🧰-함께-살펴보면-좋은-도구들">🧰 함께 살펴보면 좋은 도구들</h4>
<p>랭체인 외에도 현업에서 자주 언급되는 대안들입니다.</p>
<p><strong>LlamaIndex</strong>: 랭체인과 양대 산맥입니다. 특히 데이터를 찾고 연결하는 기능(RAG)에 특화되어 있어 SQL 관련 작업에서도 많이 쓰입니다.</p>
<p><strong>Vanna.ai</strong>: &#39;Text-to-SQL&#39;에만 집중한 특화 툴로, 회사의 과거 쿼리 데이터를 학습시켜 정확도를 높이기 좋습니다.</p>
<p><strong>LangGraph</strong>: 랭체인의 확장판입니다. 에이전트의 사고 과정을 더 세밀하게 제어하고 싶을 때(예: &quot;먼저 승인을 받고 쿼리를 실행해&quot;) 사용합니다.</p>
<h2 id="구조화된-출력-만드는-체인-with_structured_output">구조화된 출력 만드는 체인: with_structured_output</h2>
<p>AI 답변을 파싱(글자 쪼개기)하느라 고생할 필요 없이, 처음부터 API처럼 규격에 맞는 데이터를 받는 기술.</p>
<p>사람이 아닌 &#39;프로그램&#39;이 AI의 답변을 소비할 때 사용!</p>
<h4 id="예시-4지선다형-퀴즈를-생성">예시) 4지선다형 퀴즈를 생성</h4>
<ol>
<li>AI에게 줄 <strong>&#39;데이터의 틀&#39;</strong>를 만드는 작업</li>
</ol>
<p>Quiz 클래스는 퀴즈의 질문, 난이도, 그리고 네 개의 선택지를 정의</p>
<pre><code>class Quiz(BaseModel):
    question: str = Field(..., description=&quot;퀴즈의 질문&quot;)
    level: str = Field(...)
    options: List[str] = Field(...)</code></pre><pre><code>
llm = ChatOpenAI(model=&quot;gpt-4o&quot;, temperature=0.1)
prompt = ChatPromptTemplate.from_messages(
    [
        (
            &quot;system&quot;,
            &quot;You&#39;re a world-famous quizzer and generates quizzes in structured formats.&quot;,
        ),
        (
            &quot;human&quot;,
            &quot;TOPIC 에 제시된 내용과 관련한 4지선다형 퀴즈를 출제해 주세요. 만약, 실제 출제된 기출문제가 있다면 비슷한 문제를 만들어 출제하세요.&quot;
            &quot;단, 문제에 TOPIC 에 대한 내용이나 정보는 포함하지 마세요. \nTOPIC:\n{topic}&quot;,
        ),
        (&quot;human&quot;, &quot;Tip: Make sure to answer in the correct format&quot;),
    ]
)</code></pre><ol start="2">
<li>with_structured_output(Quiz) (AI에게 틀 전달)<pre><code># 구조화된 출력을 위한 모델 생성
llm_with_structured_output = llm.with_structured_output(Quiz)
</code></pre></li>
</ol>
<h1 id="퀴즈-생성-체인-생성">퀴즈 생성 체인 생성</h1>
<p>chain = prompt | llm_with_structured_output</p>
<pre><code></code></pre><h1 id="퀴즈-생성을-요청합니다">퀴즈 생성을 요청합니다.</h1>
<p>generated_quiz = chain.invoke({&quot;topic&quot;: &quot;ADSP(데이터 분석 준전문가) 자격 시험&quot;})</p>
<h1 id="생성된-퀴즈-출력">생성된 퀴즈 출력</h1>
<p>print(f&quot;{generated_quiz.question} (난이도: {generated_quiz.level})\n&quot;)
for i, opt in enumerate(generated_quiz.options):
    print(f&quot;{i+1}) {opt}&quot;)</p>
<pre><code></code></pre><p>#결과물
다음 중 데이터 분석의 과정에서 가장 먼저 수행해야 하는 단계는 무엇인가요? (난이도: 보통)</p>
<p>1) 데이터 수집
2) 데이터 전처리
3) 문제 정의
4) 모델 평가</p>
<p>```
3. 데이터의 흐름</p>
<p>① 입력: &quot;ADSP 시험에 대한 퀴즈를 내줘&quot;라고 요청합니다.</p>
<p>② 처리: AI가 내용을 생성한 뒤, Quiz 클래스 양식에 맞게 데이터를 칸칸이 집어넣습니다.</p>
<p>③ 출력: 결과물이 문장이 아니라 generated_quiz.question처럼 변수명으로 바로 접근할 수 있는 형태로 나옵니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CH08 Embedding]]></title>
            <link>https://velog.io/@no-glass-otacku/CH08-Embedding</link>
            <guid>https://velog.io/@no-glass-otacku/CH08-Embedding</guid>
            <pubDate>Sun, 04 Jan 2026 12:06:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-openai-임베딩-모델-실습">1. OpenAI 임베딩 모델 실습</h2>
<p>주요 도구: OpenAIEmbeddings</p>
<p>지원되는 모델 목록</p>
<table>
<thead>
<tr>
<th align="left">MODEL</th>
<th align="center">PAGES PER DOLLAR</th>
<th align="center">PERFORMANCE ON MTEB EVAL</th>
<th align="center">MAX INPUT</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>text-embedding-3-small</strong></td>
<td align="center">62,500</td>
<td align="center">62.3%</td>
<td align="center">8191</td>
</tr>
<tr>
<td align="left"><strong>text-embedding-3-large</strong></td>
<td align="center">9,615</td>
<td align="center">64.6%</td>
<td align="center">8191</td>
</tr>
<tr>
<td align="left"><strong>text-embedding-ada-002</strong></td>
<td align="center">12,500</td>
<td align="center">61.0%</td>
<td align="center">8191</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th align="left">모델명</th>
<th align="left">특징</th>
<th align="left">추천 상황</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>text-embedding-3-small</strong></td>
<td align="left">가장 경제적이며 성능도 준수함</td>
<td align="left">가성비가 중요한 대규모 서비스</td>
</tr>
<tr>
<td align="left"><strong>text-embedding-3-large</strong></td>
<td align="left">최고 성능, 가격은 가장 비쌈</td>
<td align="left">정확도가 최우선인 전문 검색 서비스</td>
</tr>
<tr>
<td align="left"><strong>text-embedding-ada-002</strong></td>
<td align="left">과거의 표준 모델</td>
<td align="left">굳이 새로 쓸 필요 없는 구형 모델</td>
</tr>
</tbody></table>
<p>어떤 모델을 사용해서 embedding 할건지 정하고 (여기서는 text-embedding-3-small) 텍스트를 임베딩하면 벡터화(숫자)되어 리스트에 담긴 것을 확인 가능합니다.</p>
<h4 id="embed_query-검색용">embed_query (검색용)</h4>
<p>사용자의 질문 단 하나를 벡터로 바꿀 때</p>
<pre><code>embeddings = OpenAIEmbeddings(model=&quot;text-embedding-3-small&quot;)
text = &quot;임베딩 테스트를 하기 위한 샘플 문장입니다.&quot;
query_result = embeddings.➡️embed_query(text)</code></pre><blockquote>
<p>[-0.00776276458054781, 0.03680367395281792, 0.019545823335647583, -0.0196656696498394, 0.017203375697135925]</p>
</blockquote>
<h4 id="embed_documents-저장용">embed_documents (저장용)</h4>
<p>많은 양의 문서를 한꺼번에 벡터로 바꿀 때
-&gt; 항상 문자열의 리스트를 입력받고, 각 문서에 대한 임베딩 벡터를 포함하는 2차원 리스트를 반환함.</p>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">embed_documents</th>
<th align="left">embed_query</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>입력 형태</strong></td>
<td align="left"><strong>리스트</strong> (List of Strings)</td>
<td align="left"><strong>문자열</strong> (Single String)</td>
</tr>
<tr>
<td align="left"><strong>주요 용도</strong></td>
<td align="left">데이터베이스(Vector Store) 구축용</td>
<td align="left">사용자의 질문(검색어) 처리용</td>
</tr>
<tr>
<td align="left"><strong>특징</strong></td>
<td align="left">여러 문서를 한꺼번에 배치(Batch) 처리</td>
<td align="left">단일 문장의 의도 파악에 최적화</td>
</tr>
</tbody></table>
<h3 id="차원dimension-지정-기능이-필요한-이유">차원(Dimension) 지정 기능이 필요한 이유?</h3>
<blockquote>
<p>임베딩 차원 지정은 <strong>검색 속도와 저장 비용을 최적화</strong>하면서도, 핵심 의미 정보를 유지하여 <strong>시스템 효율과 정확도 사이의 최적의 밸런스</strong>를 찾기 위해 필요합니다.</p>
</blockquote>
<p>&#39;dimensions=1024&#39; 옵션을 추가하여 차원 지정가능</p>
<pre><code>embeddings_1024 = OpenAIEmbeddings(model=&quot;text-embedding-3-small&quot;, ➡️dimensions=1024)
</code></pre><ul>
<li><p>비용 및 저장소 최적화: 차원을 줄이면 벡터 DB 용량이 감소하여 인프라 유지 비용이 대폭 절감됩니다.</p>
</li>
<li><p>검색 속도(Latency) 향상: 저차원 벡터는 유사도 계산 연산량이 적어 대규모 데이터셋에서 훨씬 빠른 응답 속도를 보장합니다.</p>
</li>
<li><p>정확도 밸런스: &#39;매트료슈카 임베딩&#39; 기술 덕분에 차원을 줄여도 핵심 의미 정보는 대부분 유지되므로, 성능 손실을 최소화하며 효율을 극대화할 수 있습니다.</p>
</li>
<li><p>환경 적응성: 메모리가 제한된 모바일이나 엣지 디바이스 환경에 맞춰 모델 부하를 조절할 수 있습니다.</p>
</li>
</ul>
<h2 id="2-캐시를-이용한-임베딩-최적화">2. 캐시를 이용한 임베딩 최적화</h2>
<p>주요 도구: CacheBackedEmbeddings</p>
<p>이유: 임베딩은 비용(돈/시간)이 듭니다. 똑같은 문장을 매번 새로 계산하는 건 비효율적이죠.</p>
<h3 id="cachebackedembeddings">CacheBackedEmbeddings</h3>
<p>embeddings를 키-값 저장소에 캐싱하는 embedder 주변에 래퍼입니다.</p>
<ul>
<li>알맹이: 실제 계산을 담당하는 OpenAIEmbeddings.</li>
<li>껍데기(Wrapper): 계산된 결과를 저장하고 관리하는 CacheBackedEmbeddings.</li>
</ul>
<h4 id="🛠️-cachebackedembeddings-초기화-매개변수">🛠️ CacheBackedEmbeddings 초기화 매개변수</h4>
<p>캐시 임베더를 설정할 때는 <code>from_bytes_store</code> 메서드를 통해 다음 세 가지 핵심 부품을 연결합니다.</p>
<ol>
<li><strong>underlying_embeddings</strong>: 실제 임베딩 계산을 수행할 모델 엔진 (예: OpenAI).</li>
<li><strong>document_embedding_cache</strong>: 계산된 벡터를 바이트 단위로 보관할 저장소 (ByteStore).</li>
<li><strong>namespace</strong>: 저장소 내의 독립된 구역 이름. <ul>
<li><strong>주의</strong>: 서로 다른 모델이 동일한 텍스트에 대해 생성한 벡터가 섞이지 않도록, 모델명을 네임스페이스로 지정하는 것이 권장됩니다.</li>
</ul>
</li>
</ol>
<h4 id="localfilestore-에서-임베딩-사용-영구-보관">LocalFileStore 에서 임베딩 사용 (영구 보관)</h4>
<pre><code># 캐시를 지원하는 임베딩 생성
embedding = OpenAIEmbeddings()

cached_embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings=embedding,
    document_embedding_cache=➡️LocalFileStore(&quot;./cache/&quot;), # 로컬 파일 저장소 설정
    namespace=embedding.model  # 기본 임베딩과 저장소를 사용하여 캐시 지원 임베딩을 생성
)</code></pre><h4 id="inmemorybytestore-사용-비영구적">InmemoryByteStore 사용 (비영구적)</h4>
<pre><code>from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import InMemoryByteStore

store = ➡️InMemoryByteStore()  # 메모리 내 바이트 저장소 생성

# 캐시 지원 임베딩 생성
cached_embedder = CacheBackedEmbeddings.from_bytes_store(
    embedding, store, namespace=embedding.model
)
</code></pre><blockquote>
<p>비용 절감과 데이터 보존이 중요한 실무 환경에는 LocalFile을, 속도가 중요하고 일회성인 작업에는 InMemory를 선택</p>
</blockquote>
<h2 id="3-오픈소스-모델-활용-huggingface">3. 오픈소스 모델 활용: HuggingFace</h2>
<p>주요 도구: HuggingFaceEmbeddings</p>
<p>특징: OpenAI API(유료) 없이 내 로컬(코랩) 환경에서 무료 오픈소스 모델로 갈아타는 방법.</p>
<h4 id="쓸만한-한국어-임베딩-모델">쓸만한 한국어 임베딩 모델</h4>
<ul>
<li><p>intfloat/multilingual-e5-large (3~4위):
현재 오픈소스 모델 중 최상위권 성능입니다.
instruct 버전은 질문과 답변의 맥락을 더 잘 구분하도록 튜닝되어 있습니다.</p>
</li>
<li><p>BAAI/bge-m3 (6위):
한국어 임베딩의 교과서 같은 모델입니다.
성능이 안정적이고, 다국어 지원이 강력해서 실무에서 가장 많이 쓰입니다.</p>
</li>
<li><p>e5-base / e5-small (7~8위):
성능은 상위권 모델보다 조금 낮지만, 가볍고 속도가 매우 빠릅니다.
컴퓨터 자원이 부족하거나 빠른 응답이 필요할 때 선택합니다.</p>
</li>
</ul>
<h3 id="huggingface-endpoint-embedding">HuggingFace Endpoint Embedding</h3>
<p>모델을 내 컴퓨터가 아닌 별도의 <strong>전용 서버(Endpoint)</strong>에 올려놓고 API 형태로 호출하는 방식</p>
<pre><code>from langchain_huggingface.embeddings import HuggingFaceEndpointEmbeddings

model_name = &quot;intfloat/multilingual-e5-large-instruct&quot;

hf_embeddings = ➡️HuggingFaceEndpointEmbeddings(
    model=model_name,
    task=&quot;feature-extraction&quot;,
    huggingfacehub_api_token=os.environ[&quot;HF_TOKEN&quot;],
)

embedded_documents = hf_embeddings.embed_documents(texts)
embedded_query = hf_embeddings.embed_query(&quot;LangChain 에 대해서 알려주세요.&quot;)</code></pre><h3 id="huggingface-embeddings">HuggingFace Embeddings</h3>
<p>내 컴퓨터(Local)의 GPU를 사용하여 임베딩을 생성합니다. 비용이 0원이며 보안이 중요한 프로젝트에 필수적</p>
<pre><code>from langchain_huggingface.embeddings import HuggingFaceEmbeddings

model_name = &quot;intfloat/multilingual-e5-large-instruct&quot;

hf_embeddings = HuggingFaceEmbeddings(
    model_name=model_name,
    model_kwargs={&quot;device&quot;: &quot;cuda&quot;},  # cuda, cpu-&gt; 내 컴퓨터 가동
    encode_kwargs={&quot;normalize_embeddings&quot;: True}, 
    # 추출된 벡터들의 길이를 1로 맞추는 정규화 작업 -&gt; 유사도를 구할 때 복잡한 계산 없이 단순한 &#39;내적&#39;만으로도 정확한 비교가 가능
)
</code></pre><h3 id="임베딩-방식별-비교표">임베딩 방식별 비교표</h3>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">Dense (밀집)</th>
<th align="left">b. Sparse (희소)</th>
<th align="left">Multi-Vector (다중)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>대표 모델</strong></td>
<td align="left"><strong>OpenAI</strong></td>
<td align="left"></td>
<td align="left"><strong>c. ColBERT</strong></td>
</tr>
<tr>
<td align="left"><strong>비유</strong></td>
<td align="left">문장의 전체적인 분위기</td>
<td align="left">문장 속 핵심 단어</td>
<td align="left">문장 속 모든 단어의 관계</td>
</tr>
<tr>
<td align="left"><strong>수학적 특징</strong></td>
<td align="left">고정된 차원에 정보 압축</td>
<td align="left">단어 사전 기반 점수 부여</td>
<td align="left">문장을 여러 벡터로 분할 저장</td>
</tr>
<tr>
<td align="left"><strong>장점</strong></td>
<td align="left">문맥 및 유사어 파악 능력이 좋음</td>
<td align="left">고유명사를 정확하게 검색함</td>
<td align="left">검색 정밀도가 가장 높음</td>
</tr>
<tr>
<td align="left"><strong>단점</strong></td>
<td align="left">특정 단어 포함 여부 확인이 어려움</td>
<td align="left">문맥(의미) 파악이 불가능함</td>
<td align="left">저장 용량이 크고 속도가 느림</td>
</tr>
</tbody></table>
<p><strong>a. BGE-M3</strong>는 세 가지 방식을 모델 하나로 모두 구현하고 있음❕❕❕</p>
<h3 id="a-bge-m3-임베딩🌟">a. BGE-M3 임베딩🌟</h3>
<p>현재 한국어를 포함한 다국어 검색에서 가장 가성비 좋고 강력한 오픈소스 모델이기 때문에 실무 표준으로 소개됨</p>
<pre><code>from langchain_huggingface import HuggingFaceEmbeddings

model_name = ➡️&quot;BAAI/bge-m3&quot;
model_kwargs = {&quot;device&quot;: &quot;cuda&quot;}
encode_kwargs = {&quot;normalize_embeddings&quot;: True}

hf_embeddings = HuggingFaceEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)
</code></pre><h4 id="랭체인langchain말고-모델-제조사baai가-직접-만든-전용-라이브러리flagembedding를-사용하는-방법">랭체인(langchain)말고 모델 제조사(BAAI)가 직접 만든 전용 라이브러리(FlagEmbedding)를 사용하는 방법</h4>
<p>랭체인의 HuggingFaceEmbeddings는 범용적이지만, BGE-M3 전용 기능(Sparse, Multi-vector 추출 등)을 세밀하게 제어하기에는 한계가 있습니다.</p>
<p>제조사가 제공하는 라이브러리를 쓰면 use_fp16(반정밀도 연산) 같은 속도 최적화 옵션을 더 직접적으로 사용할 수 있습니다.</p>
<pre><code># FlagEmbedding 설치
!pip install -qU FlagEmbedding</code></pre><h4 id="1번-스타일-일단-계산시키고-dense_vecs만-사용하기">1번 스타일: 일단 계산시키고 dense_vecs만 사용하기</h4>
<pre><code>from FlagEmbedding import BGEM3FlagModel

bge_embeddings = BGEM3FlagModel(
    &quot;BAAI/bge-m3&quot;, use_fp16=True
)  # use_fp16을 True로 설정하면 약간의 성능 저하와 함께 숫자를 표현하는 비트 수를 줄여 계산량을 줄어 빨라짐.

bge_embedded = bge_embeddings.encode(
    texts,
    batch_size=12,
    max_length=8192,  # 한번에 읽는 토큰수:작은 값을 설정하여 프로세스의 속도를 높일 수 있음.
)➡️[&quot;dense_vecs&quot;]
</code></pre><p>[&quot;dense_vecs&quot;]
-&gt;BGE-M3는 Dense, Sparse, ColBERT 결과를 동시에 생성하는 멀티태스킹 모델이므로, encode() 결과 중 필요한 데이터 형태(dense_vecs)만 명시적으로 추출하여 사용하려고 넣은 코드.</p>
<h4 id="2번-스타일-처음부터-dense-결과만-돌려주도록-세팅">2번 스타일: 처음부터 dense 결과만 돌려주도록 세팅</h4>
<pre><code>from FlagEmbedding import BGEM3FlagModel

bge_flagmodel = BGEM3FlagModel(
    &quot;BAAI/bge-m3&quot;, use_fp16=True
)  

bge_encoded = bge_flagmodel.encode(texts, ➡️return_dense=True)
</code></pre><h3 id="b-sparse-embedding-lexical-weight">b. Sparse Embedding (Lexical Weight)</h3>
<p>기존 밀집(Dense) 임베딩은 문맥은 잘 잡지만 &quot;고유 명사(예: 제품명, 이름)&quot; 검색에 약합니다. 이를 보완하기 위해 키워드 일치 방식(Lexical)의 가중치를 더해 검색 정확도를 높임</p>
<pre><code>bge_flagmodel = BGEM3FlagModel(
    &quot;BAAI/bge-m3&quot;, use_fp16=True
)  # use_fp16을 True로 설정하면 약간의 성능 저하와 함께 계산 속도가 빨라집니다.
bge_encoded = bge_flagmodel.encode(texts, ➡️return_sparse=True)</code></pre><p>여기서는 BGE-M3 모델 사용함</p>
<pre><code>lexical_scores1 = bge_flagmodel.compute_lexical_matching_score(
    bge_encoded[&quot;lexical_weights&quot;][0], bge_encoded[&quot;lexical_weights&quot;][0]
)
# 0 &lt;-&gt; 0 자기 자신과의 비교
print(lexical_scores1)</code></pre><p>0.30167388916015625 
= 0번 문서와 0번 문서가 얼마나 <strong>&#39;같은 단어&#39;</strong>를 공유하는지 계산한 값입니다. 당연히 똑같은 단어들로 구성되어 있으니 모델이 판단한 최대 일치 점수가 나옵니다.</p>
<pre><code>lexical_scores2 = bge_flagmodel.compute_lexical_matching_score(
    bge_encoded[&quot;lexical_weights&quot;][0], bge_encoded[&quot;lexical_weights&quot;][1]
)
# 0 &lt;-&gt; 1 다른 문서와의 비교
print(lexical_scores2)</code></pre><p>0 =두 문서 사이에 겹치는 단어가 하나도 없다. Dense 방식은 단어가 달라도 &#39;분위기&#39;가 비슷하면 0.8 같은 점수를 주지만, Sparse 방식은 글자 자체가 일치하지 않으면 가차 없이 0점.</p>
<h3 id="c-colbert">c. ColBERT</h3>
<p>문장 전체를 하나의 점(Vector)으로 찍는 대신, 단어 하나하나의 벡터를 모두 비교합니다. 계산량은 훨씬 많지만, 문맥적 유사도를 찾는 정밀도가 압도적으로 높아 고성능 검색 엔진 구현 시 사용</p>
<h4 id="작동-방식">작동 방식</h4>
<ol>
<li>문서의 각 토큰에 대해 별도의 벡터를 생성합니다. 즉, 하나의 문서는 여러 개의 벡터로 표현됩니다.</li>
<li>쿼리도 마찬가지로 각 토큰에 대해 별도의 벡터를 생성합니다.</li>
<li>검색 시, 쿼리의 각 토큰 벡터와 문서의 모든 토큰 벡터 사이의 유사도를 계산합니다.</li>
<li>이 유사도들을 종합하여 최종 검색 점수를 계산합니다.</li>
</ol>
<pre><code>bge_flagmodel = BGEM3FlagModel(
    &quot;BAAI/bge-m3&quot;, use_fp16=True
)  # use_fp16을 True로 설정하면 약간의 성능 저하와 함께 계산 속도가 빨라집니다.
bge_encoded = bge_flagmodel.encode(texts, ➡️return_colbert_vecs=True)
</code></pre><pre><code>colbert_scores1 = bge_flagmodel.colbert_score(
    bge_encoded[&quot;colbert_vecs&quot;][0], bge_encoded[&quot;colbert_vecs&quot;][0]
)
colbert_scores2 = bge_flagmodel.colbert_score(
    bge_encoded[&quot;colbert_vecs&quot;][0], bge_encoded[&quot;colbert_vecs&quot;][1]
)
# 0 &lt;-&gt; 0
print(colbert_scores1)
# 0 &lt;-&gt; 1
print(colbert_scores2)</code></pre><p>앞서 본 Sparse 방식이 &quot;0점&quot;을 줬던 문장들에 대해, ColBERT는 어떻게 반응하는지 비교하면 , ColBERT는 글자는 다르지만, 단어들의 세부적인 맥락을 보니 의미적 연관성이 있다고 판단해 tensor(0.3748)라고 점수를 부여해줌.</p>
<blockquote>
<p>&quot;ColBERT가 좋은 이유&quot; Sparse 방식은 &#39;아이폰&#39;과 &#39;스마트폰&#39;을 완전히 다른 것으로 보지만, ColBERT는 두 단어를 구성하는 토큰(Token) 벡터들이 서로 유사한 영역에 있음을 감지한다.</p>
</blockquote>
<h2 id="4-upstageembeddings">4. UpstageEmbeddings</h2>
<p>국내 AI 기업인 <strong>Upstage(업스테이지)</strong>의 Solar 모델 실습</p>
<p>업스테이지는 검색의 정확도를 높이기 위해 비대칭 임베딩(Asymmetric Embedding) 전략을 사용하는데 
질문과 답변은 문장 구조가 다르기 때문에 각각의 특성에 맞춰 학습시켜, <strong>질문용 모델과 답변용 모델이 분리</strong> 되어 있다!</p>
<pre><code>from langchain_upstage import UpstageEmbeddings

# 쿼리 전용 임베딩 모델
query_embeddings = UpstageEmbeddings(model=&quot;solar-embedding-1-large-query&quot;)

# 문장 전용 임베딩 모델
passage_embeddings = UpstageEmbeddings(model=&quot;solar-embedding-1-large-passage&quot;)
</code></pre><pre><code># 쿼리 임베딩
embedded_query = query_embeddings.embed_query(&quot;LangChain 에 대해서 상세히 알려주세요.&quot;)
# 임베딩 차원 출력-&gt; 4096
len(embedded_query)
# 문서 임베딩
embedded_documents = passage_embeddings.embed_documents(texts)
</code></pre><p>앞서 배운 BGE-M3나 E5 모델은 1024차원이었지만 업스테이지 모델은 4096차원으로 정보를 4배나 더 세밀하게 쪼개서 저장합니다. 그만큼 문장의 미묘한 뉘앙스를 잡아내는 능력이 탁월하지만, 저장 공간(Vector DB)을 더 많이 차지한다는 트레이드오프가 있습니다.</p>
<p>유사도 결과에서도 한국어와 영어의 교차 검색에서 언어가 달라도 의미가 통하면 높은 점수를 주는 등 더 정교한 의미 검색이 가능함!</p>
<h2 id="5-ollamaembeddings">5. OllamaEmbeddings</h2>
<p>Ollama는 로컬 환경에서 대규모 언어 모델(LLM)을 쉽게 실행할 수 있게 해주는 오픈소스 프로젝트로 개발자들이 AI 모델을 자신의 컴퓨터에서 직접 실험하고 사용할 수 있도록 지원합니다.</p>
<pre><code>from langchain_community.embeddings import OllamaEmbeddings

ollama_embeddings = ➡️OllamaEmbeddings(
    model=&quot;nomic-embed-text&quot;, #768차원짜리 가벼운 모델. 로컬에서 돌려야해서 가장 대중적으로 쓰임.
)</code></pre><p>쿼리와 문서를 인베딩하는 방식은 위 다른 실습과 동일하고 특이한 점은 유사도에서 점수가 높게 나왔는데 그 이유는 다른 모델과는 달리 정규화를 거치지 않은 순수 벡터 값을 그대로 내적했기 때문에, 벡터의 크기만큼 점수가 뻥튀기된 것입니다. 점수의 절댓값은 의미가 없고 상대적인 순위만 확인하면 됩니다.</p>
<pre><code>[Query] LangChain 에 대해서 알려주세요.
====================================
[0] 유사도: 399.644 | LangChain은 초거대 언어모델로 애플리케이션을 구축하는 과정을 단순화합니다.

[1] 유사도: 356.518 | 랭체인 한국어 튜토리얼은 LangChain의 공식 문서, cookbook 및 다양한 실용 예제를 바탕으로 하여 사용자가 LangChain을 더 쉽고 효과적으로 활용할 수 있도록 구성되어 있습니다. 

[2] 유사도: 322.359 | LangChain simplifies the process of building applications with large language models

[3] 유사도: 321.078 | 안녕, 만나서 반가워.

[4] 유사도: 224.858 | Retrieval-Augmented Generation (RAG) is an effective technique for improving AI responses.
</code></pre><h2 id="6-gpt4all-임베딩">6. GPT4ALL 임베딩</h2>
<p>GPT4All은 무료로 사용할 수 있는 로컬 실행 기반의 개인정보 보호를 고려한 챗봇으로
GPU 없이 CPU만으로도 돌아가는 저사양 환경을 위한 가성비 모델입니다.
업스테이지(4096), BGE-M3(1024)에 비해 훨씬 작은 384차원을 사용합니다. (실습에서 확인가능)</p>
<ul>
<li>장점: 숫자가 적으니 계산 속도가 빛의 속도만큼 빠르고, 메모리(RAM)를 거의 차지하지 않습니다.</li>
<li>단점: 정보의 &#39;해상도&#39;는 낮습니다. 복잡하고 긴 전문 지식 검색보다는 간단한 일상 대화나 가벼운 문장 검색에 적합합니다.</li>
</ul>
<table>
<thead>
<tr>
<th align="left">모델명</th>
<th align="left">제공사</th>
<th align="left">차원</th>
<th align="left">주요 특징</th>
<th align="left">추천 용도</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>OpenAI</strong></td>
<td align="left">OpenAI</td>
<td align="left">1536</td>
<td align="left">유료, 가장 표준적인 성능</td>
<td align="left">범용 RAG 서비스 구축</td>
</tr>
<tr>
<td align="left"><strong>BGE-M3</strong></td>
<td align="left">BAAI</td>
<td align="left">1024</td>
<td align="left">오픈소스, 한국어 성능 최강</td>
<td align="left">국내 검색 서비스, 하이브리드 검색</td>
</tr>
<tr>
<td align="left"><strong>Solar</strong></td>
<td align="left">Upstage</td>
<td align="left">4096</td>
<td align="left">유료, 초고해상도, 비대칭 임베딩</td>
<td align="left">정밀도가 중요한 전문 문서 검색</td>
</tr>
<tr>
<td align="left"><strong>GPT4All</strong></td>
<td align="left">Nomic</td>
<td align="left">384</td>
<td align="left">무료, CPU 최적화(저사양 최적)</td>
<td align="left">개인 프로젝트 및 로컬 환경</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린은 왜 자바와 같은 컴파일 언어인데 선언 순서가 중요할까?]]></title>
            <link>https://velog.io/@no-glass-otacku/%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%80-%EC%99%9C-%EC%9E%90%EB%B0%94%EC%99%80-%EA%B0%99%EC%9D%80-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%96%B8%EC%96%B4%EC%9D%B8%EB%8D%B0-%EC%84%A0%EC%96%B8-%EC%88%9C%EC%84%9C%EA%B0%80-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@no-glass-otacku/%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%80-%EC%99%9C-%EC%9E%90%EB%B0%94%EC%99%80-%EA%B0%99%EC%9D%80-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%96%B8%EC%96%B4%EC%9D%B8%EB%8D%B0-%EC%84%A0%EC%96%B8-%EC%88%9C%EC%84%9C%EA%B0%80-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sun, 19 Oct 2025 15:11:10 GMT</pubDate>
            <description><![CDATA[<p>요즘 코틀린을 배우는 중인데 과제를 하던 중 교수님이 적어두신 </p>
<pre><code>//implement here...</code></pre><p>에 class를 선언하니까 참조를 못한다고 떴습니다. 물론 class가 사용처보다 아래에 작성되긴 했지만 저는 교수님이 적으라고 한 곳에 적은 죄 밖에 없었기에 억울했습니다...
게다가 코틀린은 자바와 같은 컴파일 언어인데 선언 순서에 영향을 받을 거라고 생각치도 못했습니다.</p>
<p>저 같은 오개념을 가진 사람도 있을 것이라 생각해 이번 블로그 주제를 선정하게 되었습니다. </p>
<h3 id="기술적-오해">기술적 오해</h3>
<p>코틀린은 자바와 마찬가지로 JVM(Java Virtual Machine)을 타겟으로 하는 컴파일 언어입니다. 그러나 코틀린은 사용하고자 하는 요소가 그 사용처보다 코드 상위(먼저)에 정의되어야만 컴파일이 성공하는 경우가 일반적입니다.</p>
<p>이는 흔히 알려진 &#39;컴파일 언어는 선언 순서에 자유롭다&#39;는 인식과는 다릅니다. 그렇다면 선언 순서의 자유도에 영향을 미치는 근본적인 요인은 무엇일까요?</p>
<h3 id="선언-순서의-결정-요인-컴파일러-처리-방식의-차이">선언 순서의 결정 요인: 컴파일러 처리 방식의 차이</h3>
<p>선언 순서의 자유도를 결정하는 핵심 요인은 언어의 종류(컴파일 vs 인터프리트)가 아닌, <strong>컴파일러가 소스 코드를 처리하는 방식, 즉 &#39;통과(Pass)&#39; 횟수</strong>에 있습니다.</p>
<ul>
<li><p>$\text{다중 통과(Multi-Pass)}$ 방식: 코드를 여러 번 스캔하여, <strong>첫 번째 통과에서 모든 심볼(클래스, 함수 등)의 존재와 위치를 파악</strong>(전방 참조 허용)하고, 이후 통과에서 실제 검증 및 코드를 생성합니다. -&gt; 즉, <strong>선언 순서가 비교적 자유롭습니다.</strong> </p>
</li>
<li><p>$\text{단일 통과(Single-Pass)}$ 방식: 코드를 위에서 아래로 한 번에 처리하며, 요소를 사용할 시점에는 반드시 그 정의가 이미 컴파일러에게 알려져 있어야 합니다. -&gt; 즉, ** 선언이 사용처보다 앞에 되어있어야만 합니다.**</p>
</li>
</ul>
<h3 id="코틀린kotlin-단일-통과-경향과-언어-설계의-합리성">코틀린(Kotlin): 단일 통과 경향과 언어 설계의 합리성</h3>
<p>코틀린 컴파일러는 단일 통과에 가까운 효율적인 처리를 지향하며, 이는 언어 설계 철학과 긴밀하게 연결되어 있습니다.
다른 근본 언어를 배우고 코틀린을 배우기 시작하면 코틀린이 얼마나 컴팩트함과 직관적임을 유지하고자 했는지 참... 느껴집니다 (개인적인 의견)</p>
<p>코틀린은 다중 스캔의 복잡성을 줄여 컴파일러 구조를 단순화하고 처리 속도를 높이고자 하였고 (근데 앱 개발할때는 애뮬레이터 때문에 느려터짐) 스크립트와 유사한 순차적 실행 흐름 갖도록  선언도 이 흐름에 맞춰 사용 전 정의를 규칙으로 확립함으로써 코드의 <strong>지역성(Locality)</strong>과 가독성을 극대화합니다.</p>
<p>코틀린은 JVM 기반 컴파일 언어의 이점을 유지하면서도, 현대적인 언어의 요구사항(간결성, 명확성)을 위해 명시적인 선언 순서를 채택한 것입니다.</p>
<h3 id="결론">결론</h3>
<p>결론적으로, <strong>선언 순서의 자유도</strong>는 언어의 종류가 아닌 <strong>컴파일러의 내부 설계 방식</strong>에 의해 결정됩니다. 자바는 다중 통과로 유연성을 확보했고, 코틀린은 단일 통과 경향과 가독성 철학으로 명확성을 선택한 것입니다.</p>
<p>실무에서 코틀린을 사용할 때는 이 원리를 이해하고, 클래스나 함수를 사용하기 전에 정의하는 Top-Down 방식의 코딩 습관을 유지합시다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 웹 개발에서의 리액트 서버 컴포넌트(RSC), 프론트 개발자의 풀스택 길을 열다]]></title>
            <link>https://velog.io/@no-glass-otacku/%EB%AA%A8%EB%8D%98-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%97%90%EC%84%9C%EC%9D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8RSC-%ED%94%84%EB%A1%A0%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%92%80%EC%8A%A4%ED%83%9D-%EA%B8%B8%EC%9D%84-%EC%97%B4%EB%8B%A4</link>
            <guid>https://velog.io/@no-glass-otacku/%EB%AA%A8%EB%8D%98-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%97%90%EC%84%9C%EC%9D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8RSC-%ED%94%84%EB%A1%A0%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%92%80%EC%8A%A4%ED%83%9D-%EA%B8%B8%EC%9D%84-%EC%97%B4%EB%8B%A4</guid>
            <pubDate>Sun, 12 Oct 2025 17:45:54 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-모놀리스를-넘어-모던-웹-개발에서의-리액트-서버-컴포넌트rsc가-가져온-풀스택-개발의-역설">📜 모놀리스를 넘어: 모던 웹 개발에서의 리액트 서버 컴포넌트(RSC)가 가져온 풀스택 개발의 역설</h2>
<p>요즘 코딩 판에서 <strong>React Server Components (RSC)</strong> 얘기가 핫하다고 하는데 저도 잘 모르지만 주제가 재밌어서 알아봐 왔습니다~.
Next.js가 밀고 있는 이 기술은 단순한 기능 하나 툭 던져준 게 아니라, 우리 웹 개발판 자체를 뒤집어엎고 있다는데요.</p>
<p>RSC가 왜 등장했고, 이게 왜 <strong>&#39;풀스택 개발의 역설&#39;</strong>이라고 불리는지, 흥미롭게 파헤쳐 볼게요. 😉</p>
<hr>
<h2 id="1-배경-지식-csr과-그-시절-모놀리스를-소환합니다">1. 배경 지식: CSR과 &#39;그 시절 모놀리스&#39;를 소환합니다</h2>
<p>RSC를 이해하려면, 우리가 <strong>&#39;왜 이렇게까지 왔는가&#39;</strong>를 봐야죠. 지금 우리를 괴롭혔던 두 가지 개념부터 쿨하게 정리해봅시다.</p>
<h3 id="1-1-csr-client-side-rendering-사용자-컴퓨터-뺑이-치는-방식">1-1. CSR (Client Side Rendering): &quot;사용자 컴퓨터 뺑이 치는 방식&quot;</h3>
<p>CSR은 한때 SPA(Single Page Application)의 혁신이었지만, 요즘 들어 욕을 바가지로 먹는 방식이죠.</p>
<ul>
<li><strong>작동 방식:</strong> 서버는 텅 빈 HTML 파일 하나 던져주고, <strong>수많은 JavaScript 파일 뭉치(번들)</strong>를 사용자 브라우저에 배달합니다. 브라우저는 이 JS 뭉치를 전부 다운로드해서 실행하고, 그제야 화면을 완성하죠.</li>
<li><strong>문제점:</strong> JS 번들이 커질수록 <strong>초기 로딩이 답답해집니다.</strong> 사용자는 화면이 하얗거나 먹통인 상태(TTI 지연)를 하염없이 기다려야 해요. &quot;아니, 왜 로딩만 하고 아무것도 안 돼?&quot; 소리가 절로 나오죠. 🤬</li>
</ul>
<h3 id="1-2-모놀리스monolith-옛날-맛집의-만능-간장처럼">1-2. 모놀리스(Monolith): &quot;옛날 맛집의 만능 간장처럼&quot;</h3>
<p>모놀리스는 요즘 마이크로 서비스 아키텍처(MSA)에게 밀렸지만, 한때는 웹 서비스의 국룰이었어요.</p>
<ul>
<li><strong>정의:</strong> 서비스의 <strong>모든 기능</strong> (프론트엔드, 백엔드 로직, 데이터 처리, 인증 등)이 <strong>하나의 거대한 코드베이스와 하나의 애플리케이션</strong>에 몽땅 묶여 작동하는 방식입니다. 마치 김치찌개부터 삼겹살까지 다 파는 <strong>&#39;옛날 만능 식당&#39;</strong> 같아요.</li>
<li><strong>유명한 예시:</strong> 초창기 버전의 <strong>페이스북(Facebook)</strong>이나 <strong>이베이(eBay)</strong> 같은 서비스들이 이 모놀리스 구조에서 시작했어요. 물론 서비스가 커지면서 지금은 쪼개고 쪼개서 MSA로 갔지만, 처음에는 하나의 거대한 몸뚱이였죠.</li>
<li><strong>문제점:</strong> 코드가 엉키면 머리도 엉킵니다. 작은 기능 하나 수정해도 전체를 다시 배포해야 해서 개발 속도가 <strong>&#39;느림보 거북이&#39;</strong>가 됩니다. 그리고 한 군데 문제 터지면, <strong>줄줄이 소시지처럼</strong> 전체 서비스가 터질 위험이 있죠. 😱</li>
</ul>
<hr>
<h2 id="2-rsc의-등장과-기술적-떡상-포인트">2. RSC의 등장과 기술적 떡상 포인트</h2>
<p>RSC는 CSR의 <strong>느린 초기 로딩</strong>과 모놀리스의 <strong>배포/확장성 문제</strong> 사이에서 절묘한 균형점을 찾으려고 나타났습니다.</p>
<h3 id="💡-rsc가-서버에서-처리하는-핵심-미션">💡 RSC가 서버에서 처리하는 &#39;핵심 미션&#39;</h3>
<p>RSC는 &quot;데이터 가져오기(Data Fetching)랑 화면 그리기(Rendering)는 서버에서 해치우자&quot;는 마인드입니다.</p>
<ol>
<li><strong>데이터 패칭 (Data Fetching) 혁신:</strong> <strong>Server Components</strong>는 DB에 <strong>직접 접근</strong>해서 데이터를 땡겨옵니다. 클라이언트에서 &quot;야 서버! 데이터 줘!&quot;라고 또 API 호출할 필요가 없어져요.</li>
<li><strong>JS 번들 다이어트:</strong> 서버에서 렌더링된 컴포넌트는 클라이언트로 <strong>JS 코드가 아닌</strong> 특수 포맷으로 전송됩니다. 상호작용이 없는 컴포넌트의 JS 코드는 클라이언트 브라우저로 아예 안 넘어가죠. 결과적으로 <strong>다운로드할 JS 양이 확 줄어들어</strong> 로딩 속도가 🚀 로켓처럼 빨라집니다.</li>
</ol>
<h3 id="📊-rsc-vs-csr-vs-ssr-뭐가-다른데">📊 RSC vs. CSR vs. SSR, 뭐가 다른데?</h3>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">CSR (Client Side Rendering)</th>
<th align="left">RSC (React Server Components)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>렌더링 주체</strong></td>
<td align="left"><strong>클라이언트</strong> (사용자 브라우저)</td>
<td align="left"><strong>서버</strong> (초기 콘텐츠 및 데이터)</td>
</tr>
<tr>
<td align="left"><strong>JS 전송량</strong></td>
<td align="left">무조건 <strong>전부 다</strong> 보냄 (느림)</td>
<td align="left">상호작용 컴포넌트만 <strong>선별적</strong>으로 보냄 (빠름)</td>
</tr>
<tr>
<td align="left"><strong>데이터 접근</strong></td>
<td align="left">클라이언트에서 API <strong>별도 호출</strong></td>
<td align="left">서버 컴포넌트에서 DB에 <strong>직접 접근</strong></td>
</tr>
<tr>
<td align="left"><strong>장점</strong></td>
<td align="left">상호작용이 자유로움.</td>
<td align="left">초기 로딩 속도, 번들 크기, 데이터 접근 효율 <strong>최고</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="3-rsc가-가져온-풀스택-개발의-역설-🤯">3. RSC가 가져온 &#39;풀스택 개발&#39;의 역설 🤯</h2>
<p>RSC의 가장 재밌는 점은 개발자의 역할이 뒤섞인다는 거예요. RSC를 도입하면...</p>
<ol>
<li><strong>프론트엔드 개발자가 서버의 왕좌에 오르다:</strong> 프론트엔드 개발자가 Server Component에서 DB 접근 로직을 직접 짜게 됩니다. 이게 무슨 말이냐? <strong>프론트 개발자가 곧 서버 개발자가 되는 풀스택 경계 붕괴</strong>가 일어난 거죠.</li>
<li><strong>API 서버의 정체성 혼란:</strong> 백엔드 팀이 힘들게 만들어 놓은 <strong>API 서버가 꿔다 놓은 보릿자루</strong>가 될 위기에 처합니다. 백엔드는 이제 &quot;순수한 비즈니스 로직(핵심 기능)&quot;과 &quot;인프라 관리&quot;라는 <strong>찐 업무</strong>에만 집중해야 합니다.</li>
</ol>
<h3 id="🤔-옛날-모놀리스로-회귀하는-거-아니야-no">🤔 &#39;옛날 모놀리스&#39;로 회귀하는 거 아니야? (No!)</h3>
<p>&quot;어? 결국 하나로 합치는 거면 옛날 페이스북 모놀리스처럼 되는 거 아냐?&quot;라고 생각할 수 있지만, 절대 아닙니다.</p>
<table>
<thead>
<tr>
<th align="left">특징</th>
<th align="left">과거 모놀리스 (옛날 방식)</th>
<th align="left">RSC 기반 풀스택 (현재 방식)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>코드 구조</strong></td>
<td align="left">모든 기능이 엉킨 <strong>&#39;대환장 파티&#39;</strong></td>
<td align="left">컴포넌트별로 서버/클라이언트 역할이 <strong>명확히 분리</strong></td>
</tr>
<tr>
<td align="left"><strong>기술 스택</strong></td>
<td align="left">백엔드(Java 등) $\neq$ 프론트엔드(JS)</td>
<td align="left">JS/TS <strong>단일 언어</strong>로 통일 (개발 효율 굿)</td>
</tr>
<tr>
<td align="left"><strong>배포 방식</strong></td>
<td align="left">전체 서버를 다시 올려야 함 (무거움)</td>
<td align="left">서버리스(Edge Function) 형태로 <strong>빠르게 분산 배포</strong> (가벼움)</td>
</tr>
</tbody></table>
<p>RSC는 모놀리스의 <strong>&#39;하나로 합쳐진 편의성&#39;</strong>은 가져오되, 단점인 <strong>&#39;유지보수 지옥&#39;</strong>은 버린 <strong>&#39;신개념 하이브리드&#39;</strong>라고 이해하면 됩니다. 즉, <strong>풀스택 코딩의 편리함</strong>과 <strong>분산 아키텍처의 확장성</strong>을 동시에 잡은 치트키인 셈이죠.</p>
<hr>
<h2 id="4-이제-우리는-무엇을-준비해야-할까-찐-개발자의-숙제">4. 이제 우리는 무엇을 준비해야 할까? (찐 개발자의 숙제)</h2>
<p>RSC는 우리에게 설렘과 동시에 숙제를 던져줍니다.</p>
<h3 id="1-나만-아는-코드-금지-협업-규칙-재정립">1. <strong>&#39;나만 아는 코드&#39; 금지! 협업 규칙 재정립</strong></h3>
<p>프론트엔드 개발자가 DB 코드를 짜면, 백엔드 개발자와 <strong>코드 리뷰 및 보안 기준</strong>을 어떻게 맞출지가 핵심이에요. &quot;이건 프론트 영역인데 건들지 마!&quot; 하던 시대는 갔습니다.</p>
<h3 id="2-보안-지식-선택이-아닌-필수">2. <strong>보안 지식, 선택이 아닌 필수</strong></h3>
<p>Server Component는 서버에서 작동합니다. 즉, 데이터베이스 접근 권한, 민감 정보 노출 방지 등 <strong>백엔드 레벨의 보안 지식</strong>이 프론트엔드 개발자에게도 <strong>필수 소양</strong>이 됩니다.</p>
<h3 id="3-경계-긋는-능력--고수">3. <strong>경계 긋는 능력 = 고수</strong></h3>
<p>모든 컴포넌트를 RSC로 만들면 오히려 복잡해질 수 있어요. &quot;여기까지는 서버 컴포넌트로 성능 잡고, 여기서부터는 클라이언트 컴포넌트로 사용자 상호작용 넣자!&quot;라고 <strong>명확하게 경계를 그릴 줄 아는 능력</strong>이 진정한 고수의 덕목이 될 거예요.</p>
<p>RSC는 단순히 React의 유행이 아니라, 웹 개발의 다음 챕터를 열고 있습니다. 이 변화를 놓치지 않고 잘 탑승해서, 우리 모두 &#39;갓생 개발자&#39;로 레벨업 합시다! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[== vs equals(), length vs length(), 여러 변수 한 줄에 선언]]></title>
            <link>https://velog.io/@no-glass-otacku/vs-equals-length-vs-length-%EC%97%AC%EB%9F%AC-%EB%B3%80%EC%88%98-%ED%95%9C-%EC%A4%84%EC%97%90-%EC%84%A0%EC%96%B8</link>
            <guid>https://velog.io/@no-glass-otacku/vs-equals-length-vs-length-%EC%97%AC%EB%9F%AC-%EB%B3%80%EC%88%98-%ED%95%9C-%EC%A4%84%EC%97%90-%EC%84%A0%EC%96%B8</guid>
            <pubDate>Wed, 24 Sep 2025 16:55:12 GMT</pubDate>
            <description><![CDATA[<h3 id="📜-오늘-배운-자바-핵심-문법--vs-equals-length-vs-length-여러-변수-한-줄에-선언">📜 오늘 배운 자바 핵심 문법: <code>==</code> vs <code>equals()</code>, <code>length</code> vs <code>length()</code>, <code>여러 변수 한 줄에 선언</code></h3>
<hr>
<h3 id="📌--와-equals의-차이">📌 <code>==</code> 와 <code>.equals()</code>의 차이</h3>
<p>자바에서 두 값을 비교할 때 사용하는 두 가지 방법입니다. 어떤 자료형을 비교하느냐에 따라 적절한 방법을 사용해야 해요.</p>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left"><code>==</code> 연산자</th>
<th align="left"><code>.equals()</code> 메서드</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>주요 사용</strong></td>
<td align="left"><strong>기본 자료형</strong> (Primitive Types)<br><code>int</code>, <code>char</code>, <code>boolean</code> 등</td>
<td align="left"><strong>참조 자료형</strong> (Reference Types)<br><code>String</code>, <code>Integer</code>, <code>Double</code> 등</td>
</tr>
<tr>
<td align="left"><strong>비교 대상</strong></td>
<td align="left">변수에 저장된 <strong>실제 값</strong></td>
<td align="left">객체가 가리키는 <strong>데이터 내용</strong></td>
</tr>
<tr>
<td align="left"><strong>주의 사항</strong></td>
<td align="left">참조 자료형에 사용 시, **참조 주소(메모리 위치)**를 비교해요. 내용이 같아도 주소가 다르면 <code>false</code>입니다.</td>
<td align="left">기본 자료형에는 사용할 수 없어요.</td>
</tr>
</tbody></table>
<hr>
<h3 id="📌-length-와-length의-차이">📌 <code>length</code> 와 <code>length()</code>의 차이</h3>
<p>💡 핵심 원리: &#39;종류&#39;를 구별하기
프로그래밍 문법은 결국 &#39;무엇&#39;의 길이를 재는가에 따라 달라져요.
이때 . 뒤에 ()가 붙는지 여부는 그 &#39;무엇&#39;이 <strong>데이터(속성)</strong>인지 <strong>기능(메서드)</strong>인지를 나타내는 중요한 힌트가 됩니다.</p>
<h4 id="length-괄호-없음"><strong><code>length</code> (괄호 없음)</strong></h4>
<p>📝 이건 <strong>데이터(속성)</strong>예요. 배열처럼 고정된 크기를 가진 객체의 고유한 정보를 의미합니다. 배열을 만들 때 그 길이가 정해지기 때문에, 그냥 그 값을 읽어오기만 하면 되죠. 따라서 굳이 어떤 동작을 실행할 필요가 없어 괄호가 붙지 않습니다.</p>
<ul>
<li><strong>예시:</strong> <code>String[] arr = {&quot;A&quot;, &quot;B&quot;, &quot;C&quot;};</code><ul>
<li>👉<code>arr.length</code>의 결과는 <strong>3</strong> 입니다.</li>
</ul>
</li>
</ul>
<h4 id="length-괄호-있음"><strong><code>length()</code> (괄호 있음)</strong></h4>
<p>💻 이건 <strong>기능(메서드)</strong>이에요. 문자열처럼 가변적인 데이터를 다룰 때, 길이를 계산하거나 확인하는 &#39;동작&#39;을 수행하는 것이죠. &quot;야, 지금 이 문자열의 길이가 몇이야?&quot;라고 물어보는 명령과 같아서 괄호 ()가 필요합니다.</p>
<ul>
<li><strong>예시:</strong> <code>String str = &quot;Hello&quot;;</code>
  <em>👉 <code>str.length()</code>의 결과는 *</em>5** 입니다. 문자열의 길이를 세는 &#39;행위&#39;를 수행하는 거죠.</li>
</ul>
<h4 id="🤔-쉽게-기억하는-방법">🤔 쉽게 기억하는 방법</h4>
<p>이 두 가지를 구별하기 어렵다면, 다음과 같은 간단한 질문을 스스로에게 해보세요.</p>
<p>&quot;이것은 이미 정해진 상수(값)인가?&quot; 👉 그렇다면 length (괄호 없음)
&quot;이것은 어떤 동작을 통해 알아내야 하는가?&quot; 👉 그렇다면 length() (괄호 있음)</p>
<hr>
<h3 id="📌-변수-초기화-여러-변수-한-줄에-선언하기">📌 변수 초기화: 여러 변수 한 줄에 선언하기</h3>
<p>자바에서 여러 변수를 한 줄에 선언할 수 있지만, 초기화에는 주의해야 합니다.</p>
<ul>
<li><p><strong>잘못된 초기화:</strong>
<code>int a, b, c, d = 0;</code></p>
<ul>
<li>이 코드는 <strong><code>d</code>만 <code>0</code>으로 초기화</strong>하고, <code>a</code>, <code>b</code>, <code>c</code>는 초기화되지 않은 상태가 됩니다. 이 변수들을 사용하면 컴파일 오류가 발생해요.</li>
</ul>
</li>
<li><p><strong>올바른 초기화:</strong>
<code>int a = 0, b = 0, c = 0, d = 0;</code></p>
<ul>
<li>이렇게 <strong>각 변수에 값을 명시적으로 할당</strong>해야 모든 변수가 올바르게 초기화됩니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[StringBuilder와 문자열 조작]]></title>
            <link>https://velog.io/@no-glass-otacku/StringBuilder%EC%99%80-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%A1%B0%EC%9E%91</link>
            <guid>https://velog.io/@no-glass-otacku/StringBuilder%EC%99%80-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%A1%B0%EC%9E%91</guid>
            <pubDate>Sat, 20 Sep 2025 13:39:13 GMT</pubDate>
            <description><![CDATA[<h3 id="📜-코딩-테스트를-위한-문자열-완벽-공략">📜 코딩 테스트를 위한 문자열 완벽 공략</h3>
<p>코딩 테스트에서 문자열을 효율적으로 다루는 방법은 시간 초과를 피하는 핵심 전략입니다. <code>String</code>, <code>StringBuilder</code>, 그리고 문자열과 배열의 관계를 제대로 이해하면 성능을 크게 향상할 수 있습니다.</p>
<hr>
<h3 id="1-string과-stringbuilder의-차이점">1. <code>String</code>과 <code>StringBuilder</code>의 차이점</h3>
<h4 id="string-불변immutable-객체-🧊"><strong><code>String</code>: 불변(Immutable) 객체</strong> 🧊</h4>
<p><code>String</code>은 한 번 생성되면 그 내용을 변경할 수 없습니다. 문자열을 수정하거나 이어붙일 때마다 <strong>새로운 <code>String</code> 객체가 생성</strong>됩니다. 이 때문에 반복문에서 <code>+</code> 연산을 사용하면 수많은 객체가 만들어져 메모리 낭비와 함께 성능이 급격히 저하됩니다. 이는 코딩 테스트에서 **시간 초과(Time Limit Exceeded)**의 주요 원인이 됩니다.</p>
<h4 id="stringbuilder-가변mutable-객체-✨"><strong><code>StringBuilder</code>: 가변(Mutable) 객체</strong> ✨</h4>
<p><code>StringBuilder</code>는 기존 객체의 메모리 공간을 재활용하며 문자열을 추가, 수정, 삭제할 수 있습니다. 문자열을 자주 조작하는 코딩 테스트 환경에 매우 적합하며, 반복적인 문자열 작업에 필수적입니다.</p>
<p><strong>✅ 비효율적인 <code>String</code> vs 효율적인 <code>StringBuilder</code></strong></p>
<pre><code class="language-java">// 비효율적인 String 사용
String str = &quot;&quot;;
for (int i = 0; i &lt; 1000; i++) {
    str += &quot;a&quot;; // 매 반복마다 새로운 String 객체 생성
}

// 효율적인 StringBuilder 사용
StringBuilder sb = new StringBuilder();
for (int i = 0; i &lt; 1000; i++) {
    sb.append(&quot;a&quot;); // 기존 객체에 계속 추가
}</code></pre>
<hr>
<h3 id="2-stringbuilder-주요-메서드">2. <code>StringBuilder</code> 주요 메서드</h3>
<p><code>StringBuilder</code> 객체를 다룰 때 자주 사용하는 메서드들입니다.</p>
<table>
<thead>
<tr>
<th align="left">메서드</th>
<th align="left">설명</th>
<th align="left">예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>append()</code></td>
<td align="left">다양한 타입의 데이터를 문자열로 변환하지 않고 바로 추가합니다.</td>
<td align="left"><code>sb.append(123);</code> <br> <code>sb.append(&quot;World&quot;);</code></td>
</tr>
<tr>
<td align="left"><code>length()</code></td>
<td align="left"><code>StringBuilder</code>에 담긴 문자열의 길이를 반환합니다.</td>
<td align="left"><code>if (sb.length() == 0) { ... }</code></td>
</tr>
<tr>
<td align="left"><code>charAt()</code></td>
<td align="left">특정 인덱스의 문자를 반환합니다.</td>
<td align="left"><code>if (sb.charAt(0) == &#39;0&#39;) { ... }</code></td>
</tr>
<tr>
<td align="left"><code>toString()</code></td>
<td align="left"><code>StringBuilder</code> 객체를 <code>String</code> 타입으로 변환합니다. 최종 결과를 반환할 때 사용합니다.</td>
<td align="left"><code>String result = sb.toString();</code></td>
</tr>
</tbody></table>
<hr>
<h3 id="3-stringrepeat-java-11-이상">3. <strong><code>String.repeat()</code> (Java 11 이상)</strong></h3>
<p>vs <code>StringBuilder.append()</code></p>
<p>자바 11 이상에서 제공되는 메서드로, 특정 문자열을 지정된 횟수만큼 반복할 수 있어 코드를 간결하게 만들어줍니다. 
<code>repeat()</code>는 내부적으로 <code>StringBuilder</code>를 사용해요. 예를 들어, <code>str.repeat(5)</code>를 호출하면 자바는 내부적으로 <code>StringBuilder</code>를 생성하고, <code>append()</code>를 5번 호출한 뒤, 최종적으로 <code>toString()</code>을 호출해 새로운 문자열을 만듭니다.</p>
<pre><code class="language-java">// 예시: 5번 반복하여 &quot;abcabcabcabcabc&quot; 생성
String repeatedString = &quot;abc&quot;.repeat(5);</code></pre>
<p><code>repeat()</code>는 내부적으로 <code>StringBuilder</code>를 사용해 문자열을 이어붙이는 것과 비슷하게 동작하므로, 성능 면에서도 효율적입니다.</p>
<hr>
<h3 id="4-문자열과-배열의-효율적인-활용">4. 문자열과 배열의 효율적인 활용</h3>
<h4 id="charat-vs-tochararray-성능과-메모리의-차이"><code>charAt()</code> vs <code>toCharArray()</code>: 성능과 메모리의 차이</h4>
<p>문자열의 각 문자에 접근하는 두 가지 방법에는 성능과 메모리 사용량에 큰 차이가 있습니다. 코딩 테스트에서 <code>O(N)</code>과 <code>O(1)</code>의 차이는 결과에 큰 영향을 미칩니다.</p>
<table>
<thead>
<tr>
<th align="left">특징</th>
<th align="left"><code>charAt(j)🥇</code></th>
<th align="left"><code>toCharArray()</code></th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>작동 방식</strong></td>
<td align="left">문자열에서 <code>j</code> 인덱스의 문자를 <strong>직접 반환</strong>합니다.</td>
<td align="left">문자열의 모든 문자를 <strong>새로운 <code>char</code> 배열로 복사</strong>합니다.</td>
</tr>
<tr>
<td align="left"><strong>메모리 사용</strong></td>
<td align="left">추가 메모리 할당이 거의 없어 <strong><code>O(1)</code></strong></td>
<td align="left">문자열 길이만큼의 메모리를 추가로 사용해 <strong><code>O(N)</code></strong></td>
</tr>
<tr>
<td align="left"><strong>성능</strong></td>
<td align="left">필요한 문자 하나만 접근하므로 <strong>매우 빠릅니다.</strong></td>
<td align="left">배열 전체를 생성해야 하므로 <strong>상대적으로 느립니다.</strong></td>
</tr>
</tbody></table>
<p><strong>✅ <code>toCharArray()</code> 사용 시 주의점</strong>
반복문 안에서 <code>toCharArray()</code>를 사용하면 매번 새로운 배열이 생성되어 메모리 낭비를 초래합니다.</p>
<pre><code class="language-java">// 비효율적인 toCharArray() 사용
for (int i = 0; i &lt; wallpaper.length; i++) {
    char[] wallpaperJ = wallpaper[i].toCharArray(); // ⚠️ 매 반복마다 배열 생성
    for (int j = 0; j &lt; wallpaper[i].length(); j++) {
        if (wallpaperJ[j] == &#39;#&#39;) {
            // ...
        }
    }
}</code></pre>
<p><strong><code>charAt()</code>를 사용하면</strong> 새로운 배열을 만들 필요 없이 원래 문자열에서 바로 문자에 접근하므로 <strong>메모리 효율성</strong>과 <strong>시간 효율성</strong> 모두 높아집니다.</p>
<hr>
<h3 id="5-기타-유용한-문자열-기술">5. 기타 유용한 문자열 기술</h3>
<h4 id="문자-→-정수-변환"><strong>문자 → 정수 변환</strong></h4>
<p>문자 &#39;0&#39;~&#39;9&#39;를 실제 정수 0~9로 변환하는 기술입니다. 배열의 인덱스로 활용할 때 매우 유용합니다.</p>
<pre><code class="language-java">// 예시: 문자 &#39;5&#39;를 정수 5로 변환하여 배열의 5번 인덱스를 증가
int[] count = new int[10];
char num = &#39;5&#39;;
count[num - &#39;0&#39;]++;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[DM 과제: 전체 데이터셋 관계 트리]]></title>
            <link>https://velog.io/@no-glass-otacku/DM-%EA%B3%BC%EC%A0%9C-%EC%A0%84%EC%B2%B4-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%85%8B-%EA%B4%80%EA%B3%84-%ED%8A%B8%EB%A6%AC</link>
            <guid>https://velog.io/@no-glass-otacku/DM-%EA%B3%BC%EC%A0%9C-%EC%A0%84%EC%B2%B4-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%85%8B-%EA%B4%80%EA%B3%84-%ED%8A%B8%EB%A6%AC</guid>
            <pubDate>Wed, 16 Apr 2025 13:52:30 GMT</pubDate>
            <description><![CDATA[<p>X (원본 설명변수)            y (원본 타깃: SalePrice)
│                           │
├──→ X1                    ├──→ y1
│     └─ 결측치 있는 열 제거      └─ 이상치(y 기준) 제거된 행만 유지
│     └─ y1과 인덱스 일치
│
├──→ X2 (VIF ≥ 10 변수 제거: 다중공선성 ↓)
│     └─ X1의 열 일부만 선택 (중복 제거)
│
├──→ X3                    └──→ log_y
│     └─ X2에서 log_y 기준 이상치 제거     └─ log_y = log(SalePrice + 1)
│<br>│                            └──→ log_y2
│                                   └─ X2와 인덱스 일치
│
│                            └──→ log_y3
│                                   └─ log_y2에서 이상치 제거
│
X3 &lt;───────────────&gt; log_y3  ← 🔥 최종 회귀 학습 데이터셋
<img src="https://velog.velcdn.com/images/no-glass-otacku/post/1ba9ca96-391b-414e-8b0f-6c62546bfcaf/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker test 시행착오]]></title>
            <link>https://velog.io/@no-glass-otacku/Docker-test-%EC%8B%9C%ED%96%89%EC%B0%A9%EC%98%A4</link>
            <guid>https://velog.io/@no-glass-otacku/Docker-test-%EC%8B%9C%ED%96%89%EC%B0%A9%EC%98%A4</guid>
            <pubDate>Mon, 14 Apr 2025 12:04:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>pintos -v -- -q run &#39;priority-change&#39; 
test를 하기위해 사용하게 되는 명령어
docker안에서 명령어 입력! 따옴표 안에 실행하고픈 테스트 이름을 넣으면 됨.
여기 있는 pintos라는 명령어를 사용하기 위해서는 아래 세팅을 해야함.</p>
</blockquote>
<p>⚙️ 매번 pintos라고만 치고 실행하고 싶다면: 환경 변수 등록
<del>find /pintos -name pintos
이거 하면 pintos 스크립트 위치를 알려주는데 
/pintos/utils/pintos 랑 /pintos/src/utils/pintos에 있음</del></p>
<p>export PATH=$PATH:/pintos/utils </p>
<p>🧠 이걸 영구 적용하려면:
echo &#39;export PATH=$PATH:/pintos/utils&#39; &gt;&gt; ~/.bashrc
source ~/.bashrc
당연히 얘네도 docker안에 입력해야함</p>
<p><img src="https://velog.velcdn.com/images/no-glass-otacku/post/43245868-46d4-4fe7-83c3-edaa7d12bc98/image.png" alt=""></p>
<p>📌 문제 원인
지금 너가 사용하는 PintOS 구조는 pintos-kaist 버전이 아닌,
공식 PintOS Reference (Stanford/CS140) 기본 버전 또는
Makefile에 테스트 자동화를 포함하지 않은 최소 버전이야.</p>
<p>즉, 이 구조에선 make check도 없고</p>
<p>TESTS=...도 지원되지 않으며</p>
<p>테스트 실행은 직접 명령어로 pintos 명령을 호출해서 수동으로 해야 함</p>
<h2 id="🛠️-make-명령어-비교-pintos-기준">🛠️ <code>make</code> 명령어 비교 (Pintos 기준)</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>목적</th>
<th>실행 위치</th>
<th>공통점</th>
<th>차이점</th>
</tr>
</thead>
<tbody><tr>
<td><code>make clean</code></td>
<td>빌드된 파일(.o, .d, kernel.bin 등) 제거</td>
<td><code>/src</code>, <code>/src/threads</code> 등</td>
<td>모두 Makefile에 정의된 규칙을 사용함</td>
<td>빌드된 결과물만 삭제하고 실행은 하지 않음</td>
</tr>
<tr>
<td><code>make</code></td>
<td>현재 디렉토리의 Pintos 커널 빌드</td>
<td>보통 <code>/src/threads</code></td>
<td>Makefile로 빌드 대상 파일을 자동 생성</td>
<td>커널만 컴파일하고 테스트는 실행하지 않음</td>
</tr>
<tr>
<td><code>make check</code></td>
<td>전체 테스트 빌드 + 실행</td>
<td><code>/src</code> (프로젝트 루트)</td>
<td>테스트를 위해 내부적으로 <code>make</code> 먼저 수행</td>
<td>모든 디렉토리의 테스트를 다 실행하며 시간이 오래 걸림</td>
</tr>
</tbody></table>
<h3 id="✅-추천-사용-흐름-단계별">✅ 추천 사용 흐름 (단계별)</h3>
<ol>
<li><code>make clean</code> – 이전 결과 삭제 (에러 방지용)</li>
<li><code>make</code> – 내가 수정한 코드가 잘 컴파일되는지 확인</li>
<li><code>make check</code> – 전체 테스트를 실행해서 결과 확인</li>
</ol>
<hr>
<p>좋아, 이 오류 메시지:</p>
<pre><code class="language-bash">make: *** No rule to make target &#39;tests/threads/priority-change.result&#39;.  Stop.</code></pre>
<p>는 <strong><code>make</code>가 테스트 실행 규칙을 찾지 못했다</strong>는 뜻인데,<br>대부분 다음 중 하나야:</p>
<hr>
<h2 id="✅-1-잘못된-디렉토리에서-테스트-실행-시도">✅ 1. 잘못된 디렉토리에서 테스트 실행 시도</h2>
<p>너가 위치한 곳이 <code>pintos/threads</code>인데,<br>Pintos 공식 구조는 <strong><code>pintos/src/threads</code></strong> 구조여야 해.</p>
<h3 id="🔧-해결">🔧 해결:</h3>
<pre><code class="language-bash">cd /pintos/src/threads</code></pre>
<p><code>src</code> 폴더 안에 들어가서 <code>Makefile</code>이 있는지 확인해줘.</p>
<hr>
<h2 id="✅-2-처음-실행-시엔-make로-전체-빌드를-먼저-해줘야-할-수-있어">✅ 2. 처음 실행 시엔 <code>make</code>로 전체 빌드를 먼저 해줘야 할 수 있어</h2>
<p>| 🔁 처음 빌드 했음? | <code>make</code> 또는 <code>make all</code> 했는지 |</p>
<pre><code class="language-bash">cd /pintos/src/threads
make clean
make</code></pre>
<p>이후 다시 아래 명령어 시도:</p>
<pre><code class="language-bash">make tests/threads/priority-change.result</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[PintOS comment rule]]></title>
            <link>https://velog.io/@no-glass-otacku/PintOS-comment-rule</link>
            <guid>https://velog.io/@no-glass-otacku/PintOS-comment-rule</guid>
            <pubDate>Sat, 12 Apr 2025 18:24:22 GMT</pubDate>
            <description><![CDATA[<h1 id="📝-pintos-협업을-위한-주석-작성-가이드">📝 PintOS 협업을 위한 주석 작성 가이드</h1>
<hr>
<h2 id="✅-주석-작성-규칙-요약">✅ 주석 작성 규칙 요약</h2>
<ul>
<li>코드에는 반드시 <strong>[기능][출처] 태그</strong>를 붙입니다.  </li>
<li><del>슬라이드 기반 구현은 <code>[FromSlide]</code></del>, 본인 설계는 <code>[Custom]</code>으로 구분합니다.  </li>
<li>구조체 확장, 함수 정의, 기존 코드 내부 삽입 등 <strong>모든 위치에 주석을 명확히 달아야 합니다.</strong>  </li>
<li><strong>&quot;무엇을 하는가&quot;보다, 왜 필요한가</strong>를 설명하는 주석이 더 중요합니다.</li>
<li><strong>섹션 주석</strong> 작성하기</li>
</ul>
<h3 id="🧱-섹션-주석-작성법">🧱 섹션 주석 작성법</h3>
<p>파일 최상단이나 큰 섹션 나눌 땐:</p>
<pre><code>/**************************************/
/*     THREAD UTILITY FUNCTIONS       */
/**************************************/</code></pre><p>📌 팁: Ctrl + F로 찾을 때 키워드가 딱 걸리니까 탐색도 쉬워져.</p>
<p>작은 기능 구역(중단, 중첩 내부 등)에선:</p>
<pre><code>// ===== [Priority Scheduling Helpers] =====</code></pre><p>✔ 보통 함수들 사이에 넣기 적합함.</p>
<hr>
<h3 id="🧩-통합-예시--이-하나만-보면-모든-규칙이-보입니다">🧩 통합 예시 – 이 하나만 보면 모든 규칙이 보입니다</h3>
<pre><code class="language-c">struct thread {
    ...
    /* [AlarmClock] 해당 스레드가 깨어나야 할 시각 (tick 기준) */
    int64_t wake_up_tick;

    /* [Donation][Custom] 이 스레드가 기부받은 priority들을 저장 */
    struct list donations;
};</code></pre>
<h3 id="📝-chatgpt용-주석작성-프롬프트">📝 chatGPT용 주석작성 프롬프트</h3>
<pre><code class="language-c">I’m working on the PintOS project and I want to add helpful comments to my C code.

Please add simple and clear English comments to each function or code block, following these rules:

1. Use only easy English words. Each sentence should be short and direct.
2. Add a feature tag at the start of each comment in this format: [FeatureName]. Example: [AlarmClock], [PriorityScheduling], [Donation].
3. If the code is a new **variable or function** that I created (not originally in PintOS), also add [Custom] after the feature tag. Example: [AlarmClock][Custom].
4. If the code is inside an existing PintOS function or modifies an existing variable, **do not** add the [Custom] tag — just use the feature tag.
5. If the line is for debugging, use [DEBUG] as the tag.
6. Only describe *why* the code exists, not just what it does. Focus on the purpose.
7. Comments should appear above the code they describe.
8. Do not explain basic C syntax. Just explain the logic or purpose.

Example:
```c
/* [AlarmClock] Set the time when this thread should wake up */
t-&gt;wake_up_tick = current_time + ticks;

/* [AlarmClock][Custom] New comparison function for sleep_list ordering */
static bool wakeup_less(...) { ... }

/* [DEBUG][Donation] Print the current priority */
printf(&quot;[DEBUG][Donation] current priority = %d\n&quot;, priority);
</code></pre>
<h3 id="📝-pintos-주석-작성-가이드-한국어-요약">📝 PintOS 주석 작성 가이드 (한국어 요약)</h3>
<ol>
<li><p><strong>쉬운 영어 단어</strong>만 사용해 주세요.<br>문장은 <strong>짧고 직접적</strong>이어야 합니다.</p>
</li>
<li><p>각 주석 앞에는 <strong>[기능 이름] 태그</strong>를 붙여 주세요.<br>예: <code>[AlarmClock]</code>, <code>[PriorityScheduling]</code>, <code>[Donation]</code></p>
</li>
<li><p>해당 코드가 <strong>PintOS 슬라이드에 없는, 내가 직접 추가한(Custom) 코드</strong>일 경우<br><code>[Custom]</code> 태그도 함께 붙여 주세요.<br>예: <code>[AlarmClock][Custom]</code></p>
</li>
<li><p><strong>디버깅용 코드</strong>에는 <code>[DEBUG]</code> 태그를 사용하세요.</p>
</li>
<li><p>주석은 <strong>코드가 무엇을 하는지</strong>보다<br><strong>왜 필요한지(목적)</strong>를 설명하는 데 집중하세요.</p>
</li>
<li><p>주석은 <strong>설명하는 코드 바로 위</strong>에 작성하세요.</p>
</li>
<li><p><strong>기본적인 C 문법</strong>은 설명하지 않아도 됩니다.<br>대신 <strong>로직의 목적이나 동작 이유</strong>를 설명하세요.</p>
</li>
</ol>
<p>--&gt; 주석 작성이 어려우면 이 프롬프트 먹여서 AI가 작성하게 시키면 주석 작성 규칙에 맞춰서 써줄거임. 아래는 이해안되면 읽어보쇼</p>
<hr>
<h2 id="🧩-1-기능출처-태그-붙이기">🧩 1. 기능/출처 태그 붙이기</h2>
<h3 id="📌-태그-구조">📌 태그 구조</h3>
<pre><code class="language-c">/* [기능이름][출처] 설명 */</code></pre>
<h3 id="✅-기능-태그-예시">✅ 기능 태그 예시</h3>
<ul>
<li><code>[AlarmClock]</code></li>
<li><code>[PriorityScheduling]</code></li>
<li><code>[Donation]</code></li>
<li><code>[MLFQ]</code></li>
<li><code>[DEBUG]</code> ← 디버깅 출력</li>
</ul>
<h3 id="✅-출처-구분">✅ 출처 구분</h3>
<p>| 태그 | 의미 |
|------|------||
| <code>[Custom]</code> | 구현상 필요해서 직접 설계한 코드 |</p>
<h3 id="🔍-예시">🔍 예시</h3>
<pre><code class="language-c">/* [AlarmClock][Custom] sleep_list 정렬 비교 함수 */
static bool wakeup_less(...) { ... }

printf(&quot;[DEBUG][AlarmClock] current_tick=%lld\n&quot;, ticks);</code></pre>
<hr>
<h2 id="🧩-2-함수-주석-작성법">🧩 2. 함수 주석 작성법</h2>
<ul>
<li>함수 위에는 간단한 기능 설명과 호출 조건을 함께 작성합니다.<pre><code class="language-c">/* [PriorityDonation]
* 현재 스레드가 lock을 요청할 때, holder가 낮은 priority를 가진 경우 priority 기부
*/
void donate_priority(void) { ... }</code></pre>
</li>
</ul>
<hr>
<h2 id="🧩-3-기존-함수-내부에-코드-추가-시">🧩 3. 기존 함수 내부에 코드 추가 시</h2>
<ul>
<li><p>삽입한 코드 앞에 주석을 붙여 <strong>기능과 목적을 설명</strong>합니다.</p>
<pre><code class="language-c">void timer_interrupt(struct intr_frame *args UNUSED) {
  ticks++;

  /* [AlarmClock]sleep_list에서 깰 스레드 확인 및 unblock */
  thread_wakeup(ticks);

  thread_tick();
}</code></pre>
</li>
</ul>
<hr>
<h2 id="🧩-4-구조체-필드-확장-시">🧩 4. 구조체 필드 확장 시</h2>
<ul>
<li><p>확장한 이유를 필드마다 주석으로 명확히 남깁니다.</p>
<pre><code class="language-c">struct thread {
  ...
  /* [AlarmClock] 깨어날 tick 저장 */
  int64_t wake_up_tick;

  /* [Donation][Custom] 기부받은 priority 저장용 리스트 */
  struct list donations;
};</code></pre>
</li>
</ul>
<hr>
<h2 id="🧩-5-디버깅-코드-관리">🧩 5. 디버깅 코드 관리</h2>
<ul>
<li>디버깅용 출력 또는 코드에는 반드시 <code>[DEBUG]</code> 태그를 붙이고,</li>
<li>최종 제출 전에는 삭제하거나 주석 처리하세요.<pre><code class="language-c">printf(&quot;[DEBUG][Donation] current priority=%d\n&quot;, t-&gt;priority);</code></pre>
</li>
</ul>
<hr>
<h2 id="✅-마무리-팁">✅ 마무리 팁</h2>
<ul>
<li>태그는 <strong>항상 대괄호[]</strong>, <strong>앞글자 대문자 + CamelCase</strong></li>
<li>기능과 출처를 <strong>함께 표시</strong>하면 가독성 및 추적성 ↑</li>
<li>한눈에 보이는 주석이 좋은 주석입니다!</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[PintOS-Project1-Priority Scheduling]]></title>
            <link>https://velog.io/@no-glass-otacku/PintOS-Project1-Priority-Scheduling</link>
            <guid>https://velog.io/@no-glass-otacku/PintOS-Project1-Priority-Scheduling</guid>
            <pubDate>Sat, 12 Apr 2025 07:20:56 GMT</pubDate>
            <description><![CDATA[<p>📁 threads/
├── thread.h     ← 우선순위 필드 및 함수 선언
├── thread.c     ← 스케줄링 로직, donation, ready_list 처리
├── synch.c      ← 세마포어, 락, 조건변수 → donation 반영
├── synch.h      ← 관련 구조체 필드 추가</p>
<p><img src="https://velog.velcdn.com/images/no-glass-otacku/post/9705819b-599e-4315-9109-013e1864c1b1/image.png" alt=""></p>
<p>🔐 <strong>세마포어(Semaphore)</strong>란?
세마포어는 공유 자원에 접근할 수 있는 <strong>&quot;허가증&quot;</strong> 같은 역할을 하는 변수야.
목적: 여러 스레드가 동시에 하나의 자원을 접근하려고 할 때, 충돌을 막기 위해 사용함.</p>
<blockquote>
<ul>
<li>sema_down()
허가증을 획득하려는 시도. 허가증이 없으면 waiters 리스트에 들어가서 block 상태로 전환됨</li>
</ul>
</blockquote>
<ul>
<li>sema_up()
허가증을 반환. waiters 리스트에서 우선순위 높은 스레드를 깨움 unblock</li>
<li>waiters    기다리는 스레드 목록 (정렬 기준: priority)</li>
</ul>
<p>Main goal
현재 PintOS 스케줄러는 FIFO 방식 → 우선순위 기반 스케줄링으로 바꾸는 게 목표</p>
<p>🎯 목표</p>
<ul>
<li>ready_list를 priority 기준으로 정렬해서 가장 높은 우선순위 스레드를 먼저 실행</li>
<li>세마포어, 락, 조건변수 등 동기화 구조에서도 높은 우선순위 스레드 먼저 깨어나게 하기</li>
<li>Preemption (선점): 새로 들어온 스레드가 더 높은 우선순위면 CPU 뺏기</li>
</ul>
<p>📌 구현에서 고려할 핵심 세 가지</p>
<ol>
<li>Priority Scheduling</li>
</ol>
<ul>
<li><p>ready_list에서 항상 priority 높은 스레드 먼저 실행</p>
</li>
<li><p>새 스레드가 들어오면 현재 running 스레드와 우선순위 비교</p>
</li>
</ul>
<ol start="2">
<li>Priority Synchronization</li>
</ol>
<ul>
<li>Lock, Semaphore, Condition Variable 등에서 기다리는 스레드도 priority 순 정렬</li>
</ul>
<ol start="3">
<li>Priority Donation</li>
</ol>
<ul>
<li>낮은 우선순위 스레드가 락을 잡고 있어서 높은 우선순위 스레드가 대기하는 문제 해결</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[OS design 챕터별 구조도]]></title>
            <link>https://velog.io/@no-glass-otacku/OS-design-%EC%B1%95%ED%84%B0%EB%B3%84-%EA%B5%AC%EC%A1%B0%EB%8F%84</link>
            <guid>https://velog.io/@no-glass-otacku/OS-design-%EC%B1%95%ED%84%B0%EB%B3%84-%EA%B5%AC%EC%A1%B0%EB%8F%84</guid>
            <pubDate>Sun, 06 Apr 2025 04:20:19 GMT</pubDate>
            <description><![CDATA[<p>Processes</p>
<h2 id="ch3">CH3</h2>
<h3 id="├──-process">├── Process</h3>
<p>│&emsp;├── 📌Process Concept /정의 및 구성 요소
│&emsp;├──🔄 Process State /상태 변화 및 PCB(Process Control Block)
│&emsp;├──-🧵Threads(Process 내부)/하나의 프로세스 내부 다중 실행 흐름
│&emsp;├──🐧 Linux에서의 프로세스 표현/ task_struct
│&emsp;├──⚙️ Process Scheduling/ scheduling queues, Context switch, 
│&emsp;│&emsp;&emsp;&emsp;&emsp;&emsp;│&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; Multitasking in Mobile Systems 등
│&emsp;│&emsp;&emsp;&emsp;&emsp;&emsp;└── scheduling queues: Ready queue, Wait queues
│&emsp;│
│&emsp;└──📥Operations on Processes/ 생성(fork, exec) / 종료 (exit, wait)
├──💬Interprocess Communication (IPC)
│&emsp;├──📦Shared Memory/ 공유 메모리 방식
│&emsp;├──✉️ Message Passing/ 메시지 전송 기반 통신
│&emsp;│&emsp;&emsp;&emsp;&emsp;└── Direct &amp; Indirect Communication
│&emsp;│&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;└──Synchronization, Buffering
│&emsp;└── 🛠️ IPC 예시 시스템/ POSIX, Pipes 등
└──🌐 Client-Server Communication/ Sockets, RPC, Java 소켓 예제 등</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ch3 문제풀이를 위한 개념 정리]]></title>
            <link>https://velog.io/@no-glass-otacku/Ch3-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%9C%EB%85%90-%EC%A0%95</link>
            <guid>https://velog.io/@no-glass-otacku/Ch3-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%9C%EB%85%90-%EC%A0%95</guid>
            <pubDate>Fri, 04 Apr 2025 10:00:05 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-p27-문제-해결을-위한-필수-개념-요약">✅ P27 문제 해결을 위한 필수 개념 요약</h2>
<hr>
<h3 id="1-tcp-sequence-number">1. <strong>TCP Sequence Number</strong></h3>
<ul>
<li><strong>정의</strong>: TCP 세그먼트의 시퀀스 번호는 해당 세그먼트가 전송하는 데이터 중 <strong>첫 번째 바이트의 번호</strong>입니다.</li>
<li><strong>예시</strong>: 바이트 127부터 80바이트 전송 시 → 시퀀스 번호는 127이고, 다음 세그먼트의 시퀀스 번호는 127 + 80 = <strong>207</strong>입니다.</li>
</ul>
<p>📚 참고: {ch3.pdf} 235~236쪽 (Section 3.4 Reliable Data Transfer)</p>
<hr>
<h3 id="2-tcp-acknowledgment-number">2. <strong>TCP Acknowledgment Number</strong></h3>
<ul>
<li><strong>정의</strong>: 수신 측이 <strong>다음으로 기대하는 바이트의 번호</strong>를 의미합니다.</li>
<li>✅ 누적 ACK(Cumulative Acknowledgment) 사용<br>→ 중간 일부만 도착하더라도, 가장 처음 누락된 바이트까지만 ACK 합니다.</li>
</ul>
<p>📚 참고: {ch3.pdf} 235~238쪽 (Section 3.4), 258쪽 부근 (TCP ACK Generation)</p>
<hr>
<h3 id="3-세그먼트-도착-순서에-따른-ack-동작">3. <strong>세그먼트 도착 순서에 따른 ACK 동작</strong></h3>
<ul>
<li><p>✅ <strong>순서대로 도착한 경우:</strong></p>
<ul>
<li>첫 번째 세그먼트: ACK = 127 + 80 = <strong>207</strong></li>
<li>두 번째 세그먼트: ACK = 207 + 40 = <strong>247</strong></li>
</ul>
</li>
<li><p>⚠️ <strong>두 번째 세그먼트가 먼저 도착한 경우:</strong></p>
<ul>
<li>예상 시퀀스가 아닌 세그먼트가 오면 → 중복 ACK 생성: <strong>ACK = 127</strong></li>
</ul>
</li>
</ul>
<p>📚 참고: {ch3.pdf} 258쪽 부근</p>
<hr>
<h3 id="4-tcp-port-numbers">4. <strong>TCP Port Numbers</strong></h3>
<ul>
<li>TCP 세그먼트는 다음 정보를 포함합니다:<ul>
<li><strong>Source Port Number</strong></li>
<li><strong>Destination Port Number</strong></li>
</ul>
</li>
<li>일반적으로 <strong>연결 세션 동안 포트 번호는 동일하게 유지됨</strong></li>
</ul>
<p>📚 참고: {ch3.pdf} 221~224쪽 (Section 3.2 Multiplexing and Demultiplexing)</p>
<hr>
<h3 id="5-ack-손실과-timeout-처리">5. <strong>ACK 손실과 Timeout 처리</strong></h3>
<ul>
<li>ACK가 손실될 경우, 송신 측은 <strong>타이머가 만료될 때까지 ACK를 기다립니다.</strong></li>
<li>⏰ 타이머가 만료되면 해당 세그먼트를 <strong>재전송</strong>합니다.</li>
</ul>
<p>📚 참고: {ch3.pdf} 240쪽 (rdt3.0 개념에서 설명)</p>
<h2 id="✅-p28-문제-해결을-위한-필수-개념-요약">✅ P28 문제 해결을 위한 필수 개념 요약</h2>
<hr>
<h3 id="1-tcp-flow-control-흐름-제어">1. <strong>TCP Flow Control (흐름 제어)</strong></h3>
<ul>
<li><strong>목적</strong>: 송신자가 너무 빠르게 데이터를 보내서 <strong>수신자의 수신 버퍼를 넘치게 하지 않도록</strong> 제어하는 기능.</li>
<li>이는 송신자의 속도를 수신자의 읽기 속도에 맞추는 <strong>속도 일치 서비스(speed-matching service)</strong>입니다.</li>
</ul>
<p>📄 관련 페이지: {ch3.pdf} 280~282쪽</p>
<hr>
<h3 id="2-tcp-receive-buffer와-관련-변수">2. <strong>TCP Receive Buffer와 관련 변수</strong></h3>
<p>수신자는 TCP 연결마다 <strong>receive buffer(RcvBuffer)</strong>를 할당하며, 다음 변수가 정의됩니다:</p>
<ul>
<li><code>LastByteRead</code>: 수신 응용 프로그램이 버퍼에서 읽은 마지막 바이트</li>
<li><code>LastByteRcvd</code>: 수신 버퍼에 도착한 마지막 바이트</li>
<li><strong>Receive Window (rwnd)</strong>: 버퍼의 여유 공간<br>계산식:<br>[
rwnd = RcvBuffer - (LastByteRcvd - LastByteRead)
]</li>
</ul>
<p>📄 관련 페이지: {ch3.pdf} 281~282쪽</p>
<hr>
<h3 id="3-수신자가-송신자에게-rwnd-전달-방식">3. <strong>수신자가 송신자에게 rwnd 전달 방식</strong></h3>
<ul>
<li>수신자는 <strong>자신의 rwnd 값을 ACK 세그먼트의 &quot;Receive Window&quot; 필드에 담아</strong> 송신자에게 전달함.</li>
<li>송신자는 다음을 보장해야 함:<br>[
LastByteSent - LastByteAcked \leq rwnd
]</li>
</ul>
<p>즉, <strong>수신자가 허용한 만큼만 데이터를 전송</strong>할 수 있습니다.</p>
<p>📄 관련 페이지: {ch3.pdf} 281쪽</p>
<hr>
<h3 id="4-rwnd가-0이-되었을-때의-처리">4. <strong>rwnd가 0이 되었을 때의 처리</strong></h3>
<ul>
<li>만약 수신 버퍼가 가득 차서 <strong>rwnd = 0</strong>이 되면 송신자는 블로킹됨.</li>
<li>이 경우 송신자가 아무것도 못 보내는 것을 방지하기 위해 <strong>1바이트 세그먼트를 계속 보내어</strong> 수신자의 rwnd 변화를 감지하도록 명시되어 있음.</li>
</ul>
<p>📄 관련 페이지: {ch3.pdf} 282쪽</p>
<hr>
<h3 id="5-tcp-흐름-제어-vs-혼잡-제어">5. <strong>TCP 흐름 제어 vs 혼잡 제어</strong></h3>
<ul>
<li>흐름 제어는 <strong>수신자의 속도에 맞추기 위한 것</strong></li>
<li>혼잡 제어는 <strong>네트워크 내 혼잡을 방지하기 위한 것</strong></li>
<li>둘 다 송신자의 속도를 제한하지만, <strong>이유와 메커니즘이 다름</strong></li>
</ul>
<p>📄 관련 페이지: {ch3.pdf} 280쪽</p>
<hr>
<h2 id="✅-p32-문제-해결을-위한-필수-개념-요약">✅ P32 문제 해결을 위한 필수 개념 요약</h2>
<hr>
<h3 id="1️⃣-round-trip-time-rtt의-정의">1️⃣ Round-Trip Time (RTT)의 정의</h3>
<ul>
<li><strong>RTT (Round-Trip Time)</strong>는 세그먼트를 전송하고 해당 세그먼트에 대한 ACK(확인 응답)를 수신하는 데 걸리는 시간입니다.</li>
<li>TCP는 이를 <strong>샘플 RTT (SampleRTT)</strong>라고 부르며, <strong>재전송된 세그먼트는 RTT 측정 대상에서 제외</strong>합니다.</li>
</ul>
<p>📄 {ch3.pdf} 269쪽 설명 참조</p>
<hr>
<h3 id="2️⃣-rtt-측정의-어려움과-평균의-필요성">2️⃣ RTT 측정의 어려움과 평균의 필요성</h3>
<ul>
<li>SampleRTT 값은 <strong>혼잡, 라우터 큐 대기, 시스템 부하 등</strong>에 따라 <strong>변동성이 매우 큽니다</strong>.</li>
<li>따라서 TCP는 단일 SampleRTT를 그대로 사용하지 않고, <strong>지속적으로 평균을 유지하면서 새로운 값을 반영하는 방식</strong>을 사용합니다.</li>
</ul>
<hr>
<h3 id="3️⃣-estimatedrtt의-정의와-업데이트-공식">3️⃣ EstimatedRTT의 정의와 업데이트 공식</h3>
<ul>
<li>TCP는 SampleRTT의 평균값을 <strong>EstimatedRTT</strong>라는 이름으로 유지합니다.</li>
<li>업데이트 공식은 다음과 같습니다:</li>
</ul>
<p>[
\text{EstimatedRTT} = (1 - \alpha) \cdot \text{EstimatedRTT} + \alpha \cdot \text{SampleRTT}
]</p>
<ul>
<li>여기서 α는 보통 <strong>0.125 (1/8)</strong>이 추천됩니다. (문제에서는 α = 0.1 사용)</li>
</ul>
<p>📄 {ch3.pdf} 270쪽 설명 참조</p>
<hr>
<h3 id="4️⃣-지수-이동-평균-exponential-weighted-moving-average-ewma">4️⃣ 지수 이동 평균 (Exponential Weighted Moving Average, EWMA)</h3>
<ul>
<li>위 공식은 통계적으로 <strong>지수 이동 평균 (EWMA)</strong>이라고 불립니다.</li>
<li><strong>이전 값에 더 많은 가중치</strong>를 두되, <strong>새로운 SampleRTT도 점진적으로 반영</strong>합니다.</li>
<li>왜 “exponential”인가?<br>→ 예전 SampleRTT는 시간이 지날수록 <strong>가중치가 지수적으로 줄어들기 때문입니다</strong>.</li>
</ul>
<p>📄 {ch3.pdf} 270쪽 중간 설명 참조</p>
<hr>
<h3 id="5️⃣-devrtt-편차와-timeoutinterval">5️⃣ DevRTT (편차)와 TimeoutInterval</h3>
<ul>
<li><strong>DevRTT</strong>는 SampleRTT가 EstimatedRTT로부터 얼마나 벗어나는지를 측정하는 <strong>변동성 지표</strong>입니다.
[
\text{DevRTT} = (1 - \beta) \cdot \text{DevRTT} + \beta \cdot |\text{SampleRTT} - \text{EstimatedRTT}|
]</li>
<li>β는 보통 0.25로 추천됨</li>
<li>최종적으로, 재전송 타이머(TimeoutInterval)는 다음과 같이 설정됩니다:</li>
</ul>
<p>[
\text{TimeoutInterval} = \text{EstimatedRTT} + 4 \cdot \text{DevRTT}
]</p>
<p>📄 {ch3.pdf} 270~271쪽 설명 참조</p>
<hr>
<h3 id="6️⃣-왜-이-방식이-유용한가">6️⃣ 왜 이 방식이 유용한가?</h3>
<ul>
<li>최근의 RTT 변화에 <strong>빠르게 적응</strong>하면서도, 예외적인 SampleRTT로 인해 <strong>예측이 급변하지 않도록 완충 역할</strong>을 합니다.</li>
<li>따라서 TCP는 네트워크의 불안정한 RTT 상황 속에서도 안정적으로 타이머를 관리할 수 있습니다.</li>
</ul>
<hr>
<h2 id="✅-p34를-해결하기-위한-필수-개념-정리">✅ P34를 해결하기 위한 필수 개념 정리</h2>
<hr>
<h3 id="🔹-sendbase-송신-측-변수">🔹 <code>SendBase</code> (송신 측 변수)</h3>
<ul>
<li><code>SendBase</code>는 <strong>가장 오래된 미확인 바이트의 시퀀스 번호</strong>를 나타냅니다.</li>
<li>즉, <strong>아직 ACK를 받지 못한 데이터 중 가장 앞선 바이트 번호</strong>입니다.</li>
<li><code>SendBase - 1</code>은 수신자가 <strong>정상적으로 수신하고 ACK로 확인한 마지막 바이트 번호</strong>가 됩니다.</li>
</ul>
<p>📄 관련 설명: {ch3.pdf} 273~274쪽, Section 3.5.4</p>
<hr>
<h3 id="🔹-lastbytercvd-수신-측-변수">🔹 <code>LastByteRcvd</code> (수신 측 변수)</h3>
<ul>
<li><code>LastByteRcvd</code>는 <strong>수신자가 네트워크로부터 받은 마지막 바이트의 번호</strong>입니다.</li>
<li>즉, TCP 세그먼트가 도착하여 <strong>수신 버퍼에 저장된 마지막 바이트 번호</strong>입니다.</li>
</ul>
<p>📄 관련 설명: {ch3.pdf} 281쪽, Section 3.5.5</p>
<hr>
<h3 id="✅-두-변수-간의-관계">✅ 두 변수 간의 관계</h3>
<ul>
<li><code>SendBase</code>는 송신자가 <strong>수신자로부터 ACK를 받았는지 여부에 따라</strong> 추적되는 값입니다.</li>
<li>반면 <code>LastByteRcvd</code>는 수신자가 <strong>물리적으로 받은 바이트 수</strong>를 추적합니다.</li>
<li>이 두 변수는 간접적으로 연결됩니다:<br>➤ 송신자가 <code>SendBase</code>를 갱신하는 것은, 수신자가 데이터를 받아 <code>LastByteRcvd</code>를 증가시킨 후 ACK를 보냈기 때문입니다.</li>
</ul>
<hr>
<h3 id="🔁-요약하자면">🔁 요약하자면:</h3>
<ul>
<li><code>LastByteRcvd</code>가 증가하고 → 수신자가 ACK를 보냄 →</li>
<li>이 ACK를 받은 송신자가 → <code>SendBase</code>를 증가시킴</li>
</ul>
<p>따라서 두 변수는 <strong>TCP 연결 양쪽에서 서로 상호작용하며 TCP의 신뢰성 보장을 위해 함께 동작하는 핵심 요소</strong>라고 볼 수 있습니다.</p>
<hr>
<h2 id="✅-p35를-해결하기-위한-필수-개념-정리">✅ P35를 해결하기 위한 필수 개념 정리</h2>
<hr>
<h3 id="🔹-y-section-354-송신-측-ack-필드">🔹 <code>y</code> (Section 3.5.4, 송신 측 ACK 필드)</h3>
<ul>
<li><code>y</code>는 <strong>수신자로부터 도착한 ACK 세그먼트의 ACK 번호 값</strong>입니다.</li>
<li><code>y</code>는 수신자가 <strong>“바이트 y-1까지 수신했음을”</strong> 의미합니다.</li>
<li>송신 측은 <code>y &gt; SendBase</code>일 경우 <code>SendBase = y</code>로 갱신합니다.</li>
</ul>
<p>📄 참고: {ch3.pdf} 273~274쪽</p>
<hr>
<h3 id="🔹-lastbytercvd-section-355-수신-측-변수">🔹 <code>LastByteRcvd</code> (Section 3.5.5, 수신 측 변수)</h3>
<ul>
<li><code>LastByteRcvd</code>는 <strong>수신자가 네트워크로부터 실제로 받은 가장 마지막 바이트의 번호</strong>입니다.</li>
<li>즉, TCP 세그먼트가 도착하여 <strong>수신 버퍼에 저장된 가장 높은 바이트 번호</strong>입니다.</li>
</ul>
<p>📄 참고: {ch3.pdf} 281쪽</p>
<hr>
<h3 id="🔁-두-변수-간의-관계">🔁 두 변수 간의 관계</h3>
<ul>
<li><code>y</code>는 <strong>수신자가 ACK로서 보낸 다음으로 기대하는 바이트 번호</strong>입니다.<br>즉, <strong><code>y = LastByteRcvd + 1</code></strong>가 되는 경우가 많습니다.</li>
<li>이 경우 송신자는 <code>y</code>를 받아 <code>SendBase = y</code>로 설정하고, 이는 <strong>수신자의 <code>LastByteRcvd</code>를 기준으로 자신의 송신 상태를 갱신하는 것</strong>입니다.</li>
</ul>
<hr>
<h3 id="✅-정리된-설명">✅ 정리된 설명</h3>
<ul>
<li><code>LastByteRcvd</code>는 <strong>수신자 측에서 실제 수신된 가장 마지막 바이트의 시퀀스 번호</strong></li>
<li><code>y</code>는 <strong>ACK 메시지에서 나타나는 수신자의 다음 기대 바이트 번호</strong></li>
<li>따라서 <strong><code>y = LastByteRcvd + 1</code></strong> 이고, 이로 인해 송신자는 자신이 보낸 데이터가 수신 측에서 확인되었음을 판단합니다.</li>
</ul>
<p>이러한 구조를 통해 TCP는 <strong>송신 측과 수신 측의 상태 정보를 정확히 동기화하며 신뢰성 있는 데이터 전송을 구현</strong>합니다.</p>
<hr>
<h2 id="✅-문제-p36을-풀기-위한-개념-정리">✅ 문제 P36을 풀기 위한 개념 정리</h2>
<hr>
<h3 id="1-duplicate-ack의-의미">1. <strong>Duplicate ACK의 의미</strong></h3>
<ul>
<li><strong>Duplicate ACK</strong>는 송신자가 이미 ACK를 받은 세그먼트에 대해 다시 ACK가 도착하는 것.</li>
<li>수신자는 예상보다 높은 sequence number를 가진 세그먼트를 받았을 때, <strong>중간에 누락된 세그먼트가 있다는 뜻</strong>으로 duplicate ACK를 보냄.</li>
<li>이 방식은 TCP가 <strong>NAK(Negative ACK)</strong> 없이도 <strong>세그먼트 손실을 간접적으로 탐지</strong>할 수 있게 합니다</li>
</ul>
<hr>
<h3 id="2-왜-1개-duplicate-ack만으로는-빠른-재전송을-하지-않는가">2. <strong>왜 1개 duplicate ACK만으로는 빠른 재전송을 하지 않는가?</strong></h3>
<ul>
<li>세그먼트 손실이 아닌, 단순히 <strong>세그먼트가 재정렬(out-of-order)</strong> 되어 도착했을 가능성도 있습니다.</li>
<li>네트워크 상황에 따라 <strong>임시적인 패킷 순서 뒤바뀜</strong>이 있을 수 있으며, 이는 실제 손실과는 무관합니다.</li>
<li>이런 상황에서 단 1개의 duplicate ACK만 보고 재전송을 하면 <strong>불필요한 재전송이 발생</strong>할 수 있습니다.</li>
</ul>
<hr>
<h3 id="3-3개-duplicate-ack의-의미">3. <strong>3개 duplicate ACK의 의미</strong></h3>
<ul>
<li>TCP는 3개의 duplicate ACK가 도착하면 이를 <strong>신뢰할 수 있는 손실 신호</strong>로 간주합니다.</li>
<li>왜냐하면 송신자는 일반적으로 여러 세그먼트를 백투백으로 전송하고, 수신자도 연속적으로 ACK를 보냅니다.</li>
<li>따라서 같은 ACK가 3번 중복되어 도착하면, <strong>중간에 있는 세그먼트가 손실되었고</strong>, 그 뒤의 세그먼트들이 도착했다는 것을 유력하게 추론할 수 있습니다</li>
</ul>
<hr>
<h3 id="4-fast-retransmit의-조건-요약">4. <strong>Fast Retransmit의 조건 요약</strong></h3>
<ul>
<li>TCP는 <strong>세 번째 duplicate ACK</strong>가 도착하면, 그 시점에서 <strong>fast retransmit</strong>을 수행합니다.</li>
<li>이는 타이머 만료를 기다리지 않고 빠르게 손실을 복구함으로써 <strong>end-to-end delay를 줄이기 위한 전략</strong>입니다</li>
</ul>
<hr>
<h2 id="✅-p40을-풀기-위한-필수-개념-정리-유기적-흐름">✅ P40을 풀기 위한 필수 개념 정리 (유기적 흐름)</h2>
<hr>
<h3 id="📦-1️⃣-congestion-window-cwnd">📦 1️⃣ Congestion Window (cwnd)</h3>
<ul>
<li><strong>정의</strong>: TCP 송신자가 네트워크 혼잡을 고려해 <strong>동시에 보낼 수 있는 최대 바이트 양(또는 세그먼트 수)</strong>를 의미.</li>
<li><strong>역할</strong>: cwnd 값은 전송 속도를 제한하는 핵심 변수이며, 시간에 따라 <strong>지속적으로 증가/감소</strong>함.</li>
</ul>
<p>📘 관련: 혼잡 제어 알고리즘의 기반이 되는 변수</p>
<hr>
<h3 id="🚀-2️⃣-tcp-slow-start">🚀 2️⃣ TCP Slow Start</h3>
<ul>
<li><strong>동작 조건</strong>: 연결 시작 시 또는 timeout 발생 후, cwnd는 1로 초기화되고 <strong>지수적으로 증가</strong>함 (<code>cwnd *= 2</code>).</li>
<li><strong>종료 조건</strong>: cwnd가 <strong>ssthresh</strong>(slow start threshold)를 초과하면 slow start 종료 → <strong>congestion avoidance 진입</strong></li>
</ul>
<p>📘 효과: 네트워크 상태를 빠르게 탐색하며 가능한 전송률까지 증가</p>
<hr>
<h3 id="📈-3️⃣-tcp-congestion-avoidance">📈 3️⃣ TCP Congestion Avoidance</h3>
<ul>
<li><strong>동작 조건</strong>: <code>cwnd &gt;= ssthresh</code>가 된 시점부터, cwnd는 <strong>매 RTT마다 선형적으로 증가</strong> (<code>cwnd += 1</code>)</li>
<li><strong>목적</strong>: 혼잡을 일으키지 않도록 천천히 전송 속도를 조정</li>
</ul>
<p>📘 특징: 혼잡이 감지되지 않는 한, 네트워크에 부하를 최소화하며 신중하게 윈도우 증가</p>
<hr>
<h3 id="📬-4️⃣-loss-detection-via-triple-duplicate-acks-fast-retransmit--fast-recovery">📬 4️⃣ Loss Detection via Triple Duplicate ACKs (Fast Retransmit + Fast Recovery)</h3>
<ul>
<li><strong>조건</strong>: 같은 ACK를 3번 이상 받으면, 중간 세그먼트 손실로 간주</li>
<li><strong>TCP Reno 동작</strong>:<ul>
<li><code>ssthresh = cwnd / 2</code></li>
<li><code>cwnd = ssthresh</code></li>
<li>손실된 세그먼트를 즉시 재전송 (timeout보다 빠름)</li>
</ul>
</li>
</ul>
<p>📘 장점: timeout을 기다리지 않고 빠르게 복구함 → &quot;fast retransmit&quot;</p>
<hr>
<h3 id="⏰-5️⃣-loss-detection-via-timeout">⏰ 5️⃣ Loss Detection via Timeout</h3>
<ul>
<li><strong>조건</strong>: ACK를 일정 시간 내에 받지 못하면 <strong>타이머 만료</strong></li>
<li><strong>모든 TCP 버전 공통 동작</strong>:<ul>
<li><code>ssthresh = cwnd / 2</code></li>
</ul>
</li>
<li><strong>TCP Reno</strong>:<ul>
<li><code>cwnd = 1</code> (slow start로 다시 진입)</li>
</ul>
</li>
<li><strong>TCP Tahoe</strong>:<ul>
<li>동일하게 <code>cwnd = 1</code>, <code>ssthresh = cwnd / 2</code> → 반드시 slow start로 재시작</li>
</ul>
</li>
</ul>
<p>📘 특징: 가장 보수적인 복구 방법, 전송 속도 급감</p>
<hr>
<h3 id="🎚️-6️⃣-slow-start-threshold-ssthresh">🎚️ 6️⃣ Slow Start Threshold (ssthresh)</h3>
<ul>
<li><strong>역할</strong>: slow start와 congestion avoidance를 <strong>구분하는 경계값</strong></li>
<li><strong>갱신 시점</strong>: 손실 발생 시마다 <code>ssthresh = cwnd / 2</code>로 갱신됨</li>
<li><strong>TCP의 회복 행동은 이 값에 따라 달라짐</strong></li>
</ul>
<p>📘 연관: ssthresh는 네트워크의 혼잡도를 판단하고 향후 증가 전략을 조정하는 기준선</p>
<hr>
<h3 id="🔁-7️⃣-tcp-tahoe-vs-reno-차이">🔁 7️⃣ TCP Tahoe vs Reno 차이</h3>
<table>
<thead>
<tr>
<th>동작 조건</th>
<th>TCP Reno</th>
<th>TCP Tahoe</th>
</tr>
</thead>
<tbody><tr>
<td>Triple duplicate ACK</td>
<td>Fast retransmit + fast recovery<br>(cwnd → ssthresh)</td>
<td>Fast retransmit + <strong>slow start 재진입</strong> (cwnd → 1)</td>
</tr>
<tr>
<td>Timeout</td>
<td>cwnd → 1, ssthresh = cwnd/2</td>
<td>동일 (cwnd → 1, ssthresh = cwnd/2)</td>
</tr>
</tbody></table>
<p>📘 이 차이는 (j), (k)와 같은 질문에 매우 중요함</p>
<hr>
<h3 id="⌛-8️⃣-transmission-round의-의미">⌛ 8️⃣ Transmission Round의 의미</h3>
<ul>
<li><strong>Transmission round</strong> = 한 라운드 내에서 가능한 한도까지 segment를 보낸 후, 해당 ACK가 도착할 때까지 기다리는 시간</li>
<li>각 round에서 <strong>cwnd 크기만큼 segment 전송 가능</strong></li>
</ul>
<p>📘 그래프의 x축 단위 해석의 기준이 되는 개념</p>
<hr>
<h3 id="🧮-9️⃣-segment-수-계산-방법">🧮 9️⃣ Segment 수 계산 방법</h3>
<ul>
<li>각 round에서 보낼 수 있는 세그먼트 수는 해당 round의 <strong>cwnd 값과 동일</strong></li>
<li>누적합을 통해 <strong>몇 번째 세그먼트가 어떤 round에서 전송되었는지</strong> 추적 가능</li>
</ul>
<p>📘 (h), (k)와 같은 문항에 필수</p>
<hr>
<p>이처럼 <strong>P40은 단일 개념이 아니라, TCP 혼잡 제어 알고리즘 전반의 흐름과 조건 간 전이 원리를 종합적으로 파악하는 문제</strong>입니다.<br>그래서 각 질문은 단순 수치 이상의 개념 간 상호작용에 기반해 풀어야 해요.</p>
]]></description>
        </item>
    </channel>
</rss>