<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jun_k.log</title>
        <link>https://velog.io/</link>
        <description>개발을 즐겨보자.</description>
        <lastBuildDate>Fri, 03 Jul 2026 14:37:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Jun_k.log</title>
            <url>https://velog.velcdn.com/images/jun_k/profile/0e31adab-5706-4e94-8711-7657eff89a00/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Jun_k.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jun_k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[스터디] 스레드와 프로세스 간 통신]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B0%84-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B0%84-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Fri, 03 Jul 2026 14:37:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>스레드는 왜 레지스터와 스택만 독립적으로 사용할까?</strong></p>
</blockquote>
<p><strong>레지스터를 독립적으로 쓰는 이유</strong> -&gt; &quot;나 지금 어디 실행하고 있어&quot;</p>
<ul>
<li><p>현재 실행 중인 CPU 명령어 주소를 가리키는 PC(프로그램 카운터)
레지스터가 존재한다.</p>
</li>
<li><p>따로 쓰는 이유는 스레드 여러 개가 번갈아 가며 실행될 때,
&quot;내가 아까 코드 몇 번째 줄까지 실행했는지&quot;를 각자 기억하고 있어야 한다.
각자 기억하지 않고, 공유해 버리면 다른 스레드가 내 기록을 지워버려
내 차례가 왔을 때 어디서부터 시작해야 할지 모르는 
머릿속이 새하얘지는 상태가 되는 것이다.</p>
</li>
</ul>
<p><strong>스택</strong> -&gt; &quot;나 지금 어떤 함수 실행 중이야?&quot;</p>
<ul>
<li><p>함수가 호출될 때 필요한 <strong>지역 변수</strong>와
함수가 끝난 후 돌아갈 <strong>리턴 주소</strong>를 쌓아두는 공간이다.</p>
</li>
<li><p>따로 쓰는 이유는 스레드 A가 수행하는 함수 호출 순서와
스레드 B가 수행하는 함수 호출 순서는 완전히 다르다.</p>
<p>하나의 스택을 같이 쓰면 함수 호출 내역과 지역 변수가 한곳에 뒤섞여
완전히 엉망이 되고, 함수가 끝났을 때 어디로 돌아가야 할지 찾을 수가 없게 된다.</p>
</li>
</ul>
<p><strong>결론</strong></p>
<ul>
<li><p><strong>코드, 데이터, 힙은 공유</strong> -&gt; 자원을 효율적으로 쓰기 위해 공통 자원은 같이 쓴다.</p>
</li>
<li><p><strong>레지스터, 스택은 독립적으로</strong> -&gt; &quot;내가 지금 어디 코드를 실행하고 있고,
어떤 함수를 거쳐왔는지&quot;라는 독립적인 실행 흐름을 위해 이 두개는 따로 쓴다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>병렬처리 4가지 방법</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/eaecd427-434c-4a33-8551-dc8db047bbdf/image.png" alt=""></p>
<hr>
<blockquote>
<p><strong>동일한 컴퓨터 내부에서 파이프 통신이 소켓 통신보다 어떤 부분이 효율적일까?</strong></p>
</blockquote>
<ul>
<li><p>파이프는 동작 원리가 운영체제(커널) 메모리에 고정된 크기의 
공유 버퍼를 하나 만들어서 그 안에서 데이터를 주고받는다.</p>
</li>
<li><p>장점은 데이터 포장이나 복잡한 연결 절차(Handshake)없이
메모리 복사 한 번으로 데이터가 바로 이동한다.
통신을 위해 관리해야 하는 부분이
가볍고 단순하기에 CPU 연산량과 메모리를 거의 쓰지 않는다.</p>
</li>
</ul>
<ul>
<li><p>소켓은 외부 네트워크 컴퓨터와의 통신을 전제로 설계되어서
포트를 열고 TCP/IP 프로토콜 스택을 거쳐 데이터를 패킷 단위로 주고 받는다.</p>
</li>
<li><p>동일 컴퓨터 내에서 발생하는 비효율은
일단 불필요하게 포장을 해야한다.
같은 컴퓨터 안에서 루프백(<code>127.0.0.1</code>)으로 통신하더라도
데이터는 네트워크 패킷 형태로 포장, 재조립 되는 프로토콜 단계를 무조건 거친다.
이 과정에서 내부 메모리 복사가 여러 번 발생한다.</p>
<p>통신 전 3-Way Handshake 같은 논리적인 연결 과정을 거쳐야 하고, 로컬 안에서는
발생할 리 없는 패킷 분실이나 순서 뒤바뀜을 막기 위한
흐름 제어를 계속 유지해야 하니 자원 낭비가 심하다.</p>
</li>
<li><p>결론은 외부 네트워크 연결 가능성이 전혀 없는 동일 컴퓨터 내 통신이면
불필요한 연산과 절차가 생략된 <strong>파이프 방식이 소켓보다 훨씬 효율적</strong>이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>파일 포인터에 대한 설명 정리</strong></p>
</blockquote>
<ul>
<li>파일 포인터는 3단계로 설명할 수 있다.</li>
</ul>
<ol>
<li><p><strong>파일 열기 (초기화)</strong></p>
<ul>
<li><p>파일을 처음 열면, 손가락(파일 포인터)은 파일의 맨 첫 부분(0바이트 위치)을 가리킨다.</p>
</li>
<li><p><strong>예외</strong>: 만약 기존 파일의 맨 뒤에 내용을 덧붙이는 <strong>이어쓰기(Append) 모드</strong>로 열면
손가락은 자동으로 파일의 맨 끝(EOF, End of File)을 가리킨다.</p>
</li>
</ul>
</li>
<li><p><strong>읽기/쓰기 작업 (자동 이동)</strong></p>
<ul>
<li><p>파일 포인터의 가장 큰 특징은 &quot;작업한 만큼 알아서 앞으로 전진한다&quot;는 점이다.</p>
</li>
<li><p>만약 현재 위치에서 10바이트만큼 데이터를 읽거나 쓰면, 포인터도 자동으로
10바이트 뒤로 이동하여 다음 작업 위치에서 대기한다.</p>
</li>
</ul>
</li>
<li><p><strong>위치 강제 이동 (임의 접근)</strong></p>
<ul>
<li>순서대로만 읽지 않고, 원하는 위치로 포인터를 강제로 이동시킬 수 있다.
프로그래밍에서는 주로 <code>fseek()</code> 같은 함수를 사용해서
&quot;처음부터 50바이트 떨어진 곳으로 이동하라&quot;고 명령한다.</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jun_k/post/6edfc6a0-04c7-4502-9b49-e38946627040/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 프로세스]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</guid>
            <pubDate>Sat, 27 Jun 2026 13:33:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>프로세스의 상태</strong></p>
</blockquote>
<ul>
<li><p><strong>프로그램</strong>은 디스크에 저장된 정적인 데이터이고,</p>
</li>
<li><p><em>프로세스*</em>는 메모리에 적재되어 실행 중인 동적인 상태이다.</p>
</li>
<li><p><strong>PCB</strong>는 운영체제가 프로세스를 관리하기 위해 
커널 영역에 생성하는 &#39;상태 정보 저장소&#39;이다.</p>
</li>
<li><p>프로그램 + 메모리 적재 + PCB 생성 = 프로세스
(완료 시 PCB가 삭제, 프로세스 소멸)</p>
</li>
</ul>
<blockquote>
<p><strong>활성 상태의 5단계</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/5d0dbd1e-e9cb-4eda-a63c-9f7fb1b8d5e6/image.png" alt=""></p>
<p>프로세스가 정상적으로 메모리에 머물며 실행 진행하는 상태이다.</p>
<ol>
<li><strong>생성</strong></li>
</ol>
<ul>
<li><p>프로그램이 메모리에 올라오고, 커널에 PCB가 막 생성된 시점</p>
</li>
<li><p>바로 실행되지 않는 이유는 CPU 자원은 한정되어 있고,
이미 다른 프로세스들이 대기 중이므로 공평한 스케쥴링을 위해
즉시 실행하지 않고 &#39;준비 큐&#39;로 이동시킨다.</p>
</li>
</ul>
<ol start="2">
<li><strong>준비</strong></li>
</ol>
<ul>
<li><p>메모리에는 올라와 있지만, CPU를 할당받지 못해 자신의 차례를 기다리는 상태</p>
</li>
<li><p><strong>디스패치</strong>: CPU 스케줄러가 준비 큐에 있는 프로세스 중 
하나를 선택해 CPU 제어권을 넘겨주고 &#39;실행&#39; 상태로 만드는 과정</p>
</li>
</ul>
<ol start="3">
<li><strong>실행</strong></li>
</ol>
<ul>
<li><p>CPU를 할당받아 실제 명령어를 처리하는 상태
(동시에 실행 가능한 프로세스의 수는 CPU 코어 개수와 같다.)</p>
</li>
<li><p><strong>타임 슬라이스와 인터럽트</strong>: 한 프로세스의 CPU 독점을 막기 위해 
제한 시간(타임 슬라이스)이 주어진다. 
하드웨어 클록이 시간을 재고, 시간이 만료되면 인터럽트를 발생시켜 
해당 프로세스를 강제로 &#39;준비&#39; 상태로 되돌린다.</p>
</li>
</ul>
<ol start="4">
<li><strong>대기</strong></li>
</ol>
<ul>
<li><p>실행 중이던 프로세스가 입출력 작업을 요청하여 작업이 완료될 때까지 멈춰 있는 상태</p>
</li>
<li><p><strong>왜 대기 상태가 필요한가?</strong>: CPU 처리 속도에 비해 입출력 장치의 속도는 
수만 배 이상 느리다. 
입출력이 끝날 때까지 CPU가 해당 프로세스에 묶여 대기하면 
심각한 자원 낭비가 발생한다. 
따라서 요청을 한 프로세스는 대기 상태로 빼고, 
CPU는 즉시 준비 큐의 다른 프로세스를 실행하여 효율을 극대화한다.</p>
</li>
<li><p><strong>왜 입출력 완료 후 &#39;실행&#39;이 아닌 &#39;준비&#39;로 가는가?</strong>: 입출력이 끝났다고 해서 
곧바로 현재 실행 중인 다른 프로세스에서 CPU를 빼앗아 넘겨주면, 
컨텍스트 스위칭 비용이 증가하고 스케줄링 로직이 복잡해진다. 
따라서 일단 &#39;준비&#39; 상태로 돌아가 순리대로 다시 차례를 기다리는 것이 논리적이다.</p>
</li>
</ul>
<ol start="5">
<li><strong>완료</strong></li>
</ol>
<ul>
<li><p>모든 작업이 끝난 상태이고, 사용하던 메모리, 열어 본 파일, CPU 자원을
운영체제에 반납하고, PCB가 삭제된다.</p>
</li>
<li><p><strong>코어 덤프</strong>: 오류 등으로 비정상 종료될 경우, 추후 디버깅을 위해 
프로세스가 죽기 직전의 메모리 상태를 그대로 파일로 저장하는 기능이다.</p>
</li>
</ul>
<blockquote>
<p><strong>비활성 상태</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/7e8cddd8-07bf-49d0-9e36-ad0a9ad7e24e/image.png" alt=""></p>
<p>메모리나 실행 흐름에서 일시적으로 벗어난 상태이다.
아래 두 상태의 차이는 <strong>&#39;메모리 점유 여부&#39;</strong>이다.</p>
<ol>
<li><strong>휴식 상태</strong></li>
</ol>
<ul>
<li><p>프로세스가 메모리에는 그대로 남아있지만 실행만 일시 정지된 상태</p>
</li>
<li><p>사용자가 명시적으로 중단 시그널을 보냈을 때 (예: Unix/Linux에서<code>Ctrl + Z</code>)</p>
</li>
<li><p>메모리에 남아있으므로 fg(포그라운드)나 bg(백그라운드) 명령어 등으로 
멈춘 지점부터 즉시 재시작이 가능하다.</p>
</li>
</ul>
<ol start="2">
<li><strong>보류 상태</strong></li>
</ol>
<ul>
<li><p>프로세스가 메모리에서 쫒겨나 디스크의 스왑 영역으로 이동한 상태</p>
</li>
<li><p>쫒겨난 이유는 물리적인 메모리 공간 부족하거나, 
너무 오랫동안 입출력 대기하고 있어 메모리를 차지하는 것 
자체가 비효율적이라고 운영체제가 판단했을 때 강제로 쫒아낸다.</p>
</li>
<li><p>보류 상태의 세분화</p>
<ul>
<li><p><strong>보류 대기</strong>: &#39;대기&#39; 상태에서 메모리를 뺏김
(스왑 영역에서 쫒겨난 채로 입출력 완료 기다림)</p>
</li>
<li><p><strong>보류 준비</strong>: 보류 대기 중 입출력이 완료되거나
&#39;준비&#39; 상태에서 메모리를 뺏김,
이 상태에서는 당장 실행할 준비가 되었지만
메모리가 없어 대기 중이기에 다시 메모리만 할당받으면
즉시 &#39;준비&#39; 상태로 복귀한다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>PCB(프로세스 제어 블록)의 필요성</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/f24bb208-224a-4b11-8a1b-94e1448d6ae3/image.png" alt=""></p>
<ul>
<li><p>운영체제가 프로세스를 관리하기 위해 커널 영역에 저장하는 상태 정보 묶음인
PCB가 필요한 이유는 멀티태스킹 환경에서는 CPU가 
여러 프로세스를 번갈아 가며 실행한다.</p>
<p>프로세스가 CPU를 넘겨줬다가 다시 할당받았을 때, 
처음부터 다시 시작하는 것이 아니라 멈춘 지점부터 이어서 실행하기 위해
이전 상태를 기억할 장소가 필요하다.</p>
</li>
<li><p>프로세스가 생성될 때 고유한 PCB가 함께 커널에 만들어지고, 프로세스가
종료되면 사용하던 자원가 함께 PCB도 폐기된다.</p>
</li>
</ul>
<blockquote>
<p><strong>PCB의 구성 요소</strong></p>
</blockquote>
<ul>
<li><p><strong>포인터</strong>: 준비 큐나 대기 큐는 연결 리스트로 구현되므로
다음 차례의 PCB를 가리키기 위해 필요하다.</p>
</li>
<li><p><strong>프로그램 카운터(PC)</strong>: 프로세스가 다음에 실행할 &#39;명령어의 위치&#39;를 가리킨다.
CPU를 다시 얻었을 때 정확히 어느 코드부터 읽어야 할지 알아야 하기 때문이다.</p>
</li>
<li><p><strong>레지스터 정보</strong>: 연산 중이던 CPU 내부 레지스터의 중관 결과값들이다.</p>
</li>
<li><p><strong>상태 및 식별자</strong>: 현재 프로세스의 상태(준비, 실행 등)와 고유 번호(PID),
부모/자식 관계(PPID, CPID)를 기록한다.</p>
</li>
<li><p><strong>자원 정보</strong>: 현재 메모리 어디에 올라와 있는가?
어떤 파일이나 입출력 장치(예: 사운드카드)를 쥐고 있는가를 기록한다.</p>
</li>
</ul>
<blockquote>
<p><strong>문맥 교환</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/f3c9dd96-33c7-4b69-84d3-0929d59ac6ac/image.png" alt=""></p>
<ul>
<li><p>CPU를 차지하던 프로세스를 내보내고, 새로운 프로세스를 실행 상태로 올리는 과정</p>
</li>
<li><p>현재 CPU의 상태(레지스터, PC 등)를 쫓겨나는 프로세스의 PCB에 저장</p>
</li>
<li><p>새로 들어올 프로세스의 PCB에서 과거 상태를 가져와 CPU에 덮어쓴다.</p>
</li>
<li><p>발생 조건은 할당된 시간을 다 썼을 때, 
입출력을 요청하여 대기 상태로 빠질 때 주로 발생한다.</p>
</li>
</ul>
<blockquote>
<p><strong>타임 슬라이스(Time Slice)의 크기와 딜레마</strong></p>
</blockquote>
<ul>
<li><p>문맥 교환은 당연히 기존 상태를 뒤엎고, 새 상태를 덮어쓰는데
물리적인 시간이 소모된다.</p>
</li>
<li><p><strong>타임 슬라이스가 너무 클 때</strong>: 한 프로세스가 CPU를 너무 오래 차지하므로, 다른 프로세스들의 대기 시간이 길어진다. 
(영상이 끊기거나 키보드 입력 반응이 느려짐)</p>
</li>
<li><p><strong>타임 슬라이스가 너무 작을 때</strong>: 실제 프로그램을 실행하는 시간보다, 
문맥 교환 자체에 낭비하는 시간이 더 커진다. 
(컴퓨터 전체의 처리 성능과 효율이 급격히 저하됨)</p>
</li>
<li><p><strong>결론</strong>: 문맥 교환에 버려지는 시간 비용을 고려하여, 
운영체제는 적절한 타임 슬라이스(유닉스 기준 10~200ms)를 설정해야 한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 컴퓨터의 구조와 성능 향상]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81</guid>
            <pubDate>Sat, 20 Jun 2026 00:39:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>CPU 비트 수와 버스 대역폭</strong></p>
</blockquote>
<p><strong>CPU의 처리 단위: 워드(Word)와 비트(Bit)</strong></p>
<ul>
<li><p>CPU가 한 번에 처리할 수 있는 데이터의 최대 크기를 워드라고 한다.</p>
</li>
<li><p>32비트 CPU의 1워드 = 32비트 (4 Byte)</p>
</li>
<li><p>64비트 CPU의 1워드 = 64비트 (8 Byte)</p>
</li>
<li><p>64비트 CPU는 32비트 CPU보다 한 번에 2배 많은 데이터를
처리할 수 있어 연산 효율이 더 높다.</p>
</li>
</ul>
<hr>
<p><strong>대역폭과 버스의 구조</strong></p>
<ul>
<li><p>책에서는 CPU의 워드 크기와 버스의 대역폭은 항상 동일하다고,
나와있는데 약간의 오류가 있는 것 같다.</p>
</li>
<li><p><strong>실제 구조</strong>: 데이터 버스의 폭인 대역폭은 CPU 워드 크기와 반드시
일치하지는 않는데 예를 들면 32비트 CPU인 인텔 80486은
32비트 데이터 버스를 가졌지만</p>
<p>펜티엄 CPU는 내부적으로 32비트
아키텍처이면서도 외부 데이터 버스는 64비트였다.
현대의 64비트 CPU 시스템도 대용량 데이터 전송을 위해
메모리 버스 대역폭을 128비트 이상으로 확장하여 사용한다.</p>
</li>
</ul>
<hr>
<p><strong>아키텍처, 운영체제(OS)의 호환성</strong></p>
<ul>
<li><p><strong>명칭의 유래</strong>: 인텔 8086 프로세서 시리즈(80186, 80286, 80386...)에서 유래하여 
인텔 계열 32비트 아키텍처를 x86이라 부른다. 
이후 AMD가 x86을 64비트로 확장한 표준을 만들었고, 이를 x64라고 부른다.</p>
</li>
<li><p>물론 64비트 CPU는 하위 호환성을 지원하므로 32비트 OS도 설치 및 구동이 가능하다만
CPU 성능을 온전히 사용하기 위해서는 64비트 OS를 권장한다.)</p>
</li>
</ul>
<hr>
<p><strong>윈도우(Windows)의 응용 프로그램 관리</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>64비트 응용 프로그램</th>
<th>32비트 응용 프로그램</th>
</tr>
</thead>
<tbody><tr>
<td>설치 폴더</td>
<td>C:\Program Files</td>
<td>C:\Program Files (x86)</td>
</tr>
<tr>
<td>실행 방식</td>
<td>CPU가 64비트 모드로 직접 실행</td>
<td>WOW64 호환 서브시스템을 거쳐 실행 (API 호출 및 경로 변환 담당)</td>
</tr>
</tbody></table>
<hr>
<p><strong>JVM 환경에서의 32비트 -&gt; 64비트 변화</strong></p>
<p><strong>32비트는 4GB 할당이 한계치였다.</strong></p>
<ul>
<li><p>32비트 시스템에서 메모리 주소는 <strong>32개의 비트(0과 1)</strong>로 표현된다.</p>
</li>
<li><p>32비트로 표현할 수 있는 주소 개수 = 2³² = 약 43억 개</p>
</li>
<li><p>주소 1개당 1바이트(byte)를 가리키므로 → 2³² 바이트 = 메모리 용량 단위로 환산하면
4GB가 표현 가능한 최대치이다.</p>
</li>
<li><p>즉, 아무리 RAM이 많이 꽂혀 있어도 &quot;주소를 적을 수 있는 칸&quot;이 32개뿐이라 
4GB를 넘는 주소는 애초에 표현 자체가 불가능하다.</p>
</li>
<li><p>JVM이 객체를 저장하는 힙도 결국 메모리 주소 공간 안에 있어야 한다.
32비트 환경에서는 OS, JVM 자체 코드 등이 주소 공간 일부를 이미 사용하므로
실제 자바 애플리케이션이 쓸 수 있는 힙은 4GB보다도 더 작은 경우가 많았다.</p>
</li>
</ul>
<p><strong>64비트로 바뀐 뒤 변화</strong></p>
<ul>
<li><p>주소 비트 수가 64개로 늘어남 → 표현 가능한 주소 = 2⁶⁴ (사실상 천문학적으로 큰 수)</p>
</li>
<li><p>따라서 이론적으로는 4GB라는 벽 자체가 사라진다.</p>
</li>
<li><p>실제로 -Xmx 옵션으로 힙 크기를 수십~수백 GB까지 설정 가능
(물리적 RAM과 OS 한계 내에서)</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>HBM</strong></p>
</blockquote>
<ul>
<li><p>HBM은 High Bandwidth Memory의 약자로
말 그대로 데이터를 주고받는 통로가 엄청나게 넓은 &#39;<strong>고대역폭 휘발성 메모리</strong>&#39;이다.</p>
</li>
<li><p>초고속 데이터 처리가 필수적인 AI(인공지능) 서버와 
고성능 그래픽카드에 필수로 사용된다.</p>
</li>
</ul>
<p><strong>기존 메모리(DDR) vs HBM 구조 비교</strong></p>
<ul>
<li>둘의 차이는 메모리를 배열하는 차원과 데이터를 나르는 통로의 수에 있다.</li>
</ul>
<p><strong>기존 DDR 메모리 평면 주택 (2D)</strong></p>
<ul>
<li><p><strong>구조</strong>: 메모리 칩을 바닥에 <strong>옆으로 나란히</strong> 붙이는 <strong>평면 방식</strong></p>
</li>
<li><p>1층짜리 단독주택들을 옆으로 이어 붙여 마을을 만든 형태이다.
땅을 많이 차지하고, 당연히 옆으로 쭉 이어 붙이니 그만큼 거리 또한
칩과 CPU 사이의 거리가 멀어져 데이터를 주고받는 데 시간이 더 걸린다.</p>
</li>
</ul>
<p><strong>HBM 메모리: 아파트 (3D)</strong></p>
<ul>
<li><p><strong>구조</strong>: 메모리 칩을 위로 <strong>차곡차곡 쌓아 올린 3차원 입체 구조</strong></p>
</li>
<li><p>똑같은 땅에 고층 아파트를 지은 형태이다.
공간을 적게 차지하며 CPU 바로 옆에 붙일 수 있어서
데이터 이동 거리가 극단적으로 짧아진다.</p>
</li>
</ul>
<p><strong>그럼 HBM을 빠르게 만든 핵심 기술은 뭔가?</strong></p>
<ul>
<li><p>HBM이 압도적으로 빠른 이유는 칩을 수직으로 뚫어 연결한</p>
</li>
<li><p><em>TSV(관통 실리콘 비아)*</em> 기술 덕분이다.</p>
</li>
<li><p>HBM은 D램을 수직으로 여러 층으로 쌓고, 이 수직으로 쌓인
칩들 사이에 수천 개의 미세한 구멍을 뚫고, 이를 <strong>수직 전극(TSV)</strong>으로
연결하여 대규모 데이터가 한 번에 이동할 수 있는
넓은 통로 즉 대역폭을 만든 것이다.</p>
<p>비유로  정리하면 각 층을 연결하는 초고속 엘리베이터 통로를 뚫은 것이다.</p>
<p>기존 DDR은 건물에 엘리베이터가 32 ~ 64개뿐이다.
입주민(데이터)이 아무리 많아도 이 좁은 개수의 통로로만
오르락내리락 해야 돼서 줄을 서며 기다려야 한다.</p>
<p>HBM은 같은 건물에 엘리베이터를 1,024개~8,192개 이상 뚫어놓은 것과 같다.
통로 개수 자체가 압도적으로 많아서, 한 번에 엄청난 인원을 동시에 실어 나를 수 있다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>마스터 부트 레코드(MBR)와 부팅 과정</strong></p>
</blockquote>
<ul>
<li><p><strong>MBR (Master Boot Record)</strong>: 컴퓨터가 켜진 후 저장장치(HDD/SSD)에서 
가장 먼저 읽는 첫 번째 섹터(공간)이다.
(부팅에 필요한 이정표 역할을 한다.)</p>
</li>
<li><p><strong>바이오스 (BIOS)</strong>: 메인보드에 내장된 기본 프로그램으로
컴퓨터 전원이 켜지면 하드웨어(CPU, 메모리 등)를 점검하고,
MBR을 찾아 실행시키는 역할을 한다.</p>
</li>
<li><p><strong>부트로더 (Bootloader)</strong>: MBR에 저장되어 있는 작은 프로그램이다.
하드디스크에 있는 운영체제(OS)를 찾아서 
컴퓨터 메모리(RAM)에 올려 실행시켜 주는 안내원 같은 역할을 한다.</p>
</li>
</ul>
<p><strong>컴퓨터 부팅의 흐름</strong></p>
<pre><code>[전원 ON] ➔ [BIOS가 하드웨어 점검] ➔ [MBR 방문 및 부트로더 실행] 
➔ [OS를 메모리에 탑재] ➔ [부팅 완료]</code></pre><p><strong>왜 공격 대상이 되는가? (MBR과 보안)</strong></p>
<ul>
<li><p>컴퓨터가 켜지자마자 가장 먼저 실행되는 곳이 MBR이기 때문에
바이러스가 이곳을 장악하거나 파괴하면 운영체제 자체가 아예 켜지지 않는 상태가 된다.</p>
</li>
<li><p>그래서 일부 컴퓨터는 바이러스가 MBR을 마음대로 수정하지 못하도록
차단하는 &#39;MBR 수정 방지 옵션&#39;을 제공한다.</p>
</li>
<li><p>이 보호 옵션을 켜두면 바이러스뿐만 아니라 정상적인 운영체제
설치 프로그램도 MBR에 접근할 수 없기에
따라서 <strong>OS를 새로 설치할 때는 반드시 이 보호 옵션을 일시적으로 해제</strong>해야
새로운 부트로더를 정상적으로 저장할 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 운영체제와 컴퓨터]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%99%80-%EC%BB%B4%ED%93%A8%ED%84%B0-myj0ru5x</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%99%80-%EC%BB%B4%ED%93%A8%ED%84%B0-myj0ru5x</guid>
            <pubDate>Fri, 05 Jun 2026 07:28:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>컴퓨터의 발전 과정</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/40d79169-247f-4d51-97a2-ebc083e5c940/image.png" alt=""></p>
<hr>
<blockquote>
<p><strong>시분할 시스템의 추가 작업이란?</strong></p>
</blockquote>
<ul>
<li><p>추가 작업이란 간단히 말하면 &quot;지금 실행해야 할 프로세스의 기억을 되살리고,
잠시 멈출 프로세스의 상태를 안전하게 저장하는 메모리 교체 작업&quot;을 의미한다.</p>
</li>
<li><p><strong>일괄 처리 시스템</strong> 즉 단일 프로그램 기준으로는
메인 메모리에 오직 &#39;하나&#39;의 사용자 프로그램만 올라가고,
CPU는 이 프로그램 끝날 때까지 그냥 한 우물만 파면 된다.
당연히 바꿀 대상이 없기에 메모리를 제어하거나 다른 프로그램 눈치 볼 필요 없고,
추가 작업도 일어나지 않는다.</p>
</li>
<li><p>다중 프로그램이 공존하는 <strong>시분할 시스템</strong> 기준으론
메인 메모리에 여러 개의 프로그램(A, B, C)가 동시에 올라와 있다.
CPU는 아주 짧은 밀리초 단위의 시간 동안 A를 실행하다가 B로 넘기고,
다시 C로 넘어가는 전환 과정을 반복한다.
CPU가 A 작업 하다가 B로 넘어갈 때 A가 어디까지 계산했는지
기억을 저장 해야 하고, B가 과거에 
어디까지 했었는지도 메모리에서 찾아와야 하기에 추가 작업이 발생하는 것이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>GNU와 GPL, 그리고 리눅스</strong></p>
</blockquote>
<p>GNU</p>
<ul>
<li><p>1983년 리처드 스톨먼이 시작한 프로젝트로
&quot;누구나 자유롭게 수정하고, 공유할 수 있는 운영체제를 만들자&quot;는 움직임이다.</p>
</li>
<li><p>운영체제를 구동하는데 필요한 수많은 
기본 프로그램(코드 컴파일 하는 도구, 텍스트 에디터 등)을 만들었다.</p>
</li>
<li><p><strong>한계</strong>: 운영체제의 가장 핵심적인 두뇌인 커널을 완성하지 못하고 있었다.</p>
</li>
</ul>
<p><strong>리눅스</strong></p>
<ul>
<li><p>1991년 리누스 토르발스가 만든 운영체제의 두뇌(커널)이다.</p>
</li>
<li><p>하드웨어를 직접 제어하고, 관리하는 핵심 기능을 수행한다.</p>
</li>
<li><p><strong>한계</strong>: 두뇌만 존재할 뿐, 사용자가 명령어를 입력하고, 화면에 결과를
보여줄 &#39;기본 프로그램&#39;들이 없었다.</p>
</li>
</ul>
<p><strong>GPL</strong></p>
<ul>
<li><p>GNU 프로젝트를 진행하면서 만든 소프트웨어 라이선스(사용 기준)이다.</p>
</li>
<li><p>&quot;이 프로그램을 가져다 쓰는 것은 자유지만, 
수정해서 다시 배포할 때는 반드시 소스 코드를 세상에 공개해야 한다&quot;는 
강제성을 가진다.</p>
</li>
</ul>
<p><strong>이들은 어떻게 연결되는가?</strong></p>
<ul>
<li><p>두뇌는 있지만 기본 프로그램이 없던 리눅스(커널)와 기본 프로그램은 만들었지만
두뇌가 없던 GNU 프로젝트가 만났다.</p>
</li>
<li><p>토르발스가 자신이 만든 리눅스 커널에 GPL 라이선스를 적용하면서
GNU의 수많은 도구와 리눅스 커널이 자유롭게 결합할 수 있게 되었다.</p>
</li>
<li><p>결과적으로 우리가 흔히 &#39;리눅스&#39;라 부르는 운영체제의 
정확한 명칭은 <strong>GNU/Linux</strong> 이다.
(GNU의 도구들과 Linux 라는 두뇌가 합쳐져 비로소 온전한 운영체제가 된 것이다.)</p>
</li>
</ul>
<hr>
<hr>
<blockquote>
<p><strong>리눅스 배포판(우분투, 데비안 등)은 왜 필요한 것인가?</strong></p>
</blockquote>
<p><strong>배포판이 생긴 이유</strong></p>
<ul>
<li><p>GNU/Linux는 완성되었지만 초기에는 일반 사용자가 가져다 쓰기 매우 어려웠다. 
두뇌(커널)와 수백 개의 도구(GNU) 그리고 필요한 프로그램들을 
사용자가 일일이 다운받아 직접 조립해야 했기 때문이다.</p>
</li>
<li><p>이때, &quot;이 조립 과정을 대신해 주고, 편리한 설치 프로그램과 
소프트웨어 관리자(패키지 매니저)까지 세트로 묶어서 
하나의 상품(또는 패키지)으로 제공하자!&quot; 하고 나온 것이 
리눅스 배포판(Distribution)이다.</p>
</li>
</ul>
<p>즉, 알맹이(Linux 커널 + GNU 도구)는 똑같은데
그것을 포장하는 방식과 추가하는 편리한 기능에 따라 이름이 달라지는 것이다.</p>
<p><strong>주요 배포판</strong></p>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">데비안 (Debian) 계열</th>
<th align="left">레드햇 (Red Hat) 계열</th>
<th align="left">슬랙웨어 (Slackware)</th>
</tr>
</thead>
<tbody><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">우분투 (Ubuntu)</td>
<td align="left">RHEL (Red Hat Enterprise Linux), 페도라 (Fedora)</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>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 자료 구조]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%9E%90%EB%A3%8C-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%9E%90%EB%A3%8C-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Thu, 28 May 2026 02:19:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>원형 이중 연결 리스트란?</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/20ec2cb6-1bb1-4be2-b8ab-f5c6adad62f4/image.png" alt=""></p>
<ul>
<li><p>원형 이중 연결 리스트를 이루는 최소 단위는 노드이다.
하나의 노드는 3개의 데이터를 가진다.</p>
<ul>
<li><code>prev</code> 포인터: 이전 노드의 메모리 주소를 가리키는 포인터</li>
<li><code>data</code>: 실제 저장하는 값 (10, 20, 30 등)</li>
<li><code>next</code> 포인터: 다음 노드의 메모리 주소를 가리키는 손</li>
</ul>
</li>
<li><p>일반 이중 연결 리스트는 첫번째 노드의 <code>prev</code>는 갈 곳이 없고, 마지막 노드(40)의 <code>next</code>도 갈 곳이 없다.
끊겨 있는 형태이기에 마지막 노드의 <code>next</code> 포인터는 아무것도 가리키지 않는 <code>null</code>이 된다.</p>
</li>
<li><p>원형 이중 연결 리스트는 시작과 끝이 연결되어 있고, 완벽한 원형 구조에서는 첫 번째 노드(10)의
<code>prev</code> 포인터 역시 마지막 노드(40)를 가리키게 설계되는 것이 일반적이다.</p>
<p>끝이 없는 회전 구조이기에 리스트의 처음과 끝을 <code>O(1)</code> 시간 만에 오갈 수 있다.</p>
<p>원형 큐나 플레이리스트의 ‘반복 재생’ 기능처럼 시작과 끝이 연결되어 
순환해야 하는 로직 구현할 때 많이 사용된다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>벡터 맨 뒤에서 삽입/삭제가 O(1)인 이유</strong></p>
</blockquote>
<p>배열과 벡터의 물리적 특징은 메모리가 연속적으로 붙어 있다는 점이다.</p>
<p><strong>중간 삽입(밀기)</strong></p>
<ul>
<li><p>방이 5개 있는 일렬로 된 책상(10, 20, 30, 40)이 있고,
맨 앞에(0번 인덱스)에 새 데이터 &#39;5&#39;를 넣으려고 한다.</p>
</li>
<li><p>연속성을 유지해야 하므로, 기존에 있던 10, 20, 30, 40을 전부
한 칸씩 뒤로 밀어야만 맨 앞에 빈자리가 생긴다.</p>
</li>
<li><p>당연히 데이터가 N개 있다면 최대 N번의 데이터 이동이 발생한다.</p>
</li>
</ul>
<p><strong>중간 삭제(땡기기)</strong></p>
<ul>
<li><p>맨 앞의 &#39;5&#39;를 삭제하면 그 자리가 비게 된다.</p>
</li>
<li><p>당연히 중간에 빈틈이 있으면 안되기에 데이터를 한 칸씩
앞으로 땡겨와야 하고, 마찬가지로 데이터 개수 만큼이니
<code>o(n)</code>이 된다.</p>
</li>
</ul>
<p><strong>벡터 맨 뒤에 삽입/삭제가 O(1)인 이유는 뭘까?</strong></p>
<ul>
<li><p>현재 마지막 데이터는 3번 인덱스의 &#39;40&#39;인데
여기에 push_back(50)을 수행하여 맨 뒤에 &#39;50&#39;을 넣으려고 한다.</p>
</li>
<li><p>맨 앞에 넣을 때는 뒤의 데이터를 밀어야 했지만
맨 뒤인 4번 인덱스는 이미 비어있는 공간이다.
그 어떤 데이터도 뒤로 밀 필요 없이, 비어있는 곳에 값을 쓱 넣기만 하면 된다.
다른 데이터에 영향을 주지 않으므로 딱 한 번의 연산인 <code>O(1)</code>이 나온다.</p>
</li>
<li><p>삭제도 맨 뒤에 있는 데이터 하나 지우는 것이니 
앞의 데이터들에 영향이 없기에 <code>O(1)</code>이 나온다.</p>
</li>
<li><p>정적 배열도 사실 위의 맨 뒤에서 일어나는 일련의 과정들에 대한 <code>O(1)</code>이
똑같긴 한데 정적 배열은 방 크기가 고정되어 있기에
방이 꽉 차면 정적 배열은 새로운 데이터를 추가하는 것 자체가 불가능하지만</p>
<p>벡터는 내부적으로는 똑같은 배열을 사용하지만 방이 꽉 찼을 때
자동으로 더 큰 집으로 이사하는 기능이 내장되어 있다.</p>
</li>
</ul>
<hr>
<p><strong>동적 배열의 확장이 일어날 때 얼마나 확장되는가?</strong></p>
<ul>
<li><p>Java (현재 가장 많이 쓰이는 <code>java.util.ArrayList</code>)</p>
<ul>
<li>기존 크기의 <code>oldCapacity &gt;&gt; 1</code>(절반을 더함)을 해서 정확히 1.5배씩 늘어난다.</li>
</ul>
</li>
<li><p>언어나 환경에 따라 달라지긴 하지만 평균적으로 1.5배 ~ 2배 정도 확장한다고 한다.</p>
</li>
<li><p>이유는 1칸씩 늘리면 당연히 너무 많은 대이동이 발생하기에 비효율적이고,
너무 크게 늘릴 경우에는 실질적으로 한 데이터를 13개 정도 밖에 안 쓰는데
배열 크기를 1,000개로 늘려버리면 남은 공간은 놀고 있는 것이고, 낭비된다.</p>
</li>
<li><p><strong>2배보다 1.5배가 더 좋다고 평가받는 이유</strong></p>
<ul>
<li><p><strong>2배로 늘릴 때</strong>: 4 -&gt; 8 -&gt; 16 -&gt; 32칸으로 늘어난다. 
이때 이사를 가기 위해 새로 잡는 메모리 크기(32)는 
이전에 버렸던 메모리들의 총합($4+8+16=28$)보다 항상 크다. 
그래서 컴퓨터가 이전에 썼던 메모리 자리를 다시 재사용하지 못하고,
계속 새로운 공간을 찾아 떠나야 한다.</p>
</li>
<li><p><strong>1.5배로 늘릴 때</strong>: 연속해서 이사를 가다 보면
이전에 버렸던 메모리 구역들을 합친 크기가 
새로 필요한 메모리 크기보다 커지는 순간이 온다. 
컴퓨터 시스템 입장에서는 &quot;이전에 쓰다 버린 빈자리들 합치니까 
이번에 이사 갈 집 크기 나오네! 여기 재사용하자&quot;가 가능해져서
메모리를 훨씬 효율적으로 쓰게 된다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<p><strong>AVL 트리</strong></p>
<p><img src="https://velog.velcdn.com/images/jun_k/post/47ac2c63-11f5-4764-a6ba-9b071bf5a532/image.png" alt=""></p>
<ul>
<li><p>AVL 트리는 높이 균형에 집학하는 완벽주의자이며 좌우 높이를 칼같이 맞춘다.</p>
</li>
<li><p><strong>왜 Balance Factor(BF)는 -1, 0, 1만 허용되는가?</strong></p>
<ul>
<li><p>이 속도를 유지하려면 트리가 양옆으로 예쁘게 퍼진 &#39;삼각형&#39; 모양 유지해야함</p>
</li>
<li><p>데이터가 계속 삽입/삭제되는 동적 상황에서 왼쪽과 오른쪽의 높이를 항상 &#39;0(완벽한 똑같은 높이)&#39;으로 맞추는 것은 불가능 하기에
최소한의 허용 오차: 1</p>
</li>
<li><p>BF = 0: 왼쪽과 오른쪽 서브트리의 높이가 완벽히 같은 상태 (가장 이상적)</p>
</li>
<li><p>BF = 1: 왼쪽이 오른쪽보다 딱 1층 더 높은 상태 (이 정도는 탐색 속도에 영향 없음)</p>
</li>
<li><p>BF = -1: 오른쪽이 왼쪽보다 딱 1층 더 높은 상태 (이 정도는 탐색 속도에 영향 없음)</p>
</li>
</ul>
</li>
</ul>
<p><strong>밸런스 팩터(BF)라는 기준</strong>
<strong>원리</strong>: 각 노드마다 <code>(왼쪽 서브트리 높이) - (오른쪽 서브트리 높이)</code>를 계산한다. 
이 값을 <strong>밸런스 팩터(BF)</strong>라고 한다.</p>
<p><strong>규칙</strong>: BF는 반드시 -1, 0, 1 중 하나여야 한다. 
이 범위를 벗어나는 순간(2 또는 -2가 되는 순간) 트리가 무너졌다고 
판단하고 즉시 행동에 나선다.</p>
<p><strong>해결책: 회전(Rotation)</strong>
균형이 깨지면 흐트러진 부분을 왼쪽이나 오른쪽으로 돌리는 회전 연산을 수행한다.</p>
<p><strong>LL / RR (단일 회전)</strong>: 한쪽으로 쏠린 노드들을 팽이처럼 한 번 슥 돌려서 균형을 맞춘다.</p>
<p><strong>LR / RL (이중 회전)</strong>: 지그재그 모양으로 꼬여서 쏠려 있는 경우, 한 번 돌려 일직선으로 만든 다음, 반대 방향으로 한 번 더 돌려 균형을 맞춘다.</p>
<p>AVL 트리는 책장이 한쪽으로 기울어지는 것을 절대 못 보는 결벽증 정리 정돈 전문가이다.
 책이 한 권만 잘못 들어와도 그 즉시 전체 책장을 완벽한 대칭으로 다시 정리한다. 
탐색 속도는 항상 최상이지만, 무언가를 넣거나 뺄 때마다 책장을 
계속 재정리(회전)해야 하므로 수정 작업이 잦을 때는 피곤한 스타일이다.</p>
<hr>
<p><strong>레드-블랙 트리</strong></p>
<p><img src="https://velog.velcdn.com/images/jun_k/post/8a918147-a6c2-4b84-b8a2-a88c1b3156c6/image.png" alt=""></p>
<ul>
<li>레드-블랙 트리는 완벽한 높이 대칭을 맞추는 대신, 
&quot;노드에 색을 칠하자&quot;라는 기발한 아이디어를 가져왔다. 
&quot;가장 긴 경로가 가장 짧은 경로의 2배만 넘지 않으면 
탐색 성능($O(\log N)$)에 문제없다&quot;는 실용주의자</li>
</ul>
<p><strong>5가지 엄격한 컬러 규칙</strong>
레드-블랙 트리는 아래 규칙만 지키면 대략적인 균형이 잡힌다는 
수학적 증명을 기반으로 움직인다.</p>
<p>모든 노드는 레드(Red) 아니면 블랙(Black)이다.</p>
<p>루트 노드(가장 최상위 노드)는 무조건 블랙이다.</p>
<p>모든 리프 노드(끝에 달린 빈 노드, NIL)는 블랙이다.</p>
<p>레드 노드는 연속으로 올 수 없다. (레드 다음엔 무조건 블랙)</p>
<p>어떤 노드에서 출발하든, 리프 노드로 내려가는 길에 만나는 블랙 노드의 개수는 모두 같다.</p>
<p><strong>해결책: 색칠하기(Re-coloring)와 회전</strong>
새로운 노드를 삽입할 때는 일단 레드로 넣는다. 
이때 4번 규칙(레드 연속 금지)을 위반하는 상황이 오면 주변 노드의 색을 보고 판단한다.</p>
<p>삼촌 노드가 레드라면? 색상만 슥슥 다시 칠한다.</p>
<p>삼촌 노드가 블랙이라면? AVL 트리처럼 회전을 시킨다.</p>
<p>레드-블랙 트리는 &quot;굳이 매번 칼같이 대칭을 맞출 필요가 있나? 
가장 긴 경로가 가장 짧은 경로의 2배만 넘지 않으면 탐색하는 데 문제없어!&quot;라고 
생각하는 실용주의자이다. 
규칙이 복잡해 보이지만, 색상만 바꾸는 가벼운 작업으로 균형을 
유지할 때가 많아서 삽입/삭제 시 AVL 트리보다 훨씬 유연하고 빠르게 대처한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 데이터베이스]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Tue, 26 May 2026 01:35:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>제2정규형 (2NF): 부분 함수 종속성 제거</strong></p>
</blockquote>
<ul>
<li><p>기본키가 여러 개 조합된 경우, 일부 키에만 엮여 있는 찌꺼기 데이터 제거하기</p>
</li>
<li><p>제2정규형의 핵심은 기본키가 여러개 합쳐진 복합키일 때,
복합키 전체가 아니라 일부에만 종속되는 데이터가 있으면 분리하라는 의미이다.</p>
</li>
<li><p>복합키는 컬럼 하나로는 데이터 한 줄을 고유하게 식별하기 어려워서
(A 컬럼 + B 컬럼)을 묶어서 기본키로 사용하는 경우이다.</p>
</li>
<li><p>부분 함수 종속은 (A + B)가 합쳐져야만 알 수 있는 정보가 아니라
A 하나만 알아도 알 수 있는 정보가 테이블에 섞여 있는 상태.</p>
</li>
<li><p><strong>문제 상황</strong>
대학교 수강 신청 데이터 관리 테이블
(한 학생이 여러 과목 들을 수 있고, 고유한 한 줄 찾으려면 </p>
</li>
<li><p>*[학번]<strong>과 **[과목명]</strong>이 동시에 기본키(복합키)가 되어야 한다.</p>
<table>
<thead>
<tr>
<th align="left">학번 (PK)</th>
<th align="left">과목명 (PK)</th>
<th align="left">성적</th>
<th align="left">강의실</th>
</tr>
</thead>
<tbody><tr>
<td align="left">101</td>
<td align="left">자바 프로그래밍</td>
<td align="left">A</td>
<td align="left">301호</td>
</tr>
<tr>
<td align="left">101</td>
<td align="left">스프링 부트</td>
<td align="left">B</td>
<td align="left">402호</td>
</tr>
<tr>
<td align="left">102</td>
<td align="left">자바 프로그래밍</td>
<td align="left">A+</td>
<td align="left">301호</td>
</tr>
</tbody></table>
</li>
<li><p>성적은 누구(학번)의 어떤 과목(과목명)가 있어야 성적을 알 수가 있다. (정상)</p>
</li>
<li><p>강의실은 학번과 상관없이 (과목명)만 있으면 강의실이 어디인지 알 수 있다. (문제)</p>
</li>
<li><p>만약 &#39;자바 프로그래밍&#39; 듣는 학생이 1,000명으로 늘어나면 똑같은 과목의 &#39;301호&#39;
라는 정보가 똑같이 1,000번 반복해서 저장된다.
강의실이 &#39;505호&#39;로 바뀌는 경우에도 1,000줄을 다 수정해줘야 한다.</p>
</li>
<li><p>해결책은 원인이 되는 <strong>부분 함수 종속을 제거</strong>하기 위해 테이블을 두 개로 분리한다.</p>
<p><strong>수강 성적 테이블</strong> (복합키와 복합키 전체가 필요한 데이터만 남김)</p>
<table>
<thead>
<tr>
<th align="left">학번 (PK)</th>
<th align="left">과목명 (PK)</th>
<th align="left">성적</th>
</tr>
</thead>
<tbody><tr>
<td align="left">101</td>
<td align="left">자바 프로그래밍</td>
<td align="left">A</td>
</tr>
<tr>
<td align="left">101</td>
<td align="left">스프링 부트</td>
<td align="left">B</td>
</tr>
<tr>
<td align="left">102</td>
<td align="left">자바 프로그래밍</td>
<td align="left">A+</td>
</tr>
</tbody></table>
<p><strong>과목 테이블</strong> (과목명 하나로 결정되는 데이터만 따로 분리)</p>
<table>
<thead>
<tr>
<th align="left">과목명 (PK)</th>
<th align="left">강의실</th>
</tr>
</thead>
<tbody><tr>
<td align="left">자바 프로그래밍</td>
<td align="left">301호</td>
</tr>
<tr>
<td align="left">스프링 부트</td>
<td align="left">402호</td>
</tr>
</tbody></table>
</li>
</ul>
<hr>
<blockquote>
<p><strong>제3정규형 (3NF): 이행적 함수 종속 제거</strong></p>
</blockquote>
<ul>
<li><p>기본키가 아닌 일반 컬럼들끼리 꼬리에 꼬리를 무는 종속 관계 제거하기</p>
</li>
<li><p>기본키가 아닌 일반 컬럼들끼리 서로 종속 관계를 가지면 안된다.
(다리를 건너서 이어지는 관계를 끊어라.)</p>
</li>
<li><p>이행적 관계는 A -&gt; B 이고, B -&gt; C 이면, 결과적으로 A -&gt; C가 성립되는 관계</p>
</li>
<li><p>DB에서는 [기본키 -&gt; 일반 컬럼 1 -&gt; 일반 컬럼 2] 형태의 종속이 일어나는 것.</p>
</li>
<li><p><strong>문제 상황</strong>
사원 정보를 관리하는 테이블, <strong>[사원번호]</strong> 하나만 <strong>기본키(PK)</strong>이다.</p>
<table>
<thead>
<tr>
<th align="left">사원번호 (PK)</th>
<th align="left">이름</th>
<th align="left">부서명</th>
<th align="left">부서 전화번호</th>
</tr>
</thead>
<tbody><tr>
<td align="left">202601</td>
<td align="left">최준영</td>
<td align="left">백엔드 개발팀</td>
<td align="left">02-123-4567</td>
</tr>
<tr>
<td align="left">202602</td>
<td align="left">김철수</td>
<td align="left">백엔드 개발팀</td>
<td align="left">02-123-4567</td>
</tr>
<tr>
<td align="left">202603</td>
<td align="left">이지수</td>
<td align="left">인사팀</td>
<td align="left">02-987-6543</td>
</tr>
</tbody></table>
<ul>
<li><p>사원번호를 알면 그 사원의 부서명을 알 수 있다. (사원번호 -&gt; 부서명)</p>
</li>
<li><p>부서명을 알면 그 부서의 부서 전화번호를 알 수 있다. (부서명 -&gt; 부서 전화번호)</p>
</li>
<li><p>결과적으로 사원번호를 통해 부서 전화번호까지 알게 된다. (사원번호 -&gt; 부서명 -&gt; 전화번호)</p>
</li>
<li><p>백엔드 개발팀에 속한 사원이 100명이면 &#39;02-123-4567&#39;이라는 번호가 
100번 중복 저장되고, 만약 새로운 부서가 신설되었는데 
아직 소속된 사원이 한 명도 없으면 테이블에 사원번호가 없으므로 
새로운 부서와 부서 전화번호 등록할 수조차 없다.</p>
</li>
</ul>
</li>
<li><p>해결책은 중간 다리 역할을 하는 일반 컬럼(부서명)을 기준으로 테이블을 쪼갠다.</p>
</li>
<li><p>아래처럼 바뀌게 되면 신설 부서도 등록이 가능하고, 
전화번호가 바뀌어도 한 곳만 수정하면 된다.</p>
<p><strong>사원 기본 테이블</strong></p>
<table>
<thead>
<tr>
<th align="left">사원번호 (PK)</th>
<th align="left">이름</th>
<th align="left">부서명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">202601</td>
<td align="left">최준영</td>
<td align="left">백엔드 개발팀</td>
</tr>
<tr>
<td align="left">202602</td>
<td align="left">김철수</td>
<td align="left">백엔드 개발팀</td>
</tr>
<tr>
<td align="left">202603</td>
<td align="left">이영희</td>
<td align="left">인사팀</td>
</tr>
</tbody></table>
<p><strong>부서 정보 테이블</strong></p>
<table>
<thead>
<tr>
<th align="left">부서명 (PK)</th>
<th align="left">부서 전화번호</th>
</tr>
</thead>
<tbody><tr>
<td align="left">백엔드 개발팀</td>
<td align="left">02-123-4567</td>
</tr>
<tr>
<td align="left">인사팀</td>
<td align="left">02-987-6543</td>
</tr>
</tbody></table>
</li>
</ul>
<hr>
<blockquote>
<p><strong>트랜잭션의 지속성을 지키기 위한 기능들</strong></p>
</blockquote>
<ul>
<li>지속성은 시스템에 어떤 장애(갑작스러운 전원 차단, 하드웨어 고장 등)가
발생해도 이미 성공적으로 커밋된 트랜잭션 결과에 한해서 
데이터가 절대 유실되거나 변질되지 않고, 영구적으로 유지되어야 한다는 성질이다.</li>
</ul>
<p><strong>체크섬</strong></p>
<ul>
<li><p>데이터 오염 여부를 확인하는 디지털 지문</p>
</li>
<li><p>데이터가 디스크에 저장 or 네트워크 이동할 때 
&quot;이 데이터가 깨지지 않고, 안전한 상태인가?&quot;를 검증하는 장치이다.</p>
</li>
<li><p>데이터를 디스크에 저장하기 직전, 어떤 특정 수학적 계산 공식에
데이터를 집어넣고, 고유한 값(예: <code>0x7A3F</code>)을 만들어낸다.
이를 <strong>체크섬</strong>이라 부르고, 데이터 뒤에 꼬리표처럼 붙여서 함께 저장한다.</p>
</li>
<li><p>나중에 디스크에서 데이터 읽을 때 읽어온 데이터를 똑같은 수학 공식에 대입하여
새로운 체크섬을 만들고, 처음 저장했던 체크섬과 비교하여 데이터 오염 여부를 판단한다. (오염되었다면 당연히 복구 절차를 밟아야 한다.)</p>
</li>
</ul>
<p><strong>저널링</strong></p>
<ul>
<li><p>DB에서 가장 무거운 작업은 디스크 임의의 위치에 
실제 데이터를 직접 쓰는 물리적 행위이다.</p>
</li>
<li><p>만약 데이터를 쓰는 도중 서버가 뻥나면 데이터가 
제대로 기록되지 않아 복구가 불가능해진다.
이를 해결하기 위해 
&quot;데이터 바꾸기 전, 무엇을 바꿀지 미리 적어놓는다.&quot;의 개념이</p>
</li>
<li><p><em>저널링*</em>이다.</p>
</li>
<li><p>트랜잭션 발생 -&gt; 실제 데이터 건드리기 전에 저널 혹은 로그라 불리는
파일에 &quot;X번 데이터를 Y로 바꿀 것이다.&quot;라는 내용을 순서대로 빠르게 기록한다.
이 기록은 단순히 뒤에 이어 붙이기만 하면 되므로 속도가 매우 빠르다.</p>
</li>
<li><p>로그, 저널 기록이 안전하게 디스크에 저장(커밋)된 것이 확인되면
그제야 메모리에 있던 변경 사항을 실제 데이터 파일에 반영한다.</p>
</li>
<li><p>로그와 실제 데이터 저장하는 공간은 
저널 전용 공간으로 실제 데이터 공간과 다른 공간이다.</p>
</li>
<li><p>지속성에 필요한 이유는 한창 실제 데이터 파일에 값을 바꾸고 있는데
갑자기 정전이 발생한 경우를 가정해보면
디스크를 다시 켜면 데이터 파일은 깨져있을 수 확률이 높다.
하지만 로그/저널 기록에는 어떤 데이터를 어떻게 바꾸겠다 라는 기록이 남아있다.
시스템은 이 기록을 처음부터 다시 읽으며 깨진 데이터 파일 위에 올바른 값을
다시 덮어씌워 트랜잭션 결과를 완벽하게 복구해낸다.</p>
</li>
</ul>
<p><strong>추가 기능</strong></p>
<p><strong>OS 버퍼를 뚫고 디스크로 밀어 넣기: fsync 시스템 콜</strong></p>
<ul>
<li><p>애플리케이션이 디스크에 저장하라고 명령해도 OS는 성능을 위해
곧바로 디스크에 쓰지 않고, 메모리(OS Buffer Cache)에 임시로 들고 있다.
당연히 이 상태에서 정전 되면 데이터는 다 날아가 버린다.</p>
</li>
<li><p>해결책은 DB는 트랜잭션 완료되는 순간, OS에게 
&quot;동작 멈추고, 메모리에 있는 로그를 디스크 하드웨어에 물리적으로 정착시켜라&quot; 라고,
강제적인 명령을 내린다. ( <code>fsync()</code> )
이 과정이 성공해야만 지속성을 챙길 수 있다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>PostgreSQL</strong></p>
</blockquote>
<p><strong>PostgreSQL 스토리지 엔진 아키텍처</strong>
<img src="https://velog.velcdn.com/images/jun_k/post/4c8211b4-6b8c-4786-a782-3a71822db313/image.png" alt=""></p>
<ul>
<li><p>위 그림은 요청 처리 -&gt; 메모리 관리 -&gt; 디스크 저장을 설명해준다.</p>
</li>
<li><p><strong>PostgreSQL 서버 프로세스 (요청 처리반, 맨 위 계층)</strong></p>
<ul>
<li><p>클라이언트(앱)가 SQL 쿼리 보냈을 때 이를 해석하고, 실행 계획 짜는 곳</p>
</li>
<li><p>연결 프로세스에서 클라이언트 접속 요청을 받고, (쿼리 파서 / 분석기)가
들어온 SQL 문법이 맞는지 검사하고, 분석한다.</p>
</li>
<li><p>플래너가 &quot;이 데이터 어떻게 찾아야 가장 빠르게 찾을까?&quot; 고민하며
가장 효율적인 실행 계획을 짠다.</p>
</li>
<li><p>실행기는 플래너가 짠 계획대로 실제 데이터 가져오기 or 수정하라고 
아래 계층에 명령을 내린다.</p>
</li>
</ul>
</li>
<li><p><strong>스토리지 관리 계층 (메모리 및 작업 반장, 중간 계층)</strong></p>
<ul>
<li><p>실제 데이터 처리를 조율하는 핵심 계층
(성능 위해 대부분 메모리에서 작업된다.)</p>
</li>
<li><p>버퍼 관리자는 디스크에 있는 데이터 페이지를 메모리(버퍼 캐시)로 올려서
들고 있는 관리자이고, 모든 읽기/쓰기는 이 메모리를 거쳐야 빨라진다.</p>
</li>
<li><p>트랜잭션 관리자는 격리 수준 유지하고, 여러 사람이 동시에 
데이터 붙어도 꼬이지 않도록 동시성(MVCC) 보장한다.</p>
</li>
<li><p><strong>WAL  관리자</strong>: 위에 작성한 저널링을 담당한다.
데이터가 바뀌면 무조건 디스크의 WAL 파일에 
그 기록을 먼저 저장하여 지속성을 보장한다.</p>
</li>
<li><p>체크포인트 관리자는 메모리에만 머물고 있는 변경된 데이터들을
주기적으로 모아서 실제 디스크 파일로 안전하게 밀어 넣는 타이밍을 잡는다.</p>
</li>
<li><p>백그라운드 프로세스는 뒤에서 묵묵히 찌꺼기 데이터를 
청소하는 <code>Autovacuum</code>이나 로그를 백업하는 <code>Archiver</code> 등이 있다.</p>
</li>
</ul>
</li>
<li><p><strong>데이터 저장 및 물리적 구조 (최종 디스크 저장소, 아래 계층)</strong></p>
<ul>
<li><p>컴퓨터가 꺼져도 데이터가 영원히 보존되는 물리적인 디스크 영역</p>
</li>
<li><p>데이터 파일(테이블/인덱스)은 실제 테이블 데이터가 <strong>8KB 크기의 조각(페이지)</strong>
단위로 쪼개져서 저장되는 곳이다.</p>
</li>
<li><p><strong>WAL 파일 (로그)</strong>: 변경 이력이 순서대로 적히는 공간이다.
정전이 나면 이 파일을 보고 복구하는 것이다.</p>
</li>
<li><p>설정/제어/임시 파일은 DB의 현재 상태, 정렬할 때 쓰는 임시 공간 등
시스템 운영에 필요한 기능들을 모아놓은 곳이다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>B-트리</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_k/post/cc16b344-2bc6-494c-a2c9-a5e857be3fbf/image.png" alt=""></p>
<ul>
<li><p>B-트리의 각 노드에는 숫자가 정렬되어 들어있고, 그 숫자들은
&quot;내 왼쪽 길은 나보다 작은 숫자들이 있고, 내 오른쪽 길은 나보다 큰 숫자가 있는 곳이다.&quot;
라는 이정표 역할을 한다.</p>
</li>
<li><p>처음에 루트 노드에서 시작하고, 상자에는 [39, 83]이 있다.
기준점 두 개가 있으니 길은 세 갈래로 나뉜다.</p>
<ul>
<li><p>39보다 작거나 같은 동네로 가는 길</p>
</li>
<li><p>39보다 크고, 83보다 작거나 같은 동네로 가는 길</p>
</li>
<li><p>83보다 큰 동네로 가는 길</p>
</li>
<li><p>내가 찾는 57은 39와 83 사이에 있겠네 그럼 가운데 길로 가자
(이렇게 판단한다.)</p>
</li>
</ul>
</li>
<li><p>가운데 길(브랜치 노드)로 내려왔더니 [46, 53, 57, 72]가 있고,
이번에는 숫자가 4개 있으니 길은 다섯 갈래로 나뉜다.
규칙은 &lt;= (작거나 같으면) 왼쪽 자식으로 이동이기에
이정표의 숫자들을 하나씩 비교해 본다.</p>
<ul>
<li><p>46보다 큰가? Yes</p>
</li>
<li><p>53보다 큰가? Yes</p>
</li>
<li><p>57보다 작거나 같은가? Yes! (정확히 57과 같다.)</p>
</li>
<li><p>57를 찾았네 그럼 규칙에 따라 57 바로 밑에 있는 왼쪽 자식 길로
이동하자! 하면서 리프 노드 쪽으로 내려간다.</p>
</li>
</ul>
</li>
<li><p>최종 목적지인 리프 노드에 도착했고, 여기에는 57이란 값이 들어있다.</p>
</li>
<li><p>리프 노드에 적힌 57은 단순한 숫자가 아닌 진짜 회원 정보 or 게시글 데이터가
디스크 어딘가에 저장되어 있는지 알려주는 데이터 포인터랑 연결되어 있다.
그래서 57이 가리키는 실제 데이터를 디스크에서 뽑아와서 사용자에게 전달한다.</p>
</li>
<li><p>만약 이런 B-트리 구조가 없다면 데이터가 무작위로 100만 개 있다고 가정하면
57 찾기 위해서 앞에서부터 100만 번을 다 뒤져야 한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 운영체제와 컴퓨터]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%99%80-%EC%BB%B4%ED%93%A8%ED%84%B0</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%99%80-%EC%BB%B4%ED%93%A8%ED%84%B0</guid>
            <pubDate>Sat, 16 May 2026 00:56:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>하드웨어 인터럽트</strong></p>
</blockquote>
<ul>
<li><p>바로 잡을 부분은 설명에 운영체제에 시스템 콜을 요청한다는 내용이 있는데
하드웨어 인터럽트는 외부 장치(키보드, 마우스)가 
CPU에 직접 신호를 보내는 것이고,
CPU는 이 신호를 받아 <strong>인터럽트 벡터 테이블</strong>이란 것을 참조해서</p>
</li>
<li><p><em>ISR(인터럽트 처리 루틴)*</em>을 실행한다.</p>
<p> 시스템 콜은 하드웨어가 아닌 소프트웨어(응용 프로그램)가 
운영체제에 서비스를 요청할 때 사용하는 방식이다.</p>
</li>
<li><p><strong>인터럽트 라인</strong>: CPU에 연결된 &quot;비상벨 울려주는 선 (물리적인 통로)&quot;</p>
<ul>
<li>키보드를 누르면 -&gt; 키보드가 이 선을 통해 CPU에 신호를 보낸다.</li>
</ul>
</li>
<li><p><strong>인터럽트 벡터 테이블</strong>: 비상 연락처 목록</p>
</li>
</ul>
<table>
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><span style="color:red">신호 번호 1번</span> → 키보드 처리 코드 위치</td>
</tr>
<tr>
<td><span style="color:blue">신호 번호 2번</span> → 타이머 처리 코드 위치</td>
</tr>
<tr>
<td><span style="color:green">신호 번호 3번</span> → 마우스 처리 코드 위치</td>
</tr>
</tbody></table>
<ul>
<li>CPU가 인터럽트 신호를 받으면 이 목록을 펼쳐서
&quot;이 신호는 어디서 처리하지?&quot;를 찾는다.</li>
</ul>
<ul>
<li><p><strong>ISR (인터럽트 처리 루틴)</strong>: 실제로 처리하는 코드</p>
<ul>
<li>벡터 테이블이 가리키는 주소에 있는 실제 처리 코드.
키보드 ISR이라면 -&gt; 어떤 키가 눌렸는지 버퍼에서 읽어와라 같은 동작 수행</li>
</ul>
</li>
</ul>
<p><strong>순서 흐름</strong></p>
<pre><code>IO 장치에서 신호 발생
    ↓
인터럽트 라인을 통해 CPU에 전달
    ↓
CPU가 현재 실행 중이던 명령을 중단, 상태 저장
    ↓
인터럽트 벡터 테이블에서 해당 ISR 주소 조회
    ↓
ISR 실행 (OS 커널 코드) → 디바이스 컨트롤러의 로컬 버퍼 접근
    ↓
처리 완료 후 원래 실행 지점으로 복귀

디바이스 컨트롤러는 외부 장치와 CPU 사이의 통역사이다.
키보드는 키가 눌릴 때 발생하는 미세한 전기적 변화를 감지하는 반면

CPU는 매우 정교하게 정해진 클럭 주기에 맞춰서 고속으로
디지털 신호를 주고 받기에 CPU 내부 회로를 수만 가지 주변기기의
전기적 규격에 모두 맞게 설계하는 것은 물리적으로 불가능하며 

효율성도 극도로 떨어지기에
디바이스 컨트롤러가 외부 장치의 고유한 전기 신호를 받아 디지털 데이터로 변환해서
CPU가 이해할 수 있는 공통 규격으로 맞춰준다.</code></pre><blockquote>
<p><strong>캐시 추가 설명</strong></p>
</blockquote>
<pre><code>CPU Core (ALU): 실제 연산을 수행하는 곳 (가장 빠름)

레지스터: CPU 내부의 아주 작은 임시 저장소 (연산에 즉각 사용)

L1/L2/L3 Cache: CPU와 메모리 사이의 속도 차이를 줄이는 완충 지대

Main Memory (RAM): 현재 실행 중인 프로그램이 올라가는 곳

Storage (SSD/HDD): 데이터가 영구 저장되는 곳 (가장 느림)
</code></pre><p><strong>메모리와 CPU 사이에 레지스터 계층을 둬서 속도 차이를 해결한다?</strong></p>
<ul>
<li><p>CPU는 데이터를 메모리(RAM)에서 직접 가져와 연산하려 한다.</p>
</li>
<li><p>메모리는 CPU의 연산 속도보다 수백 배 느리다.
CPU가 메모리에서 데이터를 가져오는 동안 CPU는 손가락 빨며 기다려야 한다.</p>
</li>
<li><p><strong>해결</strong>: CPU 내부에 가장 빠른 저장소(레지스터)를 만든다.
일단 데이터를 메모리에서 레지스터로 옮겨 놓으면 
CPU는 레지스터에 있는 데이터를 가지고 자기 속도에 맞춰 
초고속으로 연산할 수 있다.</p>
</li>
<li><p>캐싱 계층이라는 것은 절대적인 어떤 개념보다는 관계의 이름이다.
캐싱이라는게 말 그대로 느린 장치에 있는 데이터 중 자주 쓰는 것만
추려서 빠른 장치에 복사해두고, 사용하는 행위인데</p>
<p>주기억 장치가 보조기억장치의 캐싱 계층이라 하는 것도
만약에 게임을 실행하거나 인텔리제이 킬 때 로딩이 돌아가는데
이것은 <strong>느린 SSD에 있는 데이터를 빠른 RAM(메모리)로 복사</strong>하는 과정이고,</p>
<p>CPU는 SSD에 직접 접근하지 않고도 RAM에 올라온 데이터를 읽는다.
이때 <strong>RAM은 SSD를 위한 캐시 역할</strong>을 하는 것이다.</p>
</li>
</ul>
<pre><code>문제점: CPU 연산 속도에 비해 메인 메모리(RAM)의 데이터 전송 속도가 너무 느림 (CPU의 병목 현상 발생).

해결책: 두 계층 사이의 극심한 속도 차이를 완화하기 위해 
고속 하드웨어인 L1, L2, L3 캐시 메모리를 배치함.

캐싱 계층은 특정 하드웨어 하나를 지칭하는 말이 아니라, 두 계층 간의 상대적인 관계를 부르는 명칭이다.

하위 계층(느린 장치)의 데이터 중 자주 사용하는 것만 
추려서 상위 계층(빠른 장치)에 미리 복사해 두고 완충 작용을 하도록 
만든 모든 중간 단계를 뜻함. (예: RAM은 SSD의 데이터를 미리 올려두는 캐시 역할을 수행함)</code></pre><blockquote>
<p><strong>스와핑 보충 설명</strong></p>
</blockquote>
<ul>
<li>RAM이 부족할 때, 디스크를 RAM처럼 임시로 활용하는 기법</li>
</ul>
<table>
<thead>
<tr>
<th align="left">용어</th>
<th align="left">실제 의미</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>가상 메모리</strong></td>
<td align="left">OS가 프로세스에게 &quot;RAM이 무한한 것처럼&quot; 착각시키는 주소 공간</td>
</tr>
<tr>
<td align="left"><strong>RAM (물리 메모리)</strong></td>
<td align="left">실제로 CPU가 직접 접근 가능한 메모리</td>
</tr>
<tr>
<td align="left"><strong>페이지</strong></td>
<td align="left">가상 메모리를 일정 크기로 나눈 단위</td>
</tr>
<tr>
<td align="left"><strong>프레임</strong></td>
<td align="left">RAM을 페이지와 같은 크기로 나눈 단위</td>
</tr>
<tr>
<td align="left"><strong>페이지 테이블</strong></td>
<td align="left">&quot;가상 주소 → 실제 RAM 주소&quot; 매핑 정보를 담은 표</td>
</tr>
<tr>
<td align="left"><strong>디스크 (스왑 영역)</strong></td>
<td align="left">RAM 대신 임시로 페이지를 보관하는 저장소</td>
</tr>
</tbody></table>
<pre><code>CPU가 가상 주소 접근
        │
        ▼
페이지 테이블 확인
        │
   ┌────┴────┐
RAM에 있음  RAM에 없음
   │            │
정상 접근  페이지 폴트 발생
                │
                ▼
          OS에 트랩(trap) 전달 (운영체제에 제어권 넘어감.)
                │
                ▼
         RAM에 빈 프레임 있음?
          ┌─────┴─────┐
         있음         없음
          │            │
          │     페이지 교체 알고리즘으로
          │     희생 페이지 선택 → 디스크의 스왑 영역으로 쫒아냄. (해당 램 프레임 비움.)
          │            │
          └─────┬──────┘
                ▼
         이제 비어 공간이 확보된 RAM 프레임에, 
         처음 요청했던 데이터를 디스크에서 읽어와 적재
                │
                ▼
          페이지 테이블 갱신 
    (페이지가 위치하게 된 실제 RAM 프레임 번호와 가상 주소 매핑, 유효 비트를 1로 변경)
                │
                ▼
       중단됐던 명령어 재실행
    (운영체제가 CPU에 제어권 돌려주면 트랩 유발했던 명령어를 다시 처음부터 실행,
       이때 RAM에 데이터가 존재하므로 정상 접근 완료.)
</code></pre><p><strong>왜 트랩이 발생할까?</strong></p>
<ul>
<li>CPU는 RAM에 없는 주소에 직접 접근 불가.</li>
<li>하드웨어(MMU)가 이를 감지하고, OS에 제어권을 넘기기 위해 발생.</li>
</ul>
<p><strong>왜 OS가 디스크에서 &quot;사용하지 않는 프레임&quot;을 찾나?</strong></p>
<ul>
<li><p>정확히는 디스크가 아니라 <strong>RAM</strong>에서 비울 프레임을 찾는 것이다.</p>
</li>
<li><p>빈 프레임이 없으면 기존 페이지를 디스크로 내보내야 하기 때문이다.</p>
</li>
<li><p>책에 나온 &quot;실제 디스크로부터 사용하지 않은 프레임을 찾는다&quot;는
설명은 조금 수정 되어야 할 것 같다.
정확한 순서는 RAM에서 교체할 프레임을 선택 -&gt; 해당 내용 디스크에 저장</p>
</li>
<li><blockquote>
<p>필요한 페이지를 디스크에스 RAM으로 로드.</p>
</blockquote>
</li>
<li><p>페이지 교체 알고리즘 내용도 조금 추가? 수정? 되어야 할 부분이
프레임을 실제 메모리에 가져와서 페이지 교체 알고리즘 기반으로 
특정 페이지와 교환한다 보다는</p>
<p>데이터를 디스크에서 가져오기 전에 RAM에 빈 공간 있는지 먼저 확인하고,
빈 공간이 없을 때만 페이지 교체 알고리즘을 실행하여 공간 확보한 뒤
디스크에서 데이터를 가져오는 것이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>CPU 스케줄링 알고리즘</strong></p>
</blockquote>
<pre><code>CPU 스케줄링 알고리즘
│
├── 비선점형 (Non-preemptive)
│   ├── FCFS
│   ├── SJF
│   └── 우선순위 (Priority) ← 선점/비선점 둘 다 가능
│
└── 선점형 (Preemptive)
    ├── Round Robin (RR)
    ├── SRTF
    └── 다단계 큐 (Multilevel Queue)
</code></pre><table>
<thead>
<tr>
<th align="left">용어</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="left">준비 큐 (Ready Queue)</td>
<td align="left">CPU 할당을 기다리는 프로세스 대기열</td>
</tr>
<tr>
<td align="left">선점 (Preemptive)</td>
<td align="left">OS가 강제로 CPU 회수 가능</td>
</tr>
<tr>
<td align="left">비선점 (Non-preemptive)</td>
<td align="left">프로세스가 스스로 CPU 반납할 때까지 대기</td>
</tr>
<tr>
<td align="left">컨텍스트 스위칭 (Context Switching)</td>
<td align="left">CPU가 다른 프로세스로 전환할 때 현재 상태 저장/복원</td>
</tr>
<tr>
<td align="left">기아 현상 (Starvation)</td>
<td align="left">특정 프로세스가 계속 CPU를 못 받는 현상 책 원문에서의 긴시간 가진 프로세스가 실행되지 않는다 현상 부분</td>
</tr>
<tr>
<td align="left">에이징 (Aging)</td>
<td align="left">오래 기다린 프로세스의 우선순위를 점진적으로 높이는 기법</td>
</tr>
</tbody></table>
<p><strong>스케줄링 목표</strong></p>
<pre><code>1. CPU 이용률 ↑  → CPU가 놀지 않게
2. 처리량 ↑      → 단위 시간당 처리 프로세스 수 많게
3. 대기 시간 ↓   → 준비 큐에서 기다리는 시간 짧게
4. 응답 시간 ↓   → 요청 후 첫 응답까지 시간 짧게</code></pre><blockquote>
<p><strong>비선점형 알고리즘</strong></p>
</blockquote>
<p><strong>FCFS (First Come First Served)</strong></p>
<pre><code>도착 순서대로 실행 (줄 서기)

[P1(24ms)] → [P2(3ms)] → [P3(3ms)]
 ↑ P1이 먼저 왔으면 24ms 동안 P2, P3는 무조건 대기</code></pre><ul>
<li><p>FCFS는 오직 도착한 순서대로만 CPU를 할당하는 방식이다.</p>
</li>
<li><p>가장 큰 특징은 긴 프로세스가 CPU를 잡으면 뒤에 있는 
짧은 프로세스들이 무한정 대기하게 된다는 점이다.</p>
</li>
</ul>
<p><strong>SJF (Shortest Job First)</strong></p>
<pre><code>실행시간이 가장 짧은 프로세스 먼저

[P1(24ms)] [P2(3ms)] [P3(3ms)] 가 있다면
→ [P2(3ms)] → [P3(3ms)] → [P1(24ms)] 순 실행</code></pre><ul>
<li><p>평균 대기시간 이론상 최소.</p>
</li>
<li><p><strong>기아 현상</strong>: 긴 프로세스는 영원히 실행 안 될수도 있음. </p>
</li>
<li><p><strong>실행 시간 예측 불가</strong>: 실제로는 과거 실행 시간 기반으로 지수 평균으로 추정.</p>
</li>
<li><p>이론상 최소인 이유는 짧은 작업을 먼저 빼줄수록 뒤 프로세스들의
대기시간 합이 줄어들기 때문이다. (수학적으로 증명됨)</p>
</li>
</ul>
<p><strong>우선순위 스케줄링 (Priority Scheduling)</strong></p>
<pre><code>각 프로세스에 우선순위 번호 부여 → 높은 우선순위 먼저

SJF도 사실 &quot;실행시간이 짧을수록 우선순위 높음&quot;으로 볼 수 있음
→ SJF는 우선순위 스케줄링의 특수한 케이스</code></pre><ul>
<li><p>기아 현상을 해결하기 위해 <strong>에이징</strong> 방법 사용 </p>
</li>
<li><blockquote>
<p>대기 시간이 길어질수록 우선순위를 점진적으로 높임.</p>
</blockquote>
</li>
<li><p>우선순위 스케줄링은 <strong>선점형/비선점형 둘 다 구현 가능</strong></p>
<ul>
<li><p><strong>비선점형</strong>: 더 높은 우선순위가 와도 현재 프로세스 완료 후 전환</p>
</li>
<li><p><strong>선점형</strong>: 더 높은 우선순위 도착 시 즉시 CPU 회수</p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>선점형 알고리즘</strong></p>
</blockquote>
<p><strong>라운드 로빈 (Round Robin)</strong></p>
<pre><code>Time Quantum(q) = 4ms, 프로세스 3개(N=3) 예시

[P1][P2][P3][P1][P2][P3]...
 4ms 4ms 4ms  4ms ...

자기 차례까지 최대 대기시간 = (N-1) × q = 2 × 4 = 8ms</code></pre><ul>
<li><p><strong>할당 시간(q) 크기의 영향</strong></p>
<pre><code>q가 너무 크다 → 사실상 FCFS와 동일
q가 너무 작다 → 컨텍스트 스위칭 폭발 → 오버헤드 증가
              (실제 작업보다 전환 비용이 더 커짐)</code></pre></li>
<li><p>응답 시간이 짧다. <strong>(현대 OS 기본)</strong></p>
</li>
<li><p>로드밸런서 트래픽 분산에도 활용.</p>
</li>
<li><p>전체 작업 완 시간은 길어질 수도 있다.</p>
</li>
</ul>
<p><strong>SRF (Shortest Remaining Time First)</strong></p>
<pre><code>SJF의 선점형 버전

실행 중 더 짧은 프로세스 도착 시 → 즉시 CPU 회수

P1(7ms 남음) 실행 중 → P2(4ms)가 도착
→ P1 중단, P2 먼저 실행 → P2 완료 후 P1 재개</code></pre><ul>
<li><p>이론상 평균 대기시간 최소
(컨텍스트 스위칭 오버헤드를 배제하고, 평균 시간은 SRF가 빠르다.)
(SJF가 최적이라고, 불리는 조건은 모든 프로세스가 동시에 도착했을 뿐인데
실제로 프로세스들이 각기 다른 시간이 도착하는 실제 환경에서는
SRF가 평균 대기 시간을 더 극단적으로 줄인다.)</p>
</li>
<li><p>기아 현상 (SJF보다 심각할 수 있음)</p>
</li>
<li><p>잦은 컨텍스트 스위칭</p>
</li>
</ul>
<p><strong>다단계 큐 (Multilevel Queue)</strong></p>
<pre><code>우선순위별로 큐를 분리

큐1 (최고 우선순위) : 시스템 프로세스 → FCFS
큐2 (중간 우선순위) : 대화형 프로세스 → RR
큐3 (낮은 우선순위) : 배치 프로세스  → FCFS
         ↓
높은 큐가 비어야만 낮은 큐 실행
</code></pre><ul>
<li><p>스케줄링 오버헤드 낮음 (큐마다 다른 알고리즘 적용)</p>
</li>
<li><p>큐 간 이동 불가 -&gt; 유연성 부족, 기아 현상 가능.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 네트워크]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Sat, 09 May 2026 11:47:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>스푸핑이 무엇인가? (버스 토폴리지 쪽)</strong></p>
</blockquote>
<p><strong>네트워크에서 스푸핑은 한마디로 &quot;나 여기 아닌데, 여기인 척 속이는 행위&quot;</strong></p>
<p><strong>원래 스위치(Switch)는 어떻게 작동하나?</strong></p>
<p>정상적인 LAN 환경에서는 <strong>스위치</strong>라는 장비가 똑똑한 우체국 역할을 한다.</p>
<ul>
<li><p><strong>원칙:</strong> &quot;A가 B에게 보내는 패킷(택배)은 오직 B에게만 전달한다.&quot;</p>
</li>
<li><p><strong>방법:</strong> 스위치는 각 포트에 어떤 주소(MAC 주소)를 가진 컴퓨터가 
연결되어 있는지 표로 기록해 둔다. 
그래서 엉뚱한 곳으로 택배가 가지 않도록 막아준다.</p>
</li>
</ul>
<p><strong>스푸핑의 원리: &quot;내가 그 사람입니다&quot;</strong></p>
<p>스푸핑은 이 &#39;우체국(스위치)&#39;을 속여서 길을 잘못 들게 만드는 기술이다.</p>
<ul>
<li><p><strong>상황:</strong> 해커(C)가 A와 B 사이의 대화를 훔쳐보고 싶어 한다.</p>
</li>
<li><p><strong>공격:</strong> 해커(C)는 스위치에게 계속해서 가짜 정보를 보낸다. 
&quot;내가 바로 B야! 앞으로 B한테 가는 택배는 다 나(C)한테 줘!&quot;라고 속이는 것이다.</p>
</li>
<li><p><strong>결과:</strong> 스위치는 속아서 A가 B에게 보내는 패킷을 
해커(C)가 있는 포트로 던져준다.</p>
</li>
</ul>
<p><strong>&quot;기능을 마비시키거나 속인다&quot;는 의미</strong></p>
<ul>
<li><p><strong>속이기 (ARP Spoofing):</strong> 위에서 설명한 것처럼
특정 호스트인 척 위장해서 패킷을 가로채는 것이다.</p>
</li>
<li><p><strong>마비시키기 (Switch Jamming):</strong> 스위치의 기억 용량은 한계가 있다. 
해커가 수만 개의 가짜 주소를 한꺼번에 보내서 
스위치의 &#39;주소록&#39;을 꽉 채워버리면
스위치는 누가 누군지 구분하지 못하는 바보가 된다. 
이때 스위치는 &quot;모르겠다, 그냥 모든 포트에 다 뿌려버리자!&quot;라고
작동 방식을 바꿔버리는데(Flooding) 이때를 노려 패킷을 훔쳐보는 것이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>링형 토폴리지</strong></p>
</blockquote>
<p><strong>노드 수가 늘어나도 네트워크 손실이 적은 이유는?</strong></p>
<ul>
<li><p>일반적으로 선이 길어지거나 노드가 많아지면 신호가 약해지는데
링형에 연결된 노드는 단순히 데이터를 옮기는 것 뿐 아니라
신호를 증폭하는 리피터 역할을 수행한다.</p>
</li>
<li><p>특정 노드가 데이터를 받으면 그 신호를 다시 깨끗하고, 
강하게 재생성하여 다음 노드로 넘겨준다.</p>
</li>
<li><p>노드를 거칠 때마다 신호가 보다 강한 데이터로 탈바꿈하여 나오니
전체적인 신호력이 약해지거나 데이터 손실이 다른 방식에 비해 매우 적다.</p>
</li>
</ul>
<p><strong>충돌 발생 가능성이 적은 이유는?</strong></p>
<ul>
<li><p>충돌이 적은 핵심 근거는 토큰 패싱(Token Passing)이라는 제어 방식에 있다.</p>
</li>
<li><p>네트워크상에 토큰이라는 어떤 노드로 데이터를 보낼 수 있는 권한인 단 하나의 발언권이 존재한다.</p>
</li>
<li><p>데이터를 보내고 싶은 노드는 반드시 이 토큰을 획득해야만 데이터 전송이 가능하다.</p>
</li>
<li><p>한 번에 오직 한 노드만 데이터를 보낼 수 있는 구조이기 때문에
여러 노드가 동시에 데이터를 던져서 발생하는 충돌이 원천적으로 방지된다.</p>
</li>
</ul>
<p><strong>링형은 토큰 패싱 때문에 그럼 한 번에 오직 한 노드만 데이터 보내는데 느리지 않나?</strong></p>
<ul>
<li><p>일단 데이터가 적을 때는 비효율적이다 전송할 데이터가 하나여도 토큰이 링을 한 바퀴 쭉 돌아
내 차례가 올 때까지 무조건 기다려야 하는데 이를 <strong>토큰 대기 시간</strong>이라고 하고,
이 시점에서는 다른 방식보다 더 속도가 느리게 느껴질 수 있다.</p>
</li>
<li><p>데이터가 많을 때는 오히려 효율적인데 여러 노드가 동시에 데이터 보내려 할 때,
버스 토폴리지처럼 충돌이 발생하면 데이터를 다 폐기하고, 다시 보내는 과정에서
엄청난 자원이 소모되는데 반면 링형은 대기 시간은 일정하지만 충돌이 0%에 가깝기에
전체적인 데이터 처리량은 훨씬 안정적이고, 높게 유지된다.</p>
</li>
</ul>
<h3 id="회선-장애가-전체에-영향을-끼치는-이유는"><strong>회선 장애가 전체에 영향을 끼치는 이유는?</strong></h3>
<ul>
<li><p>단방향의 직렬 구조이기 때문에 링형은 데이터가 순차적으로 전달된다.</p>
</li>
<li><p>데이터가 A → B → C → D 순으로 흐른다고 가정할 때, B 노드가 고장 나거나
A와 B 사이의 선이 끊기면 데이터는 C나 D로 갈 수 있는 경로가 완전히 사라진다.</p>
</li>
<li><p>각 노드가 양 옆 노드와 체인처럼 연결되어 있기에 중간 고리만 하나 끊어져도
정상적인 루프가 즉 데이터 흐름이 깨지게 되는 것이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>TCP의 가상회선 패킷 교환 방식</strong></p>
</blockquote>
<p><strong>해당 방식을 왜 사용하나?</strong></p>
<ul>
<li><p>패킷마다 각자 다른 길로 가버리면 도착 순서가 뒤죽박죽 될 수 있다.
가상회선은 같은 길로만 가기에 데이터의 순서가 바뀔 위험이 적다.</p>
</li>
<li><p>가상 경로를 미리 확보하고, 전송하기 때문에 데이터 손실이 발생했을 때
이를 감지하고, 재전송하기가 훨씬 수월하다.</p>
</li>
</ul>
<p><strong>가상회선이란?</strong></p>
<ul>
<li><p><strong>Virtual (가상):</strong> 실제로 물리적인 전용선이 깔리는 것은 아니지만</p>
</li>
<li><p><strong>Circuit (회선)</strong>: 마치 전용선을 사용하는 것처럼 데이터를 
순서대로 안정적으로 보내는 통로를 의미한다.</p>
</li>
</ul>
<p><strong>동작 과정의 3단계</strong></p>
<ul>
<li><p>TCP는 데이터를 보내기 전과 후에 반드시 약속된 절차를 거친다.</p>
</li>
<li><p><strong>연결 설정</strong></p>
<ul>
<li>데이터 보내기 전에 송신, 수신측이 서로 인사를 나누며 
데이터를 주고받을 가상 경로를 미리 정한다. (3-Way Handshake가 이 과정에 해당)</li>
</ul>
</li>
<li><p><strong>데이터 전송</strong></p>
<ul>
<li><p>한번 경로가 정해지면, 모든 패킷은 반드시 처음에 정해진 순서대로 이 길을 따라간다.
각 패킷은 목적지 주소 대신에 가상회선 식별 번호를 달고 이동한다.
덕분에 수신측에서는 패킷이 섞이지 않고, 순서대로 도착하는 것을 보장받는다.</p>
<p>가상회선 식별 번호가 사용되는 부분은
만약 전송 과정에 문제가 발생해서 1,2,4 패킷이 먼저 도착했어도 (1,2,4가 식별 번호)
수신측에서 아직 3번이 안 왔다고 판단하고, 4번을 잠시 대기소(Buffer)에 보관한다.
3번이 도착하면 그때서야 순서대로 합쳐서 상위 애플리케이션에 전달한다.</p>
</li>
</ul>
</li>
<li><p><strong>연결 해제</strong></p>
<ul>
<li>데이터 전송이 끝나면 예약했던 가상 경로와 라우터의 자원을 모두 해제한다.
(4-Way Handshake 과정)</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>BSS, ESS</strong></p>
</blockquote>
<p><strong>와이파이는 알겠는데 BSS랑 ESS는 무엇인가?</strong></p>
<ul>
<li><p><strong>BSS (Basic Service Set)</strong></p>
<ul>
<li>BSS는 무선 LAN의 가장 기본적인 최소 단위이다.</li>
<li>하나의 무선 접속 장치(AP)와 (보통 공유기) 거기에 연결된 스마트폰/노트북들의 집합</li>
<li>무선 신호는 물리적으로 도달 거리의 한계가 있다.
따라서 하나의 공유기가 책임질 수 있는 특정 구역이 생기는데
이 구역 내의 통신 묶음을 BSS라고 부른다.</li>
<li>각 BSS는 공유기의 고유 주소(MAC 주소)인 BSSID로 서로를 구분한다.
(공유기 설정에서 보는 복잡한 문자열 주소가 이것이다.)</li>
</ul>
</li>
<li><p><strong>ESS (Extended Service Set)</strong></p>
<ul>
<li>ESS는 여러 개의 BSS를 하나로 묶어 더 넓은 영역을 커버하도록 확장한 개념이다.</li>
<li>여러 개의 BSS(공유기들)가 유선망(DS)으로 서로 연결된 집합이다.</li>
<li>예로 카페 한 층은 공유기 하나(BSS)로 커버 가능하지만
거대한 대학교 캠퍼스나 회사 빌딩은 공유기 하나로 불가능하다.
이때 여러 공유기를 설치하되, 사용자가 보기에는 하나의 네트워크처럼 보이게 만들어야 한다.</li>
<li>ESS 환경에서는 내가 1번 공유기 영역에서 2번 공유기 영역으로 이동해도 
와이파이가 끊기지 않고, 자동으로 바뀌는데 이를 <strong>로밍(Roaming)</strong>이라고 부른다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>이더넷 프레임</strong></p>
</blockquote>
<p><strong>이더넷 프레임이 무엇이고, 왜 쓰이는가?</strong></p>
<ul>
<li><p>데이터 링크 계층에서 데이터를 전송하기 위해 정의된 데이터의 기본 단위이다.
네트워크상의 장치들이 서로 데이터를 주고받을 때
실제 데이터(Payload)에 주소 정보와 오류 제어 정보 등을 붙여서 포장한 상태이다.</p>
</li>
<li><p>케이블로 연결된 여러 장비 중 누구에게 데이터를 전달할지 결정해야 한다. (MAC 주소 사용)</p>
</li>
<li><p>연속적인 전기 신호 흐름 속에서 어디가 데이터 시작이고, 끝인지 명확히 해야 하기에 사용한다.</p>
</li>
<li><p>전송 중 신호 간섭 등으로 데이터가 변형되었을 때 받는 쪽에서 이를 인지하고
폐기하기 위해서 <strong>FCS</strong>가 필요하다.</p>
</li>
</ul>
<p><strong>이더넷 프레임의 구조</strong></p>
<ul>
<li><p><strong>헤더</strong></p>
<ul>
<li><p><strong>Preamble &amp; SFD (8 bytes)</strong>: 수신 측에 데이터가 시작된다고 알리는 신호이다.</p>
</li>
<li><p><strong>수신측 MAC 주소 (6 bytes)</strong>: 데이터를 받을 대상 장비의 고유 식별 주소</p>
</li>
<li><p><strong>송신측 MAC 주소 (6 bytes)</strong>: 데이터를 보내는 장비의 고유 식별 주소</p>
</li>
<li><p><strong>EtherType (2 bytes):</strong> 상위 계층(네트워크 계층)의 어떤 프로토콜이 들어있는지 적혀있다.
(예: IPv4는 <code>0x0800</code>, IPv6는 <code>0x86DD</code>)</p>
</li>
</ul>
</li>
<li><p><strong>페이로드</strong></p>
<ul>
<li><p><strong>상위 데이터 (46 ~ 1500 bytes)</strong>: 실제 전송하려는 데이터이다. (보통 IP 패킷)</p>
</li>
<li><p>최소 46바이트인 이유는 이더넷의 충돌 감지 기술(CSMA/CD)이 정상 작동하기 위해서
최소한의 길이를 보장하기 위함인데 데이터가 작으면 의미 없는 패딩 값을 채워 넣는다.</p>
</li>
</ul>
</li>
<li><p><strong>트레일러</strong></p>
<ul>
<li><strong>FCS (Frame Check Sequence, 4 bytes)</strong>: 데이터 전송 과정에서 오류가 발생했는지
확인하기 위한 체크섬(CRC32) 값이다.
(책에서는 CRC 라고 나와있는데 엄밀히 말하면 FCS가 역할 이름이고,
CRC는 FCS의 역할을 수행하는 방식의 일부이기에 FCS로 외우는 것이 적당할 듯 싶다.)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 디자인 패턴]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Fri, 01 May 2026 18:38:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="이터레이터-패턴">이터레이터 패턴</h3>
</blockquote>
<ul>
<li><p>컬렉션(List, Set, Map 등)은 데이터를 저장하는 방식이 모두 다르다. 
이터레이터 패턴은 데이터를 어떻게 저장하는지 몰라도(캡슐화),
그 안의 요소들을 하나씩 꺼내는 방법만 표준화하는 패턴이다.</p>
</li>
<li><p>사용하는 이유는 자료구조가 변경되어도(예: ArrayList -&gt; LinkedList) 
순회 로직 자체를 수정할 필요가 없다.</p>
</li>
<li><p>컬렉션 내부의 복잡한 구현(트리 구조, 해시 테이블 등)을 
외부에 노출하지 않고도 안전하게 요소를 전달할 수 있다.</p>
</li>
</ul>
<p><strong>Java 17 예시</strong></p>
<ul>
<li>자바에서는 이미 <code>java.util.Iterator</code> 인터페이스를 통해 
이 패턴을 내장하고 있다.</li>
</ul>
<pre><code class="language-java">import java.util.Iterator;
import java.util.List;

public class IteratorExample {
    public static void main(String[] args) {
        // ArrayList 형태로 저장된다.
        List&lt;String&gt; fruits = List.of(&quot;Apple&quot;, &quot;Banana&quot;, &quot;Orange&quot;);

        // 표준 이터레이터 사용 (자료구조를 몰라도 순회 가능)
        Iterator&lt;String&gt; iterator = fruits.iterator();

        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.println(&quot;과일: &quot; + fruit);
        }

        for (String fruit : fruits) {
            System.out.println(&quot;과일은: &quot; + fruit);
        }
    }
}
</code></pre>
<hr>
<blockquote>
<h3 id="옵저버-패턴">옵저버 패턴</h3>
</blockquote>
<ul>
<li><p>어떤 객체의 상태가 변할 때 
그와 연관된 다른 객체들에게 자동으로 알림을 보내는 
1:N 관계의 디자인 패턴이다.</p>
</li>
<li><p>사용하는 이유는 주체(Subject)와 관찰자(Observer)가 
서로를 구체적으로 알 필요가 없다(느슨한 결합).</p>
</li>
<li><p>상태 변화를 실시간으로 반영해야 하는 
이벤트 기반 시스템(예: 주식 차트, 푸시 알림)에 적합하다.</p>
</li>
</ul>
<p><strong>Java 17 예시</strong></p>
<ul>
<li>자바의 <code>Observable</code>는 Deprecated(권장되지 않음) 되었기에 
직접 인터페이스를 정의하거나 
Spring의 <code>ApplicationEvent</code> 등을 사용하는 것이 현대적이다.</li>
</ul>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.List;

// 옵저버 인터페이스
interface Observer {
    void update(String message);
}

// 주체 (Subject)
class NewsChannel {
    private final List&lt;Observer&gt; observers = new ArrayList&lt;&gt;();

    public void addObserver(Observer o) { observers.add(o); }

    public void notifyObservers(String news) {
        for (Observer o : observers) {
            o.update(news);
        }
    }
}

// 실행 예시
public class ObserverExample {
    public static void main(String[] args) {
        NewsChannel channel = new NewsChannel();

        // 익명 클래스 또는 람다를 사용한 옵저버 등록
        channel.addObserver(msg -&gt; System.out.println(&quot;구독자 A 수신: &quot; + msg));
        channel.addObserver(msg -&gt; System.out.println(&quot;구독자 B 수신: &quot; + msg));

        channel.notifyObservers(&quot;새로운 기능 출시&quot;);
    }
}
</code></pre>
<hr>
<blockquote>
<h3 id="노출-모듈-패턴">노출 모듈 패턴</h3>
</blockquote>
<ul>
<li>이 패턴의 본질은 &quot;우리 집 주방(내부 로직)은 보여주기 싫고,
음식(결과물)만 창구로 내보내겠다&quot;는 선언이다.</li>
</ul>
<ul>
<li><p><strong>자바스크립트의 태생적 한계</strong>: 예전 자바스크립트는 모든 코드가 
전역(Global)에 노출 되었기에 내가 만든 <code>count</code> 변수가 
남이 만든 <code>count</code> 변수와 충돌해서 값이 멋대로 바뀌는 대참사가 빈번했다.</p>
</li>
<li><p><strong>해결책</strong>: 함수 안에 변수를 가두고(클로저) 
밖에서 쓰고 싶은 것만 객체에 담아 노출 시키기로 약속한 것이다.</p>
</li>
<li><p>자바에서는 이 철학이 클래스의 접근 제어자로 이미 녹아들어 있다.</p>
<ul>
<li><p><strong>비공개(Private)</strong>: &quot;내부 부품&quot;. 
외부에서 건드리면 고장 날 수 있는 로직이나 데이터이다.</p>
</li>
<li><p><strong>공개(Public/Interface)</strong>: &quot;리모컨 버튼&quot;. 사용자가 눌러야 할 최소한의 기능이다.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class VendingMachine {
    // 숨겨진 것 (Private)
    private int moneyInside = 0;

    // 내부 로직은 건드릴 수 없게 private으로 막아 놓음.
    private void verifyCoin(int coin) {
        System.out.println(coin + &quot;원이 유효한지 검사 중...&quot;);
    }

    // 노출된 것 (Public): 사용자가 볼 수 있는 &#39;버튼&#39;만 공개
    public void insertCoin(int coin) {
        verifyCoin(coin); // 내부 로직 호출
        this.moneyInside += coin;
        System.out.println(&quot;코인이 투입되었습니다.&quot;);
    }

    public String pushButton() {
        if (moneyInside &gt;= 500) {
            return &quot;콜라&quot;;
        }
        return &quot;잔액 부족&quot;;
    }
}
</code></pre>
<hr>
<blockquote>
<h3 id="mvvm-패턴">MVVM 패턴</h3>
</blockquote>
<ul>
<li><p><strong>Model (데이터와 비즈니스 로직)</strong></p>
<ul>
<li><p>애플리케이션에서 사용하는 실제 데이터와 그 데이터를 처리하는 로직이다. (DB, API 응답, Service 레이어 등)</p>
</li>
<li><p>UI가 어떻게 생겼는지 전혀 모른다. 
오직 &quot;데이터는 무엇인가?&quot;에만 집중한다.</p>
</li>
</ul>
</li>
<li><p><strong>View (사용자 인터페이스)</strong></p>
<ul>
<li><p>사용자에게 보여지는 화면(UI) 그 자체이다.</p>
</li>
<li><p>비즈니스 로직을 포함하지 않는다. 
오직 &quot;어떻게 보여줄 것인가?&quot;와 
&quot;사용자의 입력(클릭 등)을 어떻게 받을 것인가?&quot;만 담당한다.</p>
</li>
</ul>
</li>
<li><p><strong>ViewModel (상태 관리와 중재자)</strong></p>
<ul>
<li><p>View를 위한 Model이다. 
View에 표시될 데이터를 가공하고, View의 상태를 유지한다.</p>
</li>
<li><p>가장 중요한 점은 ViewModel은 View를 참조하지 않는다. 
즉, &quot;내 데이터를 누가 가져가는지&quot; 몰라도 된다. 
대신 View가 ViewModel의 데이터를 관찰(Observe)하고 있다가 
값이 변하면 스스로 화면을 갱신한다.</p>
</li>
</ul>
</li>
</ul>
<p><strong>왜 MVVM을 사용하는가?</strong></p>
<ul>
<li><p>기존 MVC 패턴에서는 컨트롤러가 
View와 Model 사이를 일일이 간섭해야 했다. 
하지만 MVVM에서는 View가 ViewModel의 데이터에 
딱 붙어(Data Binding) 있어서 
ViewModel의 데이터만 바뀌면 화면은 알아서 바뀐다. 
덕분에 UI 코드가 매우 단순해진다.</p>
</li>
<li><p>&quot;UI 없이도 로직을 테스트하기 위해서&quot; (테스트 용이성)
ViewModel은 View(화면)에 대한 코드가 한 줄도 없다. 
따라서 화면을 직접 띄우지 않고도 
&quot;데이터가 제대로 가공되는지&quot;, &quot;상태가 잘 변하는지&quot;를 
JUnit 같은 도구로 완벽하게 테스트할 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TCP와 UDP]]></title>
            <link>https://velog.io/@jun_k/TCP%EC%99%80-UDP</link>
            <guid>https://velog.io/@jun_k/TCP%EC%99%80-UDP</guid>
            <pubDate>Sun, 26 Apr 2026 09:08:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="tcp-transmission-control-protocol-신뢰가-제일이다">TCP (Transmission Control Protocol): &quot;신뢰가 제일이다&quot;</h3>
</blockquote>
<p>TCP는 데이터의 신뢰성을 최우선으로 설계된 프로토콜이다. 
이를 위해 데이터를 보내기 전 상대방과 연결을 설정하고, 
데이터가 잘 도착했는지 매번 확인한다.</p>
<p><strong>왜 신뢰할 수 있는가?</strong></p>
<ul>
<li><p><strong>연결 지향 (3-way Handshake)</strong>: 데이터를 보내기 전 
&quot;준비됐니?&quot; -&gt; &quot;응, 너는?&quot; -&gt; &quot;나도 준비됐어&quot;라는 
3단계 과정을 거쳐 연결을 확립한다.</p>
</li>
<li><p><strong>순서 보장</strong>: 데이터에 번호(Sequence Number)를 매겨서 보낼 때
받는 쪽에서 순서가 뒤섞여도 원래대로 조립할 수 있다.</p>
</li>
<li><p><strong>흐름 제어 &amp; 혼잡 제어</strong>: 받는 쪽의 처리 속도가 느리면 보내는 속도를 줄이고, 네트워크가 막히면 데이터 양을 조절한다. (데이터 폭주 방지)</p>
</li>
<li></li>
<li><p><em>재전송*</em>: 상대방이 데이터를 못 받았다고 하면(ACK를 못 받으면) 다시 보내준다.</p>
</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>확인 과정이 많기 때문에 UDP보다 느리고,
연결을 계속 유지해야 하므로 서버 자원을 더 많이 소모한다.</li>
</ul>
<hr>
<blockquote>
<h3 id="udp-user-datagram-protocol-빠른-게-장땡이다">UDP (User Datagram Protocol): &quot;빠른 게 장땡이다&quot;</h3>
</blockquote>
<ul>
<li>UDP는 복잡한 절차를 다 생략하고, 속도와 효율성에 올인한 프로토콜이며 &quot;보냈으니까 알아서 받아라&quot;라는 식의 비연결형 방식이다.</li>
</ul>
<p><strong>왜 빠른가?</strong></p>
<ul>
<li><p><strong>비연결형</strong>: 3-way handshake 같은 사전 작업이 없이 
그냥 목적지 주소 찍고 바로 전송한다.</p>
</li>
<li><p><strong>확인 안 함</strong>: 데이터가 도착했는지, 순서가 맞는지 확인하지 않는다.</p>
</li>
<li><p><strong>가벼운 헤더</strong>: TCP는 확인을 위해 헤더 정보가 많은데
UDP는 최소한의 정보만 담겨 있어서 오버헤드가 매우 적다.</p>
</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>데이터 유실 가능성이 있고, 도착 순서가 바뀔 수도 있다. (신뢰성 낮음)</li>
</ul>
<hr>
<blockquote>
<h3 id="언제-무엇을-써야-할까">언제 무엇을 써야 할까?</h3>
</blockquote>
<p><strong>TCP를 선택해야 하는 상황</strong></p>
<ul>
<li><p>&quot;단 1바이트의 오차도 허용할 수 없을 때&quot;</p>
</li>
<li><p>HTTP/HTTPS (웹 브라우징): 페이지 구성 요소 중 하나라도 빠지면 
화면이 깨지거나 보안 이슈가 생길 수 있음.</p>
</li>
<li><p>파일 전송 (FTP): 파일의 끝부분이 유실되면 파일 실행이 안 됨.</p>
</li>
<li><p>이메일 (SMTP): 내용이 일부 누락되면 의미 전달이 안 됨.</p>
</li>
</ul>
<p><strong>UDP를 선택해야 하는 상황</strong></p>
<ul>
<li><p>&quot;데이터가 조금 유실되어도 실시간성이 중요할 때&quot;</p>
</li>
<li><p><strong>실시간 스트리밍 (유튜브/치지직 라이브)</strong>: 화면이 0.1초 끊기는 것보다
끊김 없이 계속 재생되는 게 중요함.</p>
</li>
<li><p><strong>온라인 게임 (FPS/RPG)</strong>: 내 캐릭터 위치 정보가 0.1초 늦게 전달되는 것보다
현재 위치를 빠르게 갱신하는 게 중요함.</p>
</li>
<li><p><strong>보이스 톡 (VoIP)</strong>: 목소리가 약간 지지직거려도 
실시간 대화가 끊기지 않는 것이 우선임.</p>
</li>
<li><p><strong>DNS</strong>: 주소를 빨리 찾는 게 우선이라 가벼운 UDP를 주로 사용함.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TCP/IP 4계층 모델과 OSI 7계층 모델]]></title>
            <link>https://velog.io/@jun_k/TCPIP-4%EA%B3%84%EC%B8%B5-%EB%AA%A8%EB%8D%B8%EA%B3%BC-OSI-7%EA%B3%84%EC%B8%B5-%EB%AA%A8%EB%8D%B8</link>
            <guid>https://velog.io/@jun_k/TCPIP-4%EA%B3%84%EC%B8%B5-%EB%AA%A8%EB%8D%B8%EA%B3%BC-OSI-7%EA%B3%84%EC%B8%B5-%EB%AA%A8%EB%8D%B8</guid>
            <pubDate>Sun, 26 Apr 2026 08:55:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="계층-모델은-왜-필요한가">계층 모델은 왜 필요한가?</h3>
</blockquote>
<ul>
<li><p>네트워크 통신은 매우 복잡한 과정이다. 
이를 나누지 않고, 하나의 거대한 덩어리로 처리하면 
문제가 발생했을 때 어디를 수리해야 할지 알 수 없고, 
특정 기술(예: Wi-Fi에서 이더넷으로 변경)을 바꿀 때 
전체 시스템을 다시 설계해야 한다.</p>
</li>
<li><p><strong>분할 정복</strong>: 복잡한 기능을 독립된 단위로 나눈다.</p>
</li>
<li><p><strong>표준화</strong>: 각 계층이 정해진 역할만 수행하므로 
서로 다른 제조사의 장비 간에도 통신이 가능하다.</p>
</li>
<li><p><strong>유지보수</strong>: 특정 계층에서 문제가 생기면 해당 계층만 확인하면 된다.</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="osi-7계층-모델">OSI 7계층 모델</h3>
</blockquote>
<ul>
<li>ISO(국제표준화기구)에서 만든 이론적 표준 모델이며 
통신 과정을 7개로 나누어 놓았다.</li>
</ul>
<ul>
<li><p><strong>L1. 물리 계층</strong>: 데이터를 0과 1의 전기적 신호로 변환하여 전송한다. 
(케이블, 허브)</p>
</li>
<li><p><strong>L2. 데이터 링크 계층</strong>: 인접한 노드 간의 물리적 전송을 제어한다. 
MAC 주소를 사용한다. (브리지, 스위치)</p>
</li>
<li><p><strong>L3. 네트워크 계층</strong>: 데이터가 목적지까지 가는 최적의 경로를 설정(라우팅)한다. 
IP 주소를 사용한다. (라우터)</p>
</li>
<li><p><strong>L4. 전송 계층</strong>: 종단 간(End-to-End)의 신뢰성 있는 데이터 전송을 보장한다. 
포트 번호를 사용하며, 데이터 분할 및 재조립을 담당한다. (TCP, UDP)</p>
</li>
<li><p><strong>L5. 세션 계층 (Session)</strong>: 양 끝단의 응용 프로세스가 
통신을 관리하기 위한 연결(세션)을 설정하고 유지한다.</p>
</li>
<li><p><strong>L6. 표현 계층 (Presentation)</strong>: 데이터의 형식을 정의한다. 
암호화, 복호화, 압축 등이 이 계층에서 일어난다. (JSON, JPG 등)</p>
</li>
<li><p><strong>L7. 응용 계층 (Application)</strong>: 사용자가 네트워크에 접속하는 
인터페이스 역할을 한다. (HTTP, FTP, DNS)</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="tcpip-4계층-모델">TCP/IP 4계층 모델</h3>
</blockquote>
<ul>
<li>이론보다는 <strong>&#39;실제로 어떻게 동작하는가&#39;</strong>에 집중하여 
현대 인터넷에서 표준으로 사용되는 모델이다. 
OSI 모델의 여러 계층을 하나로 묶어 효율성을 높였다.</li>
</ul>
<ul>
<li><p>L1. <strong>네트워크 액세스 계층 (Network Access)</strong>: OSI의 물리 계층과
데이터 링크 계층을 합친 형태이며 실제 프레임을 송수신한다.</p>
</li>
<li><p><strong>L2. 인터넷 계층 (Internet)</strong>: OSI의 네트워크 계층에 해당한다. 
IP를 사용하여 패킷을 목적지까지 전달한다.</p>
</li>
<li><p><strong>L3. 전송 계층 (Transport)</strong>: OSI의 전송 계층과 동일하다. 
프로세스 간 통신을 제어한다.</p>
</li>
<li><p><strong>L4. 응용 계층 (Application)</strong>: OSI의 세션, 표현, 응용 계층을 
하나로 합친 계층이며 응용 프로그램 간의 데이터 교환을 담당한다.</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="두-모델-비교">두 모델 비교</h3>
</blockquote>
<p><strong>공통점</strong></p>
<ul>
<li><p>둘 다 계층적 구조를 가진다.</p>
</li>
<li><p>상위 계층의 데이터를 하위 계층이 캡슐화하여 전송한다.</p>
</li>
<li><p>전송 계층과 네트워크(인터넷) 계층의 역할이 거의 동일하게 대응된다.</p>
</li>
</ul>
<p><strong>차이점</strong></p>
<table>
<thead>
<tr>
<th align="left">비교 항목</th>
<th align="left">OSI 7계층</th>
<th align="left">TCP/IP 4계층</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>성격</strong></td>
<td align="left">이론적 표준 (교육용/참조용 모델)</td>
<td align="left">실무적 표준 (인터넷 프로토콜의 표준)</td>
</tr>
<tr>
<td align="left"><strong>계층 수</strong></td>
<td align="left">7계층</td>
<td align="left">4계층 (상황에 따라 5계층으로 분류)</td>
</tr>
<tr>
<td align="left"><strong>범용성</strong></td>
<td align="left">모든 통신 모델을 포괄적으로 설명 가능</td>
<td align="left">주로 인터넷 프로토콜 스택(IP 중심) 설명</td>
</tr>
<tr>
<td align="left"><strong>상위 계층</strong></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>
</tr>
</tbody></table>
<p><strong>결론</strong></p>
<ul>
<li>OSI 모델은 논리적으로 완벽하지만
실제 구현하기에는 너무 세분되어 있어 오버헤드가 발생할 수 있다. 
반면 TCP/IP는 인터넷의 발전과 함께 실용적인 관점에서 
단순화되었기 때문에 현재의 표준이 되었다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 프로세스의 구조]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Fri, 24 Apr 2026 02:48:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>정적변수와 지역변수는 무엇이 다른가</strong></p>
</blockquote>
<p><strong>지역변수</strong></p>
<ul>
<li>메서드 실행 중에만 존재</li>
<li>메서드 끝나면 사라진다.</li>
<li>초기화는 필수적으로 해줘야 한다.<ul>
<li>메서드 호출할 때마다 새로 만들어지고,
스택에 할당되는데 스택은 이전 데이터로 오염되어 있다.
그래서 초기화 안 하면 쓰레기값 남아있을 수 있기에
자바는 안전을 위해 반드시 초기화하도록 강제한다.</li>
</ul>
</li>
<li>저장 위치는 스택 영역에 저장된다.<pre><code class="language-java">class Counter {
  void count() {
      int num = 0;  // 초기화 필수
      num++;
      System.out.println(num);  // 1 출력
  }  // ← num은 여기서 사라진다.
}
</code></pre>
</li>
</ul>
<p>public class Main {
    public static void main(String[] args) {
        Counter c = new Counter();
        c.count();  // 1 출력
        c.count();  // 1 출력 (새로운 num)
        c.count();  // 1 출력 (여전히 새로운 num)
    }
}</p>
<pre><code>- 매번 메서드를 호출할 때마다 ```num```이 새로 만들어진다.
당연히 이전 값은 사라진다.

**정적변수**
- 프로그램 시작 ~ 종료까지 존재한다.
- 메서드가 끝나도 값을 기억한다.
- 초기값은 0으로 자동 초기화 된다.
  - 프로그램 시작 시 메모리 영역에 한 번만 할당 된다.
  메모리 영역은 프로그램이 깔끔하게 초기화 해준다.
따라서 초기화 생략이 가능하다.
- 저장 위치는 프로세스가 점유하고 있는
메모리의 데이터 영역에 저장된다. (C/C++)
자바는 정적변수가 메서드 영역에 저장된다.
```java
class Counter {
    static int num = 0;  // 프로그램 시작 시 한 번만 초기화

    void count() {
        num++;
        System.out.println(num);
    }  // num은 메서드가 끝나도 사라지지 않는다.
}

public class Main {
    public static void main(String[] args) {
        Counter c = new Counter();
        c.count();  // 1 출력
        c.count();  // 2 출력 (이전값 기억함)
        c.count();  // 3 출력 (계속 기억함)
    }
}</code></pre><ul>
<li><code>static int num</code>은 처음 한 번만 만들어지고, 이후로는 그 값을 계속 기억한다.</li>
</ul>
<hr>
<blockquote>
<p><strong>JVM 메모리 구조</strong></p>
</blockquote>
<p><strong>메서드 영역 (Method Area)</strong></p>
<ul>
<li>코드 + 데이터 영역 (클래스 정보, static 변수, 전역변수 등)</li>
<li>메서드의 이름, 반환 타입, 매개변수 정보</li>
<li>실제 실행될 메서드의 바이트코드 (.class 파일의 내용)</li>
</ul>
<p><strong>힙 (Heap)</strong></p>
<ul>
<li><strong>객체 인스턴스</strong> (new로 만든 것들)</li>
</ul>
<p><strong>스택 (Stack)</strong></p>
<ul>
<li>지역변수, 메서드 호출, 콜스택</li>
</ul>
<pre><code class="language-java">
class MyClass {
    static int global = 10;  // 메서드 영역 (= C의 데이터 영역)

    public static void main(String[] args) {
        int local = 5;                    // 스택 (지역변수)
        Integer obj = new Integer(20);    // obj는 참조 변수라 스택, 객체는 힙영역
    }
}
</code></pre>
<hr>
<blockquote>
<p><strong>64비트 컴퓨터 기준으로 최대 256테라바이트까지 사용할 수 있다?</strong></p>
</blockquote>
<p>책 내용을 보니깐 64비트 컴퓨터 기준으로 최대 256테라바이트까지 사용 가능하다고,
나와 있는데 여기서 생긴 의문점이 실제 물리 메모리가 256테라바이트가 안되는데
어떻게 가능하다는 것인지 궁금하여 찾아보게 되었다.</p>
<ul>
<li><p>일단 이론적으로 사용할 수 있는 주소의 범위가 256TB 라는 것이고,
(가상의 주소 공간)
실제로는 물리 메모리(RAM)와 디스크(Swap)의 공간에는 한계가 있기에
실제로는 RAM(16GB) + Swap(약 2TB) 정도만 사용 가능하다는 뜻이다.</p>
</li>
<li><p>이런식으로 설계한 이유는 운영체제가 가상 메모리 시스템을 사용하기 때문이다.
프로그래머는 256TB의 주소공간이 있다고 생각하고,
프로그램을 만들면 되고, 실제로 메모리가 부족해지면 운영체제가
알아서 자동으로 디스크(Swap)를 메모리처럼 사용하여 처리해준다.</p>
<p>따라서 프로그래머는 메모리 크기를 깊게 고민할 필요 없이
필요한 만큼 할당하면 운영체제가 알아서 관리해주는 식이다.</p>
</li>
<li><p><strong>프로그래머의 입장</strong>: &quot;256TB 주소공간 있으니 마음껏 사용해 봐야지&quot;</p>
</li>
<li><p><strong>운영체제의 입장</strong>: &quot;일단 가상 주소는 할당해줄게
실제 메모리는 RAM + Swap으로 관리할게&quot;</p>
</li>
<li><p><strong>실제 동작</strong></p>
<ul>
<li>RAM으로 빠르게 처리 (가능하면)</li>
<li>RAM 부족 - Swap 사용 (느려짐)</li>
<li>둘 다 부족 -&gt; 할당 실패 (에러)</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>힙 영역의 주소 증가, 스택영역의 주소 감소</strong></p>
</blockquote>
<p><strong>힙 (Heap)</strong></p>
<ul>
<li>낮은 주소에서 높은 주소로 증가한다. = 주소 값이 계속 커진다.</li>
<li>버스 좌석 예시로 힙은 낮은 번호로 시작하는 앞에서부터 좌석을 채운다.</li>
<li>예: 0x1000 → 0x2000 → 0x3000 (증가)</li>
</ul>
<p><strong>스택 (Stack)</strong></p>
<ul>
<li>높은 주소에서 낮은 주소로 감소한다. = 주소 값이 계속 작아진다.
버스 좌석 예시로 스택은 높은 번호로 시작하는 뒤에서부터 좌석을 채운다.</li>
<li>예: 0x7000 → 0x6000 → 0x5000 (감소)</li>
</ul>
<hr>
<blockquote>
<p><strong>다중 스레드 vs 다중 프로세스 + IPC</strong></p>
</blockquote>
<p><strong>다중 스레드(Multi-Thread)</strong></p>
<ul>
<li><p>같은 프로세스 내의 모든 스레드는 같은 메모리 공간(코드, 데이터, 힙) 공유</p>
</li>
<li><p>스레드 A와 스레드 B가 같은 변수에 접근 가능</p>
</li>
<li><p>운영체제가 격리하지 않음</p>
</li>
<li><p>스레드 생성 비용: 낮음 (수 마이크로초 ~ 수백 마이크로초)</p>
<ul>
<li>메모리 공간은 이미 있음</li>
<li>스택만 추가로 할당 (스택 영역만 개별로 가지기 때문)</li>
<li>컨텍스트 스위칭도 빠름</li>
</ul>
</li>
</ul>
<p><strong>다중 프로세스</strong></p>
<ul>
<li><p>각 프로세스는 독립적인 메모리 공간을 가진다.</p>
</li>
<li><p>프로세스 A의 메모리 공간 ≠ 프로세스 B의 메모리 공간</p>
</li>
<li><p>운영체제가 완전히 격리</p>
</li>
<li><p>프로세스 생성 비용: 높음 (수 ms ~ 수십 ms)</p>
<ul>
<li>새로운 메모리 공간 할당</li>
<li>운영체제가 개별 관리</li>
<li>컨텍스트 스위칭 비용도 큼</li>
</ul>
</li>
</ul>
<p><strong>왜 이 차이가 생기는가?</strong></p>
<ul>
<li>메모리 격리는 보안과 안정성을 위함</li>
<li>한 프로세스의 오류가 다른 프로세스에 영향을 주지 않음</li>
<li>스레드는 빠른 통신이 필요해서 공유 메모리 선택</li>
</ul>
<hr>
<blockquote>
<p><strong>IPC가 왜 필요한가?</strong></p>
</blockquote>
<p><strong>기본 상황</strong>: 프로세스는 격리되어 있다.</p>
<pre><code class="language-java">프로세스 A                    프로세스 B
┌──────────────┐             ┌──────────────┐
│ 메모리 공간  │             │ 메모리 공간  │
│ data = &quot;hi&quot;  │  격리됨      │ data = ???   │
└──────────────┘             └──────────────┘

문제: 프로세스 A의 &quot;hi&quot;를 프로세스 B가 못 받음
해결책: IPC 필요</code></pre>
<p><strong>IPC 없으면 어떻게 되나?</strong></p>
<pre><code class="language-java">// 프로세스 A
class ProcessA {
    public static void main(String[] args) {
        String message = &quot;안녕하세요&quot;;
        // 프로세스 B에 이 메시지를 보낼 방법이 없다.
    }
}

// 프로세스 B
class ProcessB {
    public static void main(String[] args) {
        // 프로세스 A의 메시지를 받을 방법이 없다.
    }
}</code></pre>
<hr>
<blockquote>
<p><strong>IPC의 4가지(+ 파일) 방식</strong></p>
</blockquote>
<p><strong>방식 0: 파일 (부수적인 추가사항)</strong></p>
<ul>
<li>원래 목적이 통신이 아니라 데이터 저장이다. 
통신을 위해 만들어진 도구가 아니라 
저장된 데이터를 공유하는 부수적인 효과를 이용하는 것이기 때문에 
기존 IPC 메커니즘 분류에서는 빠지는 경우가 많다.</li>
<li>가장 단순하고, 이해하기 쉽다.</li>
<li>속도는 디스크 접근하기에 느리다.</li>
<li>어떤 언어든 가능하다.</li>
<li>프로세스들이 자주 실행 및 종료될 때 혹은
간단한 일회성 데이터 전달에 사용된다.</li>
</ul>
<pre><code class="language-java">// 프로세스 A: 파일에 메시지 쓰기
class ProcessA {
    public static void main(String[] args) throws IOException {
        String message = &quot;프로세스 B에게 전달&quot;;
        Files.write(
            Paths.get(&quot;shared_message.txt&quot;), 
            message.getBytes()
        );
        System.out.println(&quot;메시지 저장됨&quot;);
    }
}

// 프로세스 B: 파일에서 메시지 읽기
class ProcessB {
    public static void main(String[] args) throws IOException {
        Thread.sleep(1000);  // A가 쓸 때까지 대기

        String message = new String(
            Files.readAllBytes(Paths.get(&quot;shared_message.txt&quot;))
        );
        System.out.println(&quot;받은 메시지: &quot; + message);
    }
}</code></pre>
<p><strong>방식 1: 공유 메모리</strong></p>
<ul>
<li><p>일반적으로 운영체제는 프로세스 간의 독립성을 보장하기 위해
각 프로세스에게 독립적인 가상 메모리 공간을 할당한다.
즉, 프로세스 A는 프로세스 B의 메모리를 절대 볼 수 없다. 
(이를 메모리 보호라고 합니다.)</p>
</li>
<li><p>공유 메모리는 이 &quot;담장&quot;의 일부분을 허무는 기술이다.</p>
</li>
<li><p><strong>메커니즘</strong>: OS 커널이 물리 메모리(RAM)의 특정 영역을 할당한다.</p>
</li>
<li><p><strong>매핑</strong>: 프로세스 A와 프로세스 B가 각자의 가상 주소 공간을
이 동일한 물리 메모리 영역에 연결한다.</p>
</li>
<li><p><strong>결과</strong>: 프로세스 A가 특정 주소에 값을 쓰면, 
프로세스 B는 자신의 주소 공간에서 그 값을 즉시 읽을 수 있다.</p>
</li>
</ul>
<p><strong>방식 2: 파이프 (Pipe) - 단방향 통신</strong></p>
<ul>
<li>한 방향만 통신</li>
<li>파이프 생성할 때부터 &quot;누가 쓰고, 누가 읽을지&quot;를 정한다.</li>
<li>누가 무엇을 할지 명확하고, 되돌릴 방법이 없으니 실수할 여지가 적다.</li>
<li>부모-자식 프로세스 간 통신에 최적</li>
<li>OS 레벨에서 지원</li>
</ul>
<pre><code>상황 2: 영화관 매표소
┌─────────────┐  표 넘김  ┌──────────┐
│ 관객        │ ────→     │ 관객     │
│ (표 받으러) │ (표 제출) │ (표 받음)│
└─────────────┘           └──────────┘

관객은 표를 넘길 수만 있음
스태프는 표를 건네줄 수만 있음</code></pre><p><strong>방식 3: 소켓 (Socket) - 네트워크 통신</strong></p>
<pre><code>프로세스 A                           프로세스 B
(전화 받는 사람)                     (전화 거는 사람)
    ↓                                    ↓
[전화기 대기]                       [전화 걸기]
    ↑ ← ← ← ← ← ← ← 신호 ← ← ← ← ←   ↑
    ↓                                    ↓
[통화 중 (양방향)]                  [통화 중 (양방향)]
    ↑ ↔ ↔ ↔ ↔ ↔ ↔ 음성 신호 ↔ ↔ ↔   ↑</code></pre><ul>
<li>전화는 두 사람이 동시에 이야기 할 수 있다. (양방향)</li>
<li>한 명이 먼저 걸고, 다른 한 명이 받아야 함.</li>
<li>연결되면 자유롭게 주고받을 수 있다.</li>
</ul>
<p><strong>통신 과정</strong></p>
<pre><code>1단계: 대기
프로세스 A가 &quot;5000번 포트&quot;에서 기다림

2단계: 연결 신청
프로세스 B가 &quot;프로세스 A에 연결해달라&quot;고 요청

3단계: 연결 수락
프로세스 A가 &quot;좋아, 연결됨&quot;

4단계: 자유로운 대화
서로 원하는 대로 주고받음

5단계: 연결 끊기
누가 끊으면 끝</code></pre><p><strong>방식 4: 메시지 큐 (Message Queue) - 비동기 통신</strong></p>
<pre><code>프로세스 A          메시지 큐          프로세스 B
(메시지 발송)      (중간 저장소)       (메시지 처리)

메시지 넣음 →      [msg1]
메시지 넣음 →      [msg2]  →  메시지 꺼냄
메시지 넣음 →      [msg3]  →  메시지 꺼냄
(떠남)            [msg4]  →  (나중에) 메시지 꺼냄</code></pre><p><strong>왜 &quot;비동기&quot;인가?</strong></p>
<pre><code>동기 (소켓 - 전화)
A: &quot;안녕?&quot; 
B: (전화 받을 때까지 기다림)
B: &quot;응, 뭐해?&quot;
A: (대답할 때까지 기다림)
→ 둘이 동시에 움직여야 함

비동기 (메시지 큐 - 우편함)
A: 편지 쓰고 우편함에 넣음
A: (기다리지 않고 다른 일 함)
B: (나중에 와서 편지 꺼냄)
→ 시간차 있어도 괜찮음</code></pre><p><strong>메시지 큐가 왜 필요한가?</strong></p>
<p><strong>상황 1: 소켓(전화)의 문제점</strong></p>
<ul>
<li>고객이 상담사에게 전화</li>
<li><strong>고객</strong>: 안녕하세요.</li>
<li><strong>상담사</strong>: 응답없음 (퇴근함)</li>
<li><strong>고객</strong>: 기다림</li>
<li><strong>결과</strong>: 전화 끊김, 고객 불만</li>
</ul>
<p><strong>상황 2: 메시지 큐의 해결책</strong></p>
<ul>
<li>고객이 상담 요청을 메시지 큐에 넣음</li>
<li>고객: &quot;상담 요청&quot; -&gt; 큐에 저장</li>
<li>고객: 기다리지 않고 나감 -&gt; 시간 경과</li>
<li>상담사: 출근해서 큐에서 읽는다 -&gt; &quot;상담 요청&quot;</li>
<li>상담사: 처리한다.</li>
<li>결과적으로 고객은 기다릴 필요가 없고,
상담사가 없어도 요청하고자 한 메시지는 안전하게 보관된다.
여러 고객의 요청도 한 번에 처리 가능하다.</li>
</ul>
<p><strong>장점과 단점</strong></p>
<p><strong>장점</strong></p>
<ul>
<li>발신자가 기다릴 필요없이 시간차를 두고, 처리가 가능하다.</li>
<li>메시지가 손실되지않고, 시스템이 부하 견디기가 쉽다.</li>
<li>처리자를 늘리면 더 빨리 처리 가능하고, 부하 분산이 가능하다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>실시간성이 부족하다. (즉시 처리 되지 않음, 약간의 지연 발생)</li>
<li>큐가 커질수록 메모리 증가 하기에 관리가 필요하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[로컬 캐시 vs 분산 캐시]]></title>
            <link>https://velog.io/@jun_k/%EB%A1%9C%EC%BB%AC-%EC%BA%90%EC%8B%9C-vs-%EB%B6%84%EC%82%B0-%EC%BA%90%EC%8B%9C</link>
            <guid>https://velog.io/@jun_k/%EB%A1%9C%EC%BB%AC-%EC%BA%90%EC%8B%9C-vs-%EB%B6%84%EC%82%B0-%EC%BA%90%EC%8B%9C</guid>
            <pubDate>Sun, 19 Apr 2026 12:45:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="데이터의-위치">데이터의 위치</h3>
</blockquote>
<p><strong>로컬 캐시 (Local Cache)</strong></p>
<ul>
<li><p>애플리케이션 <strong>서버 내부 메모리(Heap)</strong>에 데이터를 저장한다.
해당 애플리케이션 서버 인스턴스 내에서만 접근이 가능하다.</p>
</li>
<li><p>Java 예시: Caffeine Cache, Ehcache, HashMap</p>
</li>
<li><p><strong>특징</strong>: 네트워크를 지연이 없으니 속도가 매우 빠르고,
서버 메모리 용량에 한정되어 있기에 확정성은 제한적이고,
데이터의 일관성이 낮은 편이다.</p>
</li>
</ul>
<p><strong>분산 캐시 (Distributed Cache)</strong></p>
<ul>
<li><p>애플리케이션 서버와 분리된 별도의 캐시 전용 서버에 데이터를 저장한다. 
여러 대의 서버 인스턴스가 네트워크를 통해 동일한 데이터를 공유한다.</p>
</li>
<li><p>예시: Redis, Memcached</p>
</li>
<li><p><strong>특징</strong>: 네트워크 I/O 발생하니 속도는 상대적으로 느리지만
모든 서버가 동일 데이터를 참조하니 일관성이 높고, 
확장성이 우수하다.</p>
</li>
</ul>
<p><strong>왜 로컬 캐시가 더 빠른가?</strong></p>
<ul>
<li><p>로컬 캐시는 데이터를 In-Process로 관리한다. 
즉, 데이터 저장소와 로직 수행 주체가 동일한 메모리 공간을 사용하므로 데이터 직렬화/역직렬화(Serialization) 과정과 네트워크 전송 단계가 생략되기 때문입니다.</p>
</li>
<li><p>직렬화/역직렬화가 생략되는 이유는 Redis 같은 분산 캐시는 Java와
다른 동네에 사는 별도의 프로그램이다.
자바 입장에서는 나는 자바 객체라는 고유한 언어를 쓴다고 하고
네트워크/외부 저장소에서는 &quot;나는 그런 복잡한 건 모르겠고,
내가 이해하기 쉽게 0과 1로 된 단순한 byte 형태의 데이터로 줘&quot;
라고 말하고, 자바 객체를 0과 1의 덩어리로 바꾸는 직렬화 과정이 필요하다.</p>
<p>하지만 로컬 캐시는 자바 앱 내에서 쓰이는 것이기에 그냥 그 형태로
온전히 가져가서 이용할 수 있는 것이고, 직렬화 해야하는 이유가 전혀 없다.</p>
</li>
</ul>
<p><strong>왜 분산 캐시는 데이터 일관성이 유리한가?</strong></p>
<ul>
<li>여러 서버(Instance A, B, C)가 
하나의 중앙 집중형 저장소(Redis 등)를 바라보기 때문이다. 
데이터 수정 시 한 곳만 업데이트하면 모든 서버가 즉시 반영된 값을 읽을 수 있다.</li>
</ul>
<hr>
<blockquote>
<h3 id="비유를-통한-설명">비유를 통한 설명</h3>
</blockquote>
<p><strong>로컬 캐시: &quot;스마트폰 사진첩&quot;</strong></p>
<ul>
<li><p><strong>상황</strong>: 내가 찍은 사진을 내 폰 &#39;갤러리&#39; 앱에서 열어보는 것과 같다.</p>
</li>
<li><p><strong>장점</strong>: 비행기 모드여도 상관없다. 
누르자마자 즉시 뜨며, 데이터(네트워크)를 전혀 쓰지 않는다.</p>
</li>
<li><p><strong>단점</strong>: 내가 내 폰에서 사진을 보정(데이터 수정)해도, 
친구 폰에 있는 똑같은 사진은 바뀌지 않는다. 
친구는 내가 사진을 고쳤는지조차 알 수 없다.</p>
</li>
</ul>
<p><strong>분산 캐시: &quot;클라우드&quot;</strong></p>
<ul>
<li><p><strong>상황</strong>: 사진을 클라우드에 올려두고 확인하는 것과 같다.</p>
</li>
<li><p><strong>장점</strong>: 내가 사진 설명을 수정하면, 친구가 자기 폰으로 
접속해서 봐도 수정된 설명이 보인다. 
클라우드에 접속한 모두가 항상 같은 사진을 보게 된다.</p>
</li>
<li><p><strong>단점</strong>: 사진을 보려면 인터넷(네트워크) 연결이 필요하다. 
사진첩에서 보는 것보다 로딩 속도가 미세하게나마 느릴 수밖에 없다.</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="실무-선택-기준">실무 선택 기준</h3>
</blockquote>
<p><strong>로컬 캐시를 선택해야 하는 경우</strong></p>
<ul>
<li><p><strong>단일 서버 환경</strong>: 서버가 한 대뿐이라면 분산 캐시의 필요성이 낮습니다.</p>
</li>
<li><p><strong>극도의 성능이 필요</strong>: 네트워크 지연시간(Latency)조차 허용되지 않는 초고속 응답이 필요한 경우.</p>
</li>
<li><p><strong>데이터 일관성이 중요하지 않은 경우</strong>: 날씨 정보, 공지사항 등 자주 바뀌지 않거나 서버 간 데이터가 잠시 달라도 서비스에 지장이 없는 경우.</p>
</li>
</ul>
<p><strong>분산 캐시를 선택해야 하는 경우</strong></p>
<ul>
<li><p><strong>다중 서버(MSA) 환경</strong>: 여러 인스턴스가 동일한 세션 정보나 
공유 데이터를 참조해야 할 때.</p>
</li>
<li><p><strong>데이터 무결성이 중요한 경우</strong>: 재고 수량, 포인트 결제 등 
모든 서버가 반드시 최신 상태를 공유해야 할 때.</p>
</li>
<li><p><strong>대용량 데이터 저장</strong>: 서버 메모리(Heap)에 담기에는 
데이터 양이 너무 많아 별도의 저장 공간이 필요할 때.</p>
</li>
</ul>
<p><strong>보충 설명</strong></p>
<ul>
<li><p>실무에서는 성능과 일관성을 모두 잡기 위해 </p>
</li>
<li><p><em>2차 캐시 전략(L1: 로컬, L2: 분산)*</em>을 사용하기도 한다. </p>
<p>자주 사용하는 데이터는 로컬에 두어 속도를 높이고, 
로컬에 없으면 분산 캐시에서 찾아오는 방식이다. 
다만, 이 경우 로컬 캐시 간의 동기화를 어떻게 처리할지가 설계의 핵심이 된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 직접 메모리 접근]]></title>
            <link>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%A7%81%EC%A0%91-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%A0%91%EA%B7%BC</link>
            <guid>https://velog.io/@jun_k/%EC%8A%A4%ED%84%B0%EB%94%94-%EC%A7%81%EC%A0%91-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%A0%91%EA%B7%BC</guid>
            <pubDate>Sat, 18 Apr 2026 12:46:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="왜-dma가-필요한가">왜 DMA가 필요한가?</h3>
</blockquote>
<p>컴퓨터의 모든 작업은 원래 CPU를 거쳐야 한다.
하지만 주변 장치(디스크, 네트워크 카드 등)에서 메모리로 
대량의 데이터를 옮길 때마다 CPU가 하나하나 관여한다면,
정작 중요한 연산을 처리할 시간이 부족해진다.</p>
<p>이를 해결하기 위해 <strong>&quot;데이터 전송만 전문적으로 담당하는 대리인&quot;</strong>을 세웠는데
이것이 바로 <strong>DMA 제어기</strong>이다.</p>
<hr>
<blockquote>
<h3 id="dma의-3가지-레지스터">DMA의 3가지 레지스터</h3>
</blockquote>
<ul>
<li><p>DMA 제어기 내부에는 각 주변 장치를 관리하기 위한 3가지 핵심 정보가 들어있다. 
이 값들은 <strong>운영체제(OS)</strong>가 설정한다.</p>
</li>
<li><p><strong>주소 레지스터</strong>: 데이터가 저장될 메모리의 위치 주소, &quot;어디에 놓을까?&quot;
(목적지 주소)</p>
</li>
<li><p><strong>계수 레지스터</strong>: 전송해야 할 데이터의 남은 크기, &quot;얼마나 남았나&quot;
(잔여 수량, Count)</p>
</li>
<li><p><strong>제어 레지스터</strong>: 읽기(0)인지 쓰기(1)인지 결정, &quot;가져올까, 보낼까&quot;
(작업의 종류)</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="시스템-버스-사용권">시스템 버스 사용권</h3>
</blockquote>
<p>데이터는 시스템 버스라는 도로를 통해 이동한다. 
하지만 도로는 하나인데 CPU와 DMA가 동시에 
데이터를 보내려 하면 충돌이 발생한다. </p>
<p>쉽게 말하면 CPU와 DMA 제어기는 서로 독립적인 장치이고,
누가 도로를 쓸지 결정하는 중재의 과정이 명확하지 않으면
당연히 데이터 충돌이 일어나서 데이터가 오염될 수 있다.
이를 방지하기 위한 4단계 과정을 아래와 같이 정리해 보았다.</p>
<ul>
<li><p><strong>DMA 요청</strong>: 주변 장치가 DMA 제어기에게 
&quot;나 데이터 좀 보낼게, 도로 좀 빌려줘&quot;라고 요청한다.</p>
</li>
<li><p><strong>버스 요청</strong>: DMA 제어기가 CPU에게 
&quot;비서인 제가 도로를 좀 써야겠습니다. 권한을 넘겨주세요&quot;라고 신호를 보낸다.</p>
</li>
<li><p><strong>버스 승인</strong>: CPU가 &quot;알겠다. 지금은 내가 도로를 안 쓰니 네가 써라&quot;라며 
사용권을 넘겨준다.</p>
</li>
<li><p><strong>DMA 승인</strong>: DMA 제어기가 주변 장치에게 
&quot;자 권한 따왔어. 이제 데이터 보내보자.&quot;라고 최종 승인을 내린다.</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="무중단-cpu">무중단 CPU</h3>
</blockquote>
<ul>
<li><p>직접 메모리 접근을 한다고 해서 CPU가 하던 일을 멈추는 것이 아니다. </p>
</li>
<li><p>CPU가 일하는 시간은 크게 두 부분으로 나뉜다.</p>
</li>
<li><p><strong>버스 사용 구간</strong>: 명령어 인출(Fetch), 데이터 인출/저장 
(도로 위를 달리는 시간)</p>
</li>
<li><p><strong>내부 연산 구간</strong>: 명령어 해석(Decode), 실행(Execute) 
(사무실 안에서 머리 쓰는 시간)</p>
</li>
<li><p>DMA는 CPU가 <strong>&#39;사무실에서 머리를 쓰는 시간(내부 연산)&#39;</strong>에 몰래 도로를 사용한다. 
CPU 입장에서는 자기가 도로를 안 쓸 때만 DMA가 지나가므로 
작업이 중단되지 않는 것처럼 느껴진다. 
이를 <strong>사이클 스틸링</strong>이라 부른다.</p>
</li>
</ul>
<hr>
<blockquote>
<h3 id="전체-동작-과정-쓰기write와-읽기read">전체 동작 과정: 쓰기(Write)와 읽기(Read)</h3>
</blockquote>
<ul>
<li><p>컴퓨터의 관점에서 봤을 때 <strong>쓰기</strong>는 주변 장치 데이터를 메모리에 넣는 것이고,</p>
</li>
<li><p><em>읽기*</em>는 메모리에 있는 데이터를 주변 장치로 꺼내는 것이다.</p>
</li>
<li><p><strong>직접 메모리 접근 쓰기 (예: 파일 불러오기)</strong></p>
<ul>
<li><p>스토리지에 있는 &quot;DOG&quot;라는 데이터를 메모리로 옮겨서 프로그램이 읽게 함.</p>
</li>
<li><p><strong>1단계 (설정)</strong>: OS가 CPU를 대기시키고, 
DMA 레지스터에 &quot;메모리 주소 X, 크기 3, 작업: 쓰기&quot;를 입력한다. 
스토리지에도 &quot;파일 읽어와&quot;라고 명령한다.</p>
</li>
<li><p><strong>2단계 (전송)</strong>: 스토리지가 버스 권한을 얻으면, 
DMA가 주소를 찍어주고 데이터를 쏜다. 
한 글자 보낼 때마다 주소는 +1, 
계수는 -1을 하며 계수가 0이 될 때까지 반복한다.</p>
</li>
<li><p><strong>3단계 (종료)</strong>: 전송이 끝나면 DMA가 CPU에 &quot;다 끝났습니다.&quot;라고 
인터럽트를 보내고, CPU는 잠자던 프로세스를 깨워 
&quot;DOG&quot; 데이터를 쓰게 한다.</p>
</li>
</ul>
</li>
<li><p><strong>직접 메모리 접근 읽기 (예: 파일 저장하기)</strong></p>
<ul>
<li><p>메모리에 있는 &quot;CAT&quot; 데이터를 스토리지에 저장함.</p>
</li>
<li><p><strong>1단계 (설정)</strong>: OS가 DMA에 
&quot;메모리 주소 Y에 있는 &#39;CAT&#39;을 가져가라&quot;고 설정한다. 
스토리지에도 &quot;데이터 들어올 테니 저장 준비해&quot;라고 명령한다.</p>
</li>
<li><p><strong>2단계 (전송)</strong>: DMA가 메모리에 
&quot;Y 주소에 있는 데이터 도로로 내보내&quot;라고 신호를 주면, 
메모리가 데이터를 내놓고 스토리지가 이를 받아 저장한다. 
역시 주소 증가, 계수 감소를 반복한다.</p>
</li>
<li><p><strong>3단계 (종료)</strong>: 저장이 완료되면 인터럽트를 통해 
CPU에 알리고 작업을 마무리한다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<h3 id="dma-활용-예시---카프카-제로-카피">DMA 활용 예시 - 카프카 (제로 카피)</h3>
</blockquote>
<ul>
<li><p>카프카가 대용량 데이터를 아주 빠르게 처리할 수 있는 핵심 비결 중 
하나가 바로 DMA를 이용한 &#39;제로 카피(Zero-Copy)&#39; 기술이다.</p>
</li>
<li><p><strong>필요 개념 설명</strong></p>
<ul>
<li><p><strong>커널 메모리</strong>: 컴퓨터 메모리는 유저 영역과 커널 영역으로 나뉜다.
유저 영역은 내가 만든 자바 프로그램이나 애플리케이션이 실행되는 일반적인 공간이며 보안상 디스크나 네트워크 같은 하드웨어에 직접 접근 권한이 없다.
커널 영역은 커널이 사용하는 영역이며 당연히 하드웨어를 직접 제어할 수 있는 권한을 가지고 있다.</p>
</li>
<li><p><strong>네트워크 카드</strong>: 일반적인 랜 카드를 의미하고, 컴퓨터 내부의
0과 1같은 디지털 데이터를 실제 전기 신호로 바꾸어 케이블로 쏴주는 관문.</p>
</li>
<li><p><strong>소켓 버퍼</strong>: 데이터를 네트워크로 보낼 때, 한 글자씩 실시간으로
보낼 수는 없기에 모아 놨다가 한꺼번에 보내는 공간이다.
(커널 메모리에 존재한다.)</p>
</li>
</ul>
</li>
<li><p><strong>일반적인 방식 (비효율 방식)</strong></p>
<ul>
<li><p>디스크 → 커널 메모리 → 유저 메모리(Java) → 소켓 버퍼 → 네트워크 카드</p>
</li>
<li><p>중간에 CPU가 데이터를 유저 영역으로 복사하고, 
다시 소켓으로 복사하는 &#39;노가다&#39;를 두 번이나 함.</p>
</li>
<li><p>데이터 전송하느라 정작 로직 처리는 느려진다.</p>
</li>
</ul>
</li>
<li><p><strong>제로 카피 방식 (Kafka)</strong></p>
<ul>
<li><p>디스크 → 커널 메모리 → 네트워크 카드 (중간 단계 생략)</p>
</li>
<li><p>카프카가 OS에 &quot;복사하지 말고 바로 쏴&quot;라고 명령(<code>sendfile</code>)한다.</p>
</li>
<li><p>DMA가 커널 메모리에서 네트워크 카드로 데이터를 직접 쏴버린다. 
CPU는 복사 업무에서 완전히 해방된다.</p>
</li>
</ul>
</li>
<li><p><strong>부연 설명</strong></p>
<ul>
<li><p>카프카는 데이터를 수정하지 않고 그대로 전달만 하는 경우가 많다.</p>
</li>
<li><p>그래서 굳이 CPU를 써서 <strong>유저 메모리(Java)</strong>로 데이터를 가져올 필요가 없다.</p>
</li>
<li><p>DMA에게 명령을 내려 커널 메모리에서 네트워크 카드로 바로 직행시킨다.</p>
</li>
<li><p>결과적으로 CPU 점유율은 낮아지고, 
전송 속도는 하드웨어 한계치까지 올라간다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<h3 id="요약">요약</h3>
</blockquote>
<ul>
<li><p>DMA 제어기는 단순히 &quot;전송 장치&quot;가 아니라, 
CPU의 효율성을 극대화하기 위해 탄생한 중재자이다.</p>
</li>
<li><p>CPU의 I/O 부하를 줄여 시스템 전체 성능을 높이기 위해 사용한다.</p>
</li>
<li><p>CPU가 버스를 안 쓰는 틈새 시간(내부 연산)을 이용하기 때문에
멈추지 않는다.</p>
</li>
<li><p>결과적으로 대량의 데이터 전송 중에도 백엔드 서버의 CPU는 
다른 복잡한 로직을 끊임없이 처리할 수 있게 된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] @Cacheable, @CachePut, @CacheEvict: 데이터 흐름으로 이해하는 캐시 전략]]></title>
            <link>https://velog.io/@jun_k/Spring-Cacheable-CachePut-CacheEvict-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EC%BA%90%EC%8B%9C-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@jun_k/Spring-Cacheable-CachePut-CacheEvict-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EC%BA%90%EC%8B%9C-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Mon, 13 Apr 2026 13:44:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>캐시(Cache)는 왜 쓸까?</strong></p>
</blockquote>
<ul>
<li><p>데이터베이스는 거대한 창고와 같다.
원하는 정보를 찾으려면 그 넓은 창고에서 일일이 찾아야 한다.
당연히 시간도 많이 소모되고, 그에 대한 비용도 소모될 것이다.</p>
</li>
<li><p>반면 캐시는 책상 위에 놓인 메모지이다.
자주 찾는 정보는 메모지에 적어두고, 필요할 때마다 펼쳐서 보면
당연히 창고까지 일일이 갈 필요 없이 바로 답을 얻을 수 있기에
속도가 압도적으로 빠르다.</p>
</li>
<li><p><strong>Spring Cache</strong>의 어노테이션들은 &quot;<strong>메모지를 관리하는 규칙들</strong>&quot;이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>@Cacheable: &quot;메모지에 있으면 그거 주고, 없으면 창고 가서 적어와&quot;</strong></p>
</blockquote>
<ul>
<li><p>가장 많이 쓰이는 <strong>조회</strong> 규칙</p>
</li>
<li><p><strong>동작 흐름</strong></p>
<ul>
<li><p>사용자가 &quot;1번 책 어딨어?&quot;라고 물으면, 사서는 먼저
책상 위 메모지(@Cacheable)를 본다.</p>
</li>
<li><p><strong>케이스 A (Cache Hit)</strong>: 메모지에 &quot;1번 책은 A 구역에 있음&quot;이라고,
적혀 있으면 -&gt; 창고에 안 가고, 바로 필요한 값을 준다. (메서드 실행 X)</p>
</li>
<li><p><strong>케이스 B (Cache Miss)</strong>: 메모지에 내용이 없다면? 사서가 직접
창고(DB)에 가서 데이터를 가져오고, 다음에 또 물어볼 것에 대비하여</p>
</li>
</ul>
</li>
<li><p><em>메모지에 그 내용을 적어둔다*</em>. (메서드 실행 O)</p>
<ul>
<li>똑같은 질문을 여러 번 할 때, 두 번째부터는 
창고에 가는 수고(연산/조회 비용)를 사실상 0으로 만든다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>@CachePut: &quot;창고 작업은 무조건 하고, 메모지도 최신으로 고쳐 써&quot;</strong></p>
</blockquote>
<ul>
<li><p>이것은 <strong>수정</strong> 규칙이다.</p>
</li>
<li><p><code>@Cacheable</code>과 가장 큰 차이는 <strong>&quot;일단 창고에 가느냐 마느냐&quot;</strong>이다.</p>
</li>
<li><p><strong>동작 흐름</strong></p>
<ul>
<li><p>사서는 메모지를 보지 않고, <strong>무조건 창고(DB)로 달려가서</strong> 정보를 수정한다.
(메서드 무조건 실행)</p>
</li>
<li><p>수정이 끝났으면, 그 결과물을 <strong>메모지에 새로 덮어쓴다.</strong></p>
</li>
</ul>
</li>
<li><p><strong>왜 @Cacheable을 안 쓰고 이걸 쓰나?</strong></p>
<ul>
<li>@Cacheable은 메모지에 기록이 있으면 창고(DB)를 안 가버린다.
하지만 데이터가 수정되었을 때는 실제 DB를 고치는 작업이
반드시 일어나야 하기 때문에 <code>@CachePut</code>을 사용한다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>@CacheEvict: &quot;데이터가 바뀌었으니 낡은 메모지는 버려&quot;</strong></p>
</blockquote>
<ul>
<li><p><strong>삭제</strong> or <strong>초기화</strong> 규칙이다.</p>
</li>
<li><p><strong>동작 흐름</strong></p>
<ul>
<li><p><strong>삭제</strong>: 사서가 창고에서 책을 폐기하거나 아주 크게 정보를 바꾼다.</p>
</li>
<li><p><strong>파기</strong>: 이때 책상 위에 있던 낡은 메모지를 찢어서 버린다.</p>
</li>
</ul>
</li>
<li><p><strong>왜 버려야 하나?</strong></p>
<ul>
<li>창고(DB)에서는 책이 사라졌는데 메모지에는 아직 &#39;A구역에 있음&#39;이라고,
적혀 있으면 사서는 사용자에게 거짓말을 하는 꼴이 된다.
이를 <strong>데이터 불일치</strong>라고 한다.
이를 방지하기 위해 메모리를 깨끗하게 비우는 것이다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>데이터 수정의 2가지 경우</strong></p>
</blockquote>
<ul>
<li><p><strong>방법 A (@CachePut)</strong>: &quot;DB도 고치고, 메모지(캐시)도 새 내용으로 고쳐 쓰자.&quot;</p>
<ul>
<li><p><strong>장점</strong>: 다음 조회 시 즉시 캐시에서 데이터를 가져올 수 있어 매우 빠름.</p>
</li>
<li><p><strong>단점</strong>: 수정하는 과정에서 메모지에 적을 
데이터를 만드는 연산이 복잡하거나 비용이 크다면 비효율적임.</p>
</li>
</ul>
</li>
<li><p><strong>방법 B (@CacheEvict)</strong>: &quot;DB는 고치고, 메모지는 그냥 찢어서 버리자.&quot;</p>
<ul>
<li><p><strong>장점</strong>: 메모지를 새로 적는 수고를 덜 수 있음.
어차피 나중에 누군가 요청할 때 다시 창고(DB)에서 
최신 데이터를 가져와서 적으면 됨.</p>
</li>
<li><p><strong>논리</strong>: &quot;낡은 데이터(수정 전)를 보여주느니, 
차라리 메모지를 없애서(삭제) 다음 사람에게 정확한 정보를 
새로 가져와서 전달해주겠다.&quot;는 전략이다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>코드 예시</strong></p>
</blockquote>
<p><strong>DTO (캐시에 저장할 데이터는 불변의 <code>record</code> 사용)</strong></p>
<pre><code class="language-java">
// record를 사용해 불변 객체로 정의
public record Book(Long id, String title, String author) {}
</code></pre>
<p><strong>서비스 로직</strong></p>
<pre><code class="language-java">
@Service
public class BookService {

    // 조회: 메모지에 있으면 주고, 없으면 창고(DB) 가서 가져와서 적어둠
    // books는 캐시의 저장소 이름을 지정한 것
    // key = &quot;#id&quot;는 메모지들 사이에서 특정 데이터 찾기 위한 이름표
    @Cacheable(value = &quot;books&quot;, key = &quot;#id&quot;)
    public Book getBook(Long id) {
        simulateSlowService(); // DB 조회를 시뮬레이션하는 무거운 로직
        return new Book(id, &quot;Spring 공부법&quot;, &quot;Junyoung&quot;);
    }

    // 수정: 창고(DB) 내용을 고치고, 메모지(캐시) 내용도 최신으로 업데이트함
    @CachePut(value = &quot;books&quot;, key = &quot;#book.id()&quot;)
    public Book updateBook(Book book) {
        // 실제로는 repository.save(book) 등이 호출됨
        return book; 
    }

    // 삭제: 창고(DB)에서 지우고, 메모지(캐시)도 찢어서 버림
    @CacheEvict(value = &quot;books&quot;, key = &quot;#id&quot;)
    public void deleteBook(Long id) {
        // 실제로는 repository.deleteById(id) 등이 호출됨
        System.out.println(id + &quot;번 책이 삭제되었습니다.&quot;);
    }

    // 성능 차이를 확인하기 위한 가상 지연 메서드
    private void simulateSlowService() {
        try {
            Thread.sleep(3000); // 3초 대기
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 비동기(@Async) 환경에서 MDC와 SecurityContext가 사라지는 이유와 해결책]]></title>
            <link>https://velog.io/@jun_k/Spring-%EB%B9%84%EB%8F%99%EA%B8%B0Async-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-MDC%EC%99%80-SecurityContext%EA%B0%80-%EC%82%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%9D%B4%EC%9C%A0%EC%99%80-%ED%95%B4%EA%B2%B0%EC%B1%85</link>
            <guid>https://velog.io/@jun_k/Spring-%EB%B9%84%EB%8F%99%EA%B8%B0Async-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-MDC%EC%99%80-SecurityContext%EA%B0%80-%EC%82%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%9D%B4%EC%9C%A0%EC%99%80-%ED%95%B4%EA%B2%B0%EC%B1%85</guid>
            <pubDate>Sun, 12 Apr 2026 14:33:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>왜 정보가 사라질까?</strong></p>
</blockquote>
<p><strong>ThreadLocal (스레드 지역 변수)</strong></p>
<ul>
<li><p>특정 스레드 내에서만 공유되는 변수</p>
</li>
<li><p>다른 스레드가 접근할 수 없으므로 동기화 문제 없이
데이터를 안전하게 보관할 수 있다.</p>
</li>
<li><p><strong>MDC &amp; SecurityContext</strong>: 두 개념 모두 내부적으로 
ThreadLocal을 사용하여 정보를 저장한다.</p>
</li>
<li><p><strong>MDC</strong>: 로그에 찍을 추적 ID(Trace ID) 등을 스레드별로 저장.</p>
<ul>
<li><p>&quot;<strong>해당 스레드가 살아있는 동안, 어디서든 꺼내 쓸 수 있는 사물함</strong>&quot;</p>
</li>
<li><p><strong>입구 (필터)</strong>: 요청이 들어오면 서버는 무작위 UUID를 생성하여
MDC 사물함에 넣는다.</p>
</li>
<li><p><strong>실행 중 (서비스/리포지토리)</strong>: 로그 찍을 때, 내가 직접 ID 입력 안해도
로깅 라이브러리(Logback 등)가 자동으로 MDC 사물함 뒤져서
ID를 로그 앞에 붙여준다.</p>
</li>
<li><p><strong>Finally</strong>: 요청 처리가 끝나면 다음 사람을 위해 사물함을 비운다.</p>
</li>
<li><p>로그용 ID를 따로 넘길 필요 없기에 코드가 깔끔해지고,
데이터 오염도 방지해주고, 각 스레드마다 독립적인 MDC 사물함을
쓰기 때문에, A의 ID가 B의 로그에 찍힐 걱정이 없다.</p>
</li>
</ul>
</li>
<li><p><strong>SecurityContext</strong>: 현재 로그인한 사용자 정보를 스레드별로 저장.</p>
</li>
<li><p><code>@Async</code>나 <code>CompletableFuture</code>를 사용하면 작업은 </p>
</li>
<li><p><em>기존 스레드(Parent)가 아닌 새로운 스레드(Worker)*</em>에서 실행된다.
<code>ThreadLocal</code>은 스레드 단위로 데이터가 격리되어 있기 때문에, 
부모 스레드가 가진 정보를 자식 스레드는 알 방법이 없다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>해결 전략: 컨텍스트 전파</strong></p>
</blockquote>
<ul>
<li><p>비동기 환경에서 정보를 유지하려면 
&quot;<strong>부모 스레드의 정보를 복사해서 자식 스레드에 직접 주입</strong>&quot;하는 과정이 필요하다.</p>
</li>
<li><p><strong>단계 정리</strong></p>
<ol>
<li><p><strong>Capture</strong>: 부모 스레드에서 실행 직전 컨텍스트 정보를 읽어온다.</p>
</li>
<li><p><strong>Setup</strong>: 자식 스레드가 작업을 시작하기 전, 읽어온 정보를 설정한다.</p>
</li>
<li><p><strong>Execute</strong>: 비동기 비즈니스 로직 실행한다.</p>
</li>
<li><p><strong>Clear</strong>: 작업 완료 후, 자식 스레드의 컨텍스트를 무조건 비운다.
(스레드 풀 환경에서 데이터 오염 방지)</p>
</li>
</ol>
</li>
<li><p><strong>Spring Boot에서 가장 권장되는 방식인 <code>TaskDecorator</code>를 활용한 설정</strong> 
(MDC 전달을 위한 TaskDecorator 구현)</p>
</li>
</ul>
<pre><code class="language-java">import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import java.util.Map;

public class MdcTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        // [1] 부모 스레드의 MDC 맵을 복사 (Capture)
        Map&lt;String, String&gt; contextMap = MDC.getCopyOfContextMap();

        return () -&gt; {
            try {
                // [2] 자식 스레드에 설정 (Setup)
                if (contextMap != null) {
                    MDC.setContextMap(contextMap);
                }
                // [3] 실제 작업 수행 (Execute)
                runnable.run();
            } finally {
                // [4] 작업 완료 후 정리 (Clear) - 스레드 풀 재사용 대비
                MDC.clear();
            }
        };
    }
}
</code></pre>
<p><strong>ThreadExecutor 설정</strong></p>
<pre><code class="language-java">@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        // 위에서 만든 데코레이터 설정
        executor.setTaskDecorator(new MdcTaskDecorator());
        executor.initialize();
        return executor;
    }
}</code></pre>
<hr>
<blockquote>
<p><strong>Spring Security Context 전달</strong></p>
</blockquote>
<ul>
<li>SecurityContext는 보안상의 이유로 좀 더 엄격한 관리가 필요하다.
이를 위해 전용 래퍼 클래스를 제공한다.</li>
</ul>
<p><strong>DelegatingSecurityContextAsyncTaskExecutor 사용</strong></p>
<pre><code class="language-java">@Bean
public Executor securityAwareExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.initialize();

    // SecurityContext를 자식 스레드에 복사해주는 래퍼 사용
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}</code></pre>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">해결 방법</th>
<th align="left">비고</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>MDC</strong></td>
<td align="left"><code>TaskDecorator</code> 구현</td>
<td align="left">커스텀 필드 전파에 유리</td>
</tr>
<tr>
<td align="left"><strong>SecurityContext</strong></td>
<td align="left"><code>DelegatingSecurityContext...</code> 사용</td>
<td align="left">보안 정보 전파에 최적화</td>
</tr>
<tr>
<td align="left"><strong>공통 주의사항</strong></td>
<td align="left"><strong>반드시 <code>finally</code>에서 <code>clear()</code> 호출</strong></td>
<td align="left">스레드 풀 환경에서 데이터 누수 방지</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/jun_k/post/58cca414-abbb-4d93-8db6-e7420e115884/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[경쟁 상태(Race Condition)의 본질과 해결 전략]]></title>
            <link>https://velog.io/@jun_k/%EA%B2%BD%EC%9F%81-%EC%83%81%ED%83%9CRace-Condition%EC%9D%98-%EB%B3%B8%EC%A7%88%EA%B3%BC-%ED%95%B4%EA%B2%B0-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@jun_k/%EA%B2%BD%EC%9F%81-%EC%83%81%ED%83%9CRace-Condition%EC%9D%98-%EB%B3%B8%EC%A7%88%EA%B3%BC-%ED%95%B4%EA%B2%B0-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Sun, 12 Apr 2026 14:10:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>멀티스레드를 사용하는 이유</strong></p>
</blockquote>
<p>현대 CPU는 여러 개의 코어를 가지고 있으며, 백엔드 서버(Spring Boot 등)는
수많은 요청을 동시에 처리해야 한다.
처리량을 극대화하기 위해서 하나의 프로세스 내에서 여러 개의 <strong>스레드</strong>가
자원을 공유하며 병렬로 작업을 한다.</p>
<blockquote>
<p><strong>경쟁 상태 (Race Condition)</strong></p>
</blockquote>
<p><strong>경쟁 상태</strong>란, 
&quot;두 개 이상의 스레드가 공유 데이터에 동시에 접근하여 변경하려고 할 때,
실행 순서에 따라 결과가 달라지는 현상&quot; (결과가 그때그때 달라짐.)</p>
<p><strong>예시 (<code>count++</code>)</strong>
우리 눈으로 보기에는 자바 코드 상에서 <code>count++</code> 한 줄이지만,
CPU 입장에서 봤을 때 아래의 3단계를 거쳐야 한다.</p>
<ol>
<li><p><strong>READ</strong>: 메모리에 저장된 <code>count</code> 값을 읽어 CPU 레지스터로 가져온다.</p>
</li>
<li><p><strong>MODIFY</strong>: 레지스터에 있는 값에 1을 더한다.</p>
</li>
<li><p><strong>WRITE</strong>: 계산된 결과를 다시 메모리에 저장한다.</p>
</li>
</ol>
<p><strong>경쟁 상태로 인한 문제 발생</strong></p>
<ul>
<li>스레드 A와 B가 <code>count=0</code>인 상태에서 동시에 접근한다는 가정.</li>
</ul>
<ol>
<li><p><strong>스레드 A</strong>: 메모리에서 <code>0</code>을 읽음 (READ)</p>
</li>
<li><p><strong>스레드 B</strong>: 메모리에서 <code>0</code>을 읽음 (READ) -&gt; A가 아직 저장하지 않음</p>
</li>
<li><p><strong>스레드 A</strong>: <code>0 + 1 = 1</code> -&gt; 계산 (MODIFY)</p>
</li>
<li><p><strong>스레드 B</strong>: <code>0 + 1 = 1</code> -&gt; 계산 (MODIFY)</p>
</li>
<li><p><strong>스레드 A</strong>: 메모리에 <code>1</code> 저장 (WRITE)</p>
</li>
<li><p><strong>스레드 B</strong>: 메모리에 <code>1</code> 저장 (WRITE)</p>
</li>
</ol>
<ul>
<li><p><strong>최종 결과</strong>: 정상 흐름이라면 <code>count</code>는 <code>2</code>가 되어야 하지만, 실제로는 <code>1</code>이 된다.</p>
</li>
<li><p><strong>스레드 A의 시점</strong>: &quot;내가 읽었을 때 <code>0</code>이었으니까, 
여기에 <code>1</code>을 더해서 <code>1</code>을 저장해야지.&quot;</p>
</li>
<li><p><strong>스레드 B의 시점</strong>: &quot;나도 읽었을 때 <code>0</code>이었어.
그러니 나도 <code>1</code>을 더해서 <code>1</code>을 저장할게.&quot;</p>
</li>
<li><p>이처럼 작업이 채 끝나지 않았는데 중간에 다른 스레드가 개입하면서
약간 딱딱하게 말하면 <strong>원자성이 깨졌다</strong>고 하고, 
쉽게 말하면 <strong>데이터가 꼬여버리는 것</strong>이다.</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>해결을 위한 4가지 방법</strong></p>
</blockquote>
<ol>
<li><strong>격리: ThreadLocal (공유 자체를 차단)</strong></li>
</ol>
<ul>
<li><p>공유를 아예 막아버리면 경쟁 자체도 발생하지 않는다.</p>
</li>
<li><p><strong>ThreadLocal</strong>은 각 스레드에게 자신만의 전용 저장소를 할당한다.</p>
</li>
<li><p><strong>적용</strong>: 사용자 세션 정보, 트랜잭션 컨텍스트 등
(<code>@Transactional</code>이 붙은 메서드를 실행하면 내부적으로 <code>TransactionSynchronizationManager</code> 실행되고, 이 클래스 내부적으로
<code>ThreadLocal</code>을 사용하여 트랜잭션 정보를 보관한다.
당연히 트랜잭션 정보는 사용자끼리 섞이면 안되기 때문에
<code>ThreadLocal</code>로 관리되어야 한다.)</p>
</li>
<li><p>스레드 풀 환경에서는 작업 종료 후 반드시 <code>.remove()</code>를 호출해야
보안 문제나 메모리 누수를 막을 수 있다.
(스레드는 재사용되기에 <code>.remove</code>를 해서 이전 내용물을 비워줘야 한다. 안 비우면 사용자 A의 요청을 <strong>스레드 1번</strong>이 처리해서 
이 안에 A의 데이터가 들어있을텐데 비우지 않으면 일단 스레드 1번이 스레드 풀로 반납이 되고, 다만 그 안에 담긴 데이터는 스레드와 운명을 같이하며 지워지지 않기에  나중에 사용자 B가 마침 반납된 <strong>스레드 1번</strong>을 받았을때 이전 사용자 A의 정보가 그대로 들어있게 된다.
당연히 메모리도 차지하고 있기에 누수 문제도 발생한다.)</p>
</li>
</ul>
<ol start="2">
<li><strong>불변성: Record &amp; Final (수정 자체를 차단)</strong></li>
</ol>
<ul>
<li>상태가 변하지 않는다면, 여러 스레드가 읽기만 하므로 안전하다.</li>
<li><code>record</code> 키워드를 사용하여 DTO를 설계하면 모든 필드가 <code>final</code>이 되어
데이터 정합성이 보장된다.<pre><code class="language-java">// 데이터 수정 불가
public record UserCounter(int count) { } </code></pre>
</li>
</ul>
<ol start="3">
<li><strong>Atomic 클래스 (비차단 제어)</strong></li>
</ol>
<ul>
<li><p>Lock을 걸지 않고도 CPU 레벨에서 원자성을 보장하는 
CAS(Compare-And-Swap) 알고리즘을 사용한다.</p>
</li>
<li><p>내가 알고 있는 정보가 여전히 유효한지 끝까지 확인하는 방식</p>
</li>
<li><p><strong>CAS 알고리즘의 논리 구조</strong></p>
<ul>
<li><p>현재 메모리 값($V$)을 읽는다.</p>
</li>
<li><p>내가 예상한 값($E$)과 현재 값($V$)이 같은지 비교한다.</p>
</li>
<li><p>$V = E$라면 새로운 값($N$)으로 교체한다.
아니라면 다시 1단계로 돌아간다(Loop).</p>
</li>
<li><p><strong>장점</strong>: 스레드를 멈추게 하는(Blocking) 비용이 없어 성능이 우수하다.</p>
</li>
</ul>
</li>
<li><p><strong>상황 가정: count++ (현재 값 10)</strong></p>
<ol>
<li><strong>읽기 (Read)</strong>: 스레드 A가 메모리를 보니 10이다.<ul>
<li>이때 스레드 A는 메모해 둔다. &quot;내가 본 값($E$)은 10이다.&quot;<br></li>
</ul>
</li>
<li><strong>연산 (Modify)</strong>: 스레드 A가 자기 안에서 10 + 1을 해서 11을 만든다.<ul>
<li>이때 <strong>바꾸고 싶은 값($N$)</strong>이 11이 된다.<br></li>
</ul>
</li>
<li><strong>검증 (Compare)</strong>: 이제 메모리에 11을 쓰기 직전, 
CPU가 메모리의 <strong>진짜 값($V$)</strong>을 다시 확인한다.<br></li>
</ol>
<ul>
<li><strong>질문</strong>: &quot;지금 메모리에 있는 값($V$)이, 내가 아까 봤던 값($E=10$)과 똑같니?&quot;<br></li>
<li><strong>왜 이 질문을 하나?</strong>: 만약 그 사이 다른 스레드가 값을 11로 바꿔버렸다면, 메모리의 <strong>진짜 값($V$)</strong>은 11이 되어 있을 것이다. 
그러면 $V(11) \neq E(10)$이 되므로, 
&quot;아! 누군가 그새 값을 바꿨구나!&quot;라고 판단하고 작업을 취소하는 것이다.</li>
</ul>
</li>
</ul>
<ol start="4">
<li><strong>Synchronized &amp; Lock (직접 제어)</strong></li>
</ol>
<ul>
<li><p>특정 영역(Critical Section)에 한 번에 하나의 스레드만 
들어오도록 문을 잠그는 방식이다.</p>
</li>
<li><p>자원을 사용하려는 스레드가 아예 문을 걸어 잠그는 것</p>
</li>
<li><p><strong>Synchronized</strong>: 자바의 고유 락(Monitor Lock)을 이용한 간단한 해결책.</p>
</li>
<li><p><strong>ReentrantLock</strong>: 더 세밀한 제어(공정성 설정, 타임아웃 등)가 
필요할 때 사용한다.</p>
</li>
<li><p><strong>단점</strong>: 과도한 사용은 스레드 대기를 유발하여 
시스템 전체의 성능 하락을 초래한다.</p>
</li>
<li><p><strong>비유: 1인용 공용 화장실</strong></p>
<ul>
<li><p>스레드 A가 화장실에 들어가면서 문을 잠근다. (Lock 획득)</p>
</li>
<li><p>뒤이어 온 스레드 B는 문이 잠겨 있으니 화장실 앞에서 줄 서서 기다린다.
(Blocking/Wait)</p>
</li>
<li><p>스레드 A가 일을 다 보고, 문을 열고 나온다.</p>
</li>
<li><p>줄 서 있던 스레드 B가 그제야 들어간다.</p>
</li>
</ul>
</li>
<li><p><strong>성능 하락이 발생하는 이유</strong></p>
<ul>
<li><p><strong>컨텍스트 스위칭</strong>: 줄을 서서 기다리는 스레드 B는 그냥 서 있는게 아니고,
OS에 의해 &quot;휴면 상태&quot;로 전환되었다가 다시 &quot;실행 상태&quot;로 깨어나야 한다.
이 &quot;잠들고 깨우는 과정&quot;이 CPU 입장에서는 굉장히 무거운 작업이다.</p>
</li>
<li><p><code>count++</code> 같이 아주 간단하고 짧은 작업이라도, 잠그고 깨우는 비용이
훨씬 큰 배보다 배꼽이 큰 상황이 발생할 수 있다.</p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 1590번: 캠프가는 영식]]></title>
            <link>https://velog.io/@jun_k/%EB%B0%B1%EC%A4%80-1590%EB%B2%88-%EC%BA%A0%ED%94%84%EA%B0%80%EB%8A%94-%EC%98%81%EC%8B%9D</link>
            <guid>https://velog.io/@jun_k/%EB%B0%B1%EC%A4%80-1590%EB%B2%88-%EC%BA%A0%ED%94%84%EA%B0%80%EB%8A%94-%EC%98%81%EC%8B%9D</guid>
            <pubDate>Sat, 11 Apr 2026 11:55:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jun_k/post/46d1ec72-b2f8-4811-98b0-d224fa767307/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jun_k/post/178d9a04-ea50-4a04-9ce8-7e69763736af/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jun_k/post/7a167dfd-3fb5-48fc-b662-e20395eb8a11/image.png" alt=""></p>
<p>버스 정류장에 도착한 시간 T 이후에 가장 빨리 출발하는 버스를 찾는 문제다.</p>
<p><strong>문제 설명</strong></p>
<ul>
<li><p><strong>기준 시점</strong>: 영식이가 도착하는 시간 T</p>
</li>
<li><p><strong>버스 정보</strong>: 각 버스 노선은 <strong>시작 시간(S)</strong>, <strong>간격(I)</strong>, <strong>대수(C)</strong>를 가진다.</p>
</li>
<li><p>모든 노선의 모든 버스 시각 중 T 이상인 값 중 가장 작은 값을 찾아 
T와의 차이를 구하는 것.</p>
</li>
</ul>
<p><strong>입력 값</strong></p>
<ul>
<li><p>첫째 줄에 노선의 종류(우등, 일반 느낌) N개, 버스터미널에 도착하는 시간 T</p>
</li>
<li><p>둘째 줄에는 버스 개수 N개에 대한 각 버스의 시작 시간, 간격, 대수가
공백을 사이에 두고 주어진다.</p>
</li>
</ul>
<p><strong>출력 값</strong></p>
<ul>
<li><p><strong>영식이가 기다려야 하는 시간을 출력한다.</strong></p>
<ul>
<li><p>추가적으로 영식이가 도착하는 동시에 버스가 출발하면 0,</p>
</li>
<li><p>만약 버스가 없어서 캠프에 갈 수 없으면 -1</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>하나의 버스 노선(S, I, C)에 대해 영식이가 탈 수 있는 가장 빠른 버스 시각 구하기</strong></p>
<ol>
<li><strong>영식이가 첫 차보다 빨리 온 경우 ( T ≤ S )</strong><ul>
<li>가장 빨리 탈 수 있는 차가 당연히 첫 차인 S이다.</li>
<li>기다리는 시간: (S - T)<br></li>
</ul>
</li>
<li><strong>영식이가 마지막 차보다 늦게 온 경우 ( T &gt; S + (C-1)I )</strong><ul>
<li>이 노선에는 탈 수 있는 버스가 없다.<br></li>
</ul>
</li>
<li><strong>영식이가 운행 기간 중에 온 경우 ( S &lt; T ≤ S + (C-1)I )</strong><ul>
<li>시작 시간인 S 시점부터 T까지 흐른 시간은 (T - S)</li>
<li>이 시간 동안 버스 간격 I가 몇 번 지났는지 계산해야 한다. 
<code>(T - S) / I</code></li>
<li>이를 토대로 현재 노선에서 기다려야 하는 시간도 구한다.</li>
</ul>
</li>
</ol>
<ul>
<li><p>전체 버스 노선 중 최소값 갱신</p>
<p> 모든 노선에 대해 위 과정을 반복하며 가장 작은 기다리는 시간을 저장한다.</p>
<ul>
<li><strong>초기값 설정</strong>: 최소 대기 시간을 저장할 변수를 
아주 큰 값(예: <code>Integer.MAX_VALUE</code>)으로 초기화한다.<br></li>
<li><strong>유효성 검사</strong>: 만약 어떤 버스 노선에서도 탈 수 있는 버스가 없다면
(즉, 초기값이 변하지 않았다면) <code>1</code>을 출력한다.</li>
</ul>
</li>
</ul>
<ul>
<li><p>왜 마지막 버스인데 C-1 이냐하면</p>
<ul>
<li><p><strong>C</strong>: 버스의 총 대수 (예: 3대)</p>
</li>
<li><p><strong>I</strong>: 버스 사이의 시간 간격 (예: 10분)</p>
</li>
<li><p><strong>S</strong>: 첫 번째 버스가 출발하는 시각 (기준점)</p>
</li>
<li><p>버스가 3대(C=3) 있다고 가정하고 시간을 계산</p>
</li>
</ul>
<ol>
<li><p><strong>첫 번째 버스 (1번):</strong> 그냥 시작 시간 S에 출발한다. (간격 I를 더할 필요가 없음)</p>
</li>
<li><p><strong>두 번째 버스 (2번):</strong> S에서 간격 I가 <strong>1번</strong> 지났을 때 출발한다. (S + 1 x I)</p>
</li>
<li><p><strong>세 번째 버스 (마지막 버스):</strong> S에서 간격 I가 <strong>2번</strong> 지났을 때 출발한다.
(S + 2 x I)</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<pre><code> 버스가 3대(C=3)일 때 마지막 버스의 시각을 구하기 위해 
 간격 I를 곱한 횟수는 **2번이다**. 즉, 3 - 1번</code></pre><pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class test1590 {

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

    StringTokenizer st = new StringTokenizer(br.readLine());

    // 버스 종류 개수 (우등인지 일반인지)
    int N = Integer.parseInt(st.nextToken());

    // 영식이가 버스 터미널에 도착할 시간
    int T = Integer.parseInt(st.nextToken());

    // 결과값 저장 (초기값: -1, 버스가 없는 경우 대비)
    long minWait = -1;

    for (int i = 0; i &lt; N; i++) {
      st = new StringTokenizer(br.readLine());
      long S = Integer.parseInt(st.nextToken()); // 시작 시간
      long I = Integer.parseInt(st.nextToken()); // 간격
      long C = Integer.parseInt(st.nextToken()); // 대수

      // 해당 버스 노선의 마지막 차 시간 계산
      long lastBusTime = S + (C - 1) * I;

      // 영식이가 마지막 차보다 늦게 오면 이 노선은 당연히 놓치는 것이다.
      if (T &gt; lastBusTime) continue;

      long nextBusTime;

      // 영식이가 첫 차보다 빨리 온 경우
      if (T &lt;= S) {
        nextBusTime = S;
      } else {
        // 첫 차 이후에 도착한 경우 -&gt; 몇 번째 차를 탈 수 있는지 계산
        // (T인 도착시간 - S인 시작시간)을 간격 I로 나눈 몫으로 이미 지나간 차의 대수 파악
        // 예로 50이라는 시간에 도착했고 시작 시간인 S는 30이라면 50 - 30 = 20
        // 20이라는 시간이 지나간 것이고 이 20이라는 시간동안 몇 대가 지나갔는지 확인하려면
        // 당연히 간격인 I로 나눈 몫을 통해 확인할 수 있다. 
        // (간격이 10이면 20 / 10 -&gt; 2대 지나간 것)
        // ceil로 올림 처리
        int goneBusCount = (int) Math.ceil((double) (T - S) / I);
        nextBusTime = S + goneBusCount * I;
      }

      // 현재 노선에서 기다려야 하는 시간
      long currentWait = nextBusTime - T;

      // 전체 노선 중 최소값 갱신
      if (minWait == -1 || currentWait &lt; minWait) {
        minWait = currentWait;
      }
    }

    System.out.println(minWait);
  }

}
</code></pre>
<p><img src="https://velog.velcdn.com/images/jun_k/post/4f86c9cf-b868-4053-958a-42db99b0329e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jun_k/post/16c8e7e7-7a49-4731-a6f4-deeab4304261/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[주변 장치 구조 및 연결 방식]]></title>
            <link>https://velog.io/@jun_k/%EC%A3%BC%EB%B3%80-%EC%9E%A5%EC%B9%98-%EA%B5%AC%EC%A1%B0-%EB%B0%8F-%EC%97%B0%EA%B2%B0-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@jun_k/%EC%A3%BC%EB%B3%80-%EC%9E%A5%EC%B9%98-%EA%B5%AC%EC%A1%B0-%EB%B0%8F-%EC%97%B0%EA%B2%B0-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Fri, 10 Apr 2026 08:06:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>메모리 맵 입출력 방식 (Memory-mapped Input Output)</strong></p>
</blockquote>
<ul>
<li><p>CPU가 사용할 수 있는 전체 주소 공간의 일부를
메모리에 할당하고, 남은 구역을 주변 장치(HDD, 키보드 등)에 할당하는 방식</p>
</li>
<li><p><strong>CPU의 시야</strong>: CPU는 <code>STORE 0x8000, 1</code>이라는 명령을 내릴 때, 
이것이 RAM에 저장하는 것인지 HDD에 명령을 내리는 것인지 구분하지 않는다.
오직 &quot;0x8000번지에 1을 보낸다&quot;는 행위 자체에만 집중한다.</p>
</li>
<li><p><strong>통합 관리</strong>: 메모리와 주변 장치를 별개로 보지 않고, 16비트 기준으로
65,536개($2^{16}$)의 주소 안에 함께 배치한다.</p>
</li>
<li><p><strong>레지스터 매핑</strong>: 주변 장치 안에는 상태를 보고하거나 
명령을 받는  <strong>제어/데이터/상태 레지스터</strong>들이 있고,
메모리의 특정 주소(예: 0xA000, 0xA300 등)와 1:1로 대응시켜 관리한다.</p>
</li>
<li><p><strong>레지스터 매핑 추가 설명</strong></p>
<ul>
<li><strong>주변 장치</strong>: 키보드, 그래픽카드 같은 장치.
내부에는 설정을 바꾸거나 데이터를 주고받기 위한 </li>
</ul>
</li>
<li><p><em>아주 작은 메모리 칸(레지스터)*</em>들이 들어있다.</p>
<ul>
<li><p><strong>주소 버스</strong>: CPU가 &quot;몇 번지로 가라!&quot;라고 외치는 전선 뭉치.</p>
</li>
<li><p><strong>CPU</strong>: 명령어를 실행할 때 오직 <strong>&#39;주소&#39;</strong>만 보고 데이터를 읽거나 쓴다.</p>
</li>
<li><p><strong>동작 과정</strong></p>
<ul>
<li><p>CPU가 <code>STORE 1, 0xA000</code> 이라는 명령을 실행한다.
(주소 0xA000에 1을 저장해라)</p>
</li>
<li><p>주소 전선에 <code>0xA000</code>이라는 신호가 흐른다.</p>
</li>
<li><p>시스템 내부에 있는 &#39;주소 해독기&#39;가 이 신호를 보고 </p>
</li>
<li><p>*&quot;이건 RAM이 아니라 프린터로 보내야 해&quot;**라고 판단하여
신호의 길을 주변 장치 쪽으로 틀어준다.</p>
</li>
<li><p>주변 장치 내부의 제어 레지스터에 <code>1</code>이라는 값이 기록된다.
기계는 이 <code>1</code>을 보고 &quot;아, 작동을 시작하라는 뜻이구나&quot;라고 
판단하여 물리적으로 일을 시작한다.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>매핑이 없다면?</strong></p>
<ul>
<li>CPU는 메모리용 명령어(LOAD/STORE)와
장치용 명령어(IN/OUT)을 따로 만들어야 한다.
하지만 매핑을 하면 <strong>명령어 하나로 모든 것을 처리</strong>할 수 있기에
CPU 구조가 보다 단순해진다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>주소 버스(A0~A15)의 정체 (16비트 기준)</strong></p>
</blockquote>
<ul>
<li><p>CPU와 모든 부품은 &quot;<strong>16가닥의 구리선(주소 버스)</strong>&quot;로 연결되어 있다.
이 구리선들이 CPU가 몇 번지로 가라고, 신호하는 통로이다.</p>
</li>
<li><p>주소 <code>0xA000</code>을 이진수로 바꾸면 <code>1010 0000 0000 0000</code>이다.
CPU는 이 숫자에 맞춰 16개의 전선에 전기를 흘리거나(1) 끊는다(0).</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>A15: &quot;메모리냐 장치냐&quot;를 가르는 마스터 스위치</strong></p>
</blockquote>
<ul>
<li><p>16가닥(A0~A15)중 가장 왼쪽 전선인 <strong>A15(마스터 스위치)</strong>는 가장 큰 주소 단위를 결정하고,
하드웨어 엔지니어는 이 전선 하나로 길을 반으로 나눈다.</p>
</li>
<li><p>가장 왼쪽인 A15가 마스터 스위치인 이유는 우리가 쓰는 십진수에서도
가장 왼쪽 숫자가 전체 크기를 결정한다.
예로 <code>1234</code>라는 숫자에서 가장 왼쪽의 숫자인 <code>1</code>이 천의 자리이고, 
이 숫자가 <code>0</code>이면 전체 값은 <code>0000 ~ 0999</code> 사이
이 숫자가 <code>1</code>이면 전체 값은 <code>1000 ~ 1999</code> 사이이다.
이처럼 가장 왼쪽 숫자는 전체 범위를 두 덩어리로 크게 나누는 기준이 된다.</p>
</li>
<li><p><strong>메모리 구역 (A15=0)</strong>: 주소의 시작이 0인 모든 곳 (0x0000 ~ 0x7FFF).</p>
<ul>
<li><strong>물리적 원리</strong>: A15 전선에 전기가 안 들어올 때만
RAM이 작동하도록 <strong>반전기(NOT 게이트)</strong>를 달아놓는다.
즉, &quot;전기가 안 흐름 → 반전기 통과 → RAM에 전기 전달 → RAM 활성화&quot;</li>
</ul>
</li>
<li><p><strong>주변 장치 구역 (A15=1)</strong>: 주소의 시작이 1인 모든 곳 (0x8000 ~ 0xFFFF).</p>
<ul>
<li><strong>물리적 원리</strong>: A15 전선에 전기가 흐를 때만 <strong>장치 해독기(Decoder)</strong>에
전기가 공급되어 장치들이 깨어난다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>해독기(Decoder): &quot;어떤 장치인가&quot;를 고르는 문지기</strong></p>
</blockquote>
<ul>
<li><p>A15를 통과해 주변 장치의 구역에 들어왔다면
이때에는 <strong>A13, A14</strong>가 나설 차례가 된다.</p>
<ul>
<li><p><strong>동작 원리</strong>: 해독기는 $A_{13}$와 $A_{14}$ 전선의 전기 조합을 보고,
특정 장치의 버튼을 누른다.</p>
<ul>
<li><code>A13=0, A14=0</code> 이면? -&gt; <strong>키보드</strong> 버튼 클릭</li>
<li><code>A13=1, A14=0</code> 이면? -&gt; <strong>HDD</strong> 버튼 클릭</li>
</ul>
</li>
<li><p>버튼(활성화 신호)이 눌린 장치만 CPU가 보내는 데이터를 받아들이고,
나머지 장치들은 자기 일이 아니겠거니 하고, 무시한다. </p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>전체적인 흐름</strong></p>
</blockquote>
<ul>
<li><p><code>STORE 1, 0xA000</code> 명령을 내리는 순간 벌어지는 일</p>
<ul>
<li><p><strong>소프트웨어</strong>: &quot;0xA000(HDD의 제어 레지스터 주소)에 1을 써라.&quot;</p>
</li>
<li><p><strong>전기 신호</strong>: $A_{15}$ 전선에 전기 흐름(1), $A_{13}$ 전선 전기 흐름(1),
$A_{14}$ 전선 전기 없음(0).</p>
</li>
<li><p><strong>물리적 선택</strong></p>
<ul>
<li><p>$A_{15}$가 1이라서 RAM은 가만히 있고, 장치 구역이 깨어난다.</p>
</li>
<li><p>해독기가 <code>A13=1, A14=0</code> 신호를 보고,
HDD 버튼을 누르며 문을 열어준다.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>운영체제에서 HDD에서 클러스터 단위로 파일을 저장하면 
왜 파일 입출력 성능이 높아지는 것인가?</strong></p>
</blockquote>
<ul>
<li><p>컴퓨터 내부에서 HDD의 헤드(데이터를 읽는 바늘)가
아무래도 기계 장치이다 보니 전자가 이동하는 속도에 비해서는
물리적으로 움직이는 속도가 현저히 느리다.</p>
<ul>
<li><p><strong>탐색 시간(Seek Time)</strong>: 헤드가 데이터가 있는 원판인
플래터의 위치로 이동하는 시간.</p>
</li>
<li><p><strong>회전 지연(Rotational Latency)</strong>: 플래터가 빙글빙글 돌아
데이터가 헤드 밑에 올 때까지 기다리는 시간.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>섹터</strong></p>
<ul>
<li>HDD가 물리적으로 데이터를 저장하는 가장 작은 칸이다.
보통 512바이트 정도인데, 사실 이 단위는
운영체제가 관리하기엔 너무 작다.</li>
</ul>
<br>
</li>
<li><p><strong>클러스터</strong></p>
<ul>
<li><p>운영체제는 여러 개의 섹터를 하나로 묶어</p>
</li>
<li><p><em>클러스터*</em>라는 단위를 만들고, 이 단위로만 파일을 주고 받는다.
보통 4096바이트의 크기로
이렇게 하나로 묶으면 성능이 올라가는 이유는 아래와 같다.</p>
<ul>
<li>데이터를 섹터 단위로 쪼개서 여기저기 저장하면, 
헤드는 그 조그만하고, 잘 보이지도 않는 조각들을 
찾으려 쉬지 않고, 움직여야 한다.</li>
</ul>
<p>하지만 클러스터 단위로 묶어버리면, 헤드가 한 번만 이동해서
그 자리에 연속된 데이터를 한꺼번에 쭉 읽어버릴 수 있다.
(아무래도 영역이 넓기 때문에 그 영역을 한번에 읽을 수 있다.)</p>
<ul>
<li>그리고 I/O 요청 횟수도 감소한다.
CPU가 HDD에 &quot;데이터 좀 보내줘&quot;라고, 요청하면
한 번의 명령으로 클러스터라는 큰 덩어리의 데이터를 한 번에
가져오기 때문에 왓다갔다 하는 횟수가 확연히 줄어든다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>SSD의 직접 접근 방식</strong></p>
</blockquote>
<ul>
<li><p>HDD와 SSD의 가장 큰 차이는 데이터를 찾는 방식이
물리적이냐, 전기적이냐에 있다.</p>
</li>
<li><p><strong>HDD (순차적/물리적 접근)</strong></p>
<ul>
<li>레코드판을 예시로 들면 만약 1번 노래를 듣다가
갑자기 10번 노래를 듣고 싶다고 하면 바늘(헤드)이
1번에서 -&gt; 10번 위치까지 직접 <strong>움직여야</strong> 한다.
당연히 멀리 있으면 오래 걸리고, 가까이 있으면 빠르다.
즉, 데이터의 &#39;물리적인 위치&#39;가 속도를 결정한다.</li>
</ul>
</li>
<li><p><strong>SSD (직접/전기적 접근)</strong></p>
<ul>
<li>아파트의 인터콤을 예시로 들면 경비실에서 
101호를 호출하나 1501호를 호출하나,
전기 신호가 전선을 타고 전달되는 시간은 거의 똑같다.
기계적으로 움직이는 부품이 없기 때문이다.</li>
</ul>
</li>
<li><p><strong>직접 접근이란</strong></p>
<ul>
<li><p>중간 단계를 거치지 않고, 주소만 알면 목표 지점으로 </p>
</li>
<li><p><em>즉시 점프*</em>한다는 뜻이다.</p>
</li>
<li><p>낸드 플래시 내부에는 데이터가 저장되는 아주 작은 방들이
가로세로 바둑판처럼 배열되어 있다.
각 방에는 고유한 <strong>행(Row) 번호</strong>와 <strong>열(Column) 번호</strong>가 붙어 있다.</p>
</li>
<li><p><strong>주소 해독기(Decoder)의 역할</strong></p>
<ul>
<li><p>CPU가 &quot;0x5000 주소의 데이터를 가져와!&quot;라고 명령한다.</p>
</li>
<li><p>SSD 컨트롤러는 이 주소를 이진수로 분석해 해당 방으로 
연결된 전선에 전기를 흘린다.</p>
</li>
<li><p>전기는 빛의 속도에 가깝게 이동하므로, 칩의 입구에 있는 데이터나 
가장 구석에 있는 데이터나 <strong>도달하는 시간 차이가 거의 0</strong>이다.</p>
</li>
</ul>
</li>
<li><p><strong>탐색 시간의 소멸</strong>: 데이터를 찾기 위해 헤드를 옮기는 과정이 없다.</p>
</li>
<li><p><strong>회전 지연의 소멸</strong>: 데이터가 올 때까지 원판이 돌기를 기다릴 필요가 없다.</p>
</li>
<li><p><strong>일관된 전기적 경로</strong>: 어떤 주소를 넣든 주소 해독기를 거쳐 
해당 셀(Cell)까지 도달하는 전기적 경로는 물리적으로 
매우 짧고 일정하게 설계되어 있다.</p>
</li>
</ul>
</li>
<li><p><strong>랜덤 읽기 성능</strong></p>
<ul>
<li><p>직접 접근 특성 때문에 랜덤한 곳에 있는 
데이터를 읽는 성능은 SSD가 압도적으로 유리하다.</p>
</li>
<li><p>HDD는 데이터가 여기저기 흩어져 있으면 헤드가 왔다 갔다 하느라
속도가 처참하게 느려진다. (데이터 파편화가 치명적)</p>
</li>
<li><p>SSD는 데이터가 어디에 흩어져 있든 주소만 알면
즉시 가져올 수 있으므로 성능 저하가 거의 없다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jun_k/post/8f2a5a87-7984-4267-9771-42403a47c72b/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT의 구조와 원리]]></title>
            <link>https://velog.io/@jun_k/JWT%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jun_k/JWT%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Sun, 05 Apr 2026 13:09:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>JWT란 무엇인가?</strong></p>
</blockquote>
<p>JWT는 정보를 JSON 객체 형태로 안전하게 전송하기 위한 <strong>개방형 표준(RFC 7519)</strong>이다.</p>
<p>&quot;전 세계 개발자들이 &#39;이렇게 만들자&#39;고 <strong>약속한(RFC 7519)</strong>
 <strong>가볍고(JSON)</strong> 조작이 불가능한 디지털 서명</p>
<ul>
<li><p><strong>Header</strong>: 토큰의 타입과 사용된 암호화 알고리즘 정보</p>
</li>
<li><p><strong>Payload</strong>: 실제 전달할 데이터(사용자 정보 등)</p>
</li>
<li><p><strong>Signature</strong>: <strong>Header</strong>와 <strong>Payload</strong>를 조합해 만든 보안 코드 (위변조 방지 핵심)</p>
</li>
</ul>
<hr>
<blockquote>
<p><strong>JWT의 3단 구조: 왜 점(.)으로 나뉠까?</strong></p>
</blockquote>
<p>JWT는 세 부분으로 구성되며, 각 부분은 &#39;마침표(<code>.</code>)&#39;로 구분된다.
<code>Header.Payload.Signature</code> 형식을 가진다.</p>
<p><strong>1. Header (헤더): &quot;이 물건의 정체는 무엇인가?&quot;</strong></p>
<ul>
<li><p>헤더는 토큰의 &#39;메타 데이터&#39;를 담고 있다. 
두 가지 핵심 정보를 포함한다.</p>
</li>
<li><p><strong>typ</strong>: 토큰의 타입을 지정한다. (보통 JWT)</p>
</li>
<li><p><strong>alg</strong>: 시그니처(서명)를 생성할 때 사용할 해싱 알고리즘을 지정한다.
(예: HS256, RS256)</p>
</li>
<li><p><strong>비유</strong>: 택배 상자 겉면에 붙은 <strong>&#39;배송 라벨&#39;</strong>과 같다.
이 상자가 어떤 종류의 물건인지,
어떤 방식으로 취급(검증)해야 하는지 알려주는 역할이다.</p>
<br>

</li>
</ul>
<p><strong>2. Payload (페이로드): &quot;실제 전달할 내용물&quot;</strong></p>
<ul>
<li><p>토큰에 담을 실제 정보(클레임)들이 들어있다.
클레임은 크게 세 종류로 나뉜다.</p>
</li>
<li><p><strong>등록된 클레임</strong>: 서비스에서 이미 정의된 정보들
(예: <code>iss</code>(발행자), <code>sub</code>(제목), <code>exp</code>(만료시간))</p>
</li>
<li><p><strong>공개 클레임(Public)</strong>: 충돌 방지를 위해 URI 형식으로 정의한 정보</p>
</li>
<li><p><strong>비공개 클레임(Private)</strong>: 서버와 클라이언트 간에 협의하여
사용하는 사용자 정의 정보 (예: <code>userId</code>, <code>role</code>)</p>
</li>
<li><p><strong>주의</strong>: 페이로드는 암호화된 것이 아니라 <strong>Base64Url로 인코딩</strong>된 것뿐이다.
누구나 디코딩하면 내용을 볼 수 있기 때문에,</p>
</li>
<li><p><em>비밀번호 같은 민감한 정보*</em>는 절대로 페이로드에 넣어서는 안 된다.</p>
</li>
</ul>
<br>

<p><strong>3. Signature (시그니처): &quot;이 내용물이 진짜인가?&quot;</strong></p>
<ul>
<li><p><strong>JWT의 핵심</strong>이다.
헤더의 인코딩 값과 페이로드의 인코딩 값을 합친 뒤,
&quot;<strong>서버만 알고 있는 비밀키(Secret Key)</strong>&quot;로 암호화하여 생성한다.</p>
</li>
<li><p><strong>목적</strong>: 토큰의 <strong>무결성 검증</strong>,</p>
<ul>
<li><p>만약 중간에 누군가 페이로드의 내용을 1바이트라도 수정한다면,
서버의 비밀 키로 계산한 시그니처와 일치하지 않게 된다.</p>
</li>
<li><p>서류 봉투에 찍힌 &quot;<strong>인감도장</strong>&quot; or &quot;<strong>봉인 씰</strong>&quot;
봉투 안의 내용(Payload)이 바뀌었는지, 이 도장이 진짜
우리 회사의 도장(Secret Key)이 맞는지 확인하는 장치이다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p><strong>왜 인코딩(Base64Url)을 하는 것인가?</strong></p>
</blockquote>
<ul>
<li><p>JSON 데이터에는 공백이나 특수문자(<code>{</code>, <code>}</code>, <code>&quot;</code>, <code>:</code>,  <code></code>,  <code>\n</code> 등)가 포함되어 있어 
전송 시 URL이나 HTTP 헤더의 규칙과 충돌하여 데이터가 깨질 위험이 있는데</p>
<p>이를 방지하기 위해 </p>
</li>
<li><p><em>전 세계 모든 시스템이 공통적으로
해석할 수 있는 64개의 안전한 문자(알파벳, 숫자, -, _)로만 
데이터를 변환(Base64Url)*</em>하여 전송의 안정성과 무결성을 보장하기 위함이다.</p>
</li>
</ul>
<blockquote>
<p><strong>왜 세션 대신 JWT를 쓰는 것인가?</strong></p>
</blockquote>
<p><strong>Stateless(무상태)</strong></p>
<ul>
<li><p><strong>세션</strong>: 사용자가 로그인하면 서버(메모리나 DB)에 
&quot;이 사람은 로그인됨&quot;이라는 기록을 남겨야 한다.</p>
</li>
<li><p><strong>JWT</strong>: 서버는 기록을 남기지 않는다.
대신 로그인 시 <strong>유효기간이 적힌 위조 불가능한 티켓(JWT)</strong>을 
만들어서 사용자에게 준다. 
이후 사용자가 <strong>티켓(JWT)</strong>을 제시하면 서버는 자기 장부를 확인하는 게 아니라, 
티켓의 인장(Signature)이 진짜인지만 확인하고 즉시 통과시킨다.</p>
</li>
</ul>
<p><strong>Scale-out (서버 확장)</strong></p>
<ul>
<li><p><strong>세션</strong>: 서버가 1번부터 10번까지 있을 때, 
1번 서버에서 로그인한 사용자가 2번 서버로 요청을 보내면 
2번 서버는 그 사용자를 모른다(세션 공유 문제 발생). 
이를 해결하려면 별도의 공유 DB(Redis 등)를 구축해야 한다.</p>
</li>
<li><p><strong>JWT</strong>: 모든 서버가 동일한 <strong>비밀 키(Secret Key)</strong>만 공유하고 있다면, 
어느 서버로 요청이 가든 그 자리에서 즉시 티켓 검증이 가능하다.
서버를 수백 대 늘려도 인증 체계가 복잡해지지 않는다.</p>
</li>
</ul>
<p><strong>Decoupled (분리): 역할의 독립</strong></p>
<ul>
<li><p><strong>세션</strong>: 인증과 서비스 로직이 같은 세션 저장소를
바라봐야 하므로 강하게 결합된다.</p>
</li>
<li><p><strong>JWT</strong>: <strong>&quot;인증만 해주는 서버&quot;</strong>를 따로 만들 수 있다.
인증 서버가 토큰을 발행해주면, 메일 서비스 서버나 결제 서비스 서버는
각자 자기 키로 토큰이 맞는지 확인만 하면 된다.
서로 남의 장부를 뒤져볼 필요가 없어 서비스 간 독립성이 극대화된다.</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>