<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hani.log</title>
        <link>https://velog.io/</link>
        <description>개발 일기🌱</description>
        <lastBuildDate>Thu, 31 Jul 2025 14:22:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hani.log</title>
            <url>https://velog.velcdn.com/images/pyh-dotcom/profile/18fa2bdb-4921-42b4-bf44-f5d384bce4eb/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hani.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pyh-dotcom" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[혼공네트] 4주차_전송 계층]]></title>
            <link>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-4%EC%A3%BC%EC%B0%A8%EC%A0%84%EC%86%A1-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-4%EC%A3%BC%EC%B0%A8%EC%A0%84%EC%86%A1-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Thu, 31 Jul 2025 14:22:46 GMT</pubDate>
            <description><![CDATA[<h1 id="ip의-한계">IP의 한계</h1>
<h2 id="ip의-특징-unreliable-connectless">IP의 특징: unreliable, connectless</h2>
<p>IP 패킷은 신뢰성이 없고 연결을 수립하는 과정이 없다.
⇒ 호스트 간의 연결이 이루어지지 않고 패킷이 수신됐다는 것을 보장할 수 없다.</p>
<p>왜? 그럴까?
<strong>빠른 전송 속도</strong>가 중요하다.
패킷 한 두 개 쯤 손실이 발생해도 큰 지장이 없다.</p>
<h2 id="전송-계층에서-ip-한계-보완">전송 계층에서 IP 한계 보완</h2>
<ul>
<li>TCP<ul>
<li>연결형 통신
TCP의 경우 송수신할 두 호스트가 연결을 수립한다.</li>
<li>신뢰성 있는 통신
오류, 흐름, 혼잡 제어 등으로 패킷이 올바른 순서대로 전달되는 것을 보장한다.</li>
</ul>
</li>
<li>UDP<ul>
<li>TCP가 항상 좋은 것은 아니다. 빠른 전송이 필요한 경우 사용할 수 있다.</li>
<li>비연결형 통신</li>
<li>신뢰할 수 없는 통신</li>
</ul>
</li>
</ul>
<h2 id="포트port">포트(port)</h2>
<p>패킷은 실행 중인 <strong>특정 프로세스까지 전달</strong>되어야 한다.
한 호스트 내에서 <strong>각 프로세스를 구분할 수 있는 정보</strong>가 바로 포트이다.</p>
<p>포트 번호는 16bit로 표현
⇒ 2<sup>16</sup>개 존재
⇒ 0 - 65536</p>
<p>e.g.)</p>
<ul>
<li>22번: ssh</li>
<li>80번: http</li>
<li>443번: https</li>
<li>3306: MySQL 데이터베이스</li>
</ul>
<h3 id="nat과-napt">NAT과 NAPT</h3>
<p>IP 주소를 변환하는 기술이다.
LAN에서 사용되는 사설 IP ↔ WAN에서 사용되는 공인 IP
비교적 적은 공인 IP, 비교적 다수의 사설 IP ⇒ 모든 IP 주소를 일대일 매칭하는 것은 불가능</p>
<p>포트를 기반으로한 <strong>NAPT(Network Address Port Translation)</strong>이 해결 방안이 될 수 있다.
같은 공인 IP 주소를 서로 다른 사설 IP 주소로 식별하기 위해 NAT 테이블에 포트를 함께 기술하여 사용한다.</p>
<h1 id="tcp">TCP</h1>
<h2 id="tcp-세그먼트-구조">TCP 세그먼트 구조</h2>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/8a6d698e-f37c-4e16-a09b-13628f139781/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>한국어</th>
<th>영어</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>순서 번호</td>
<td>sequence number</td>
<td>송수신하는 세그먼트의 올바른 순서를 보장</br>세그먼트 데이터의 첫 바이트에 부여되는 번호</td>
</tr>
<tr>
<td>확인 응답 번호</td>
<td>acknowledgement number</td>
<td>상대 호스트가 보낸 세그먼트에 대한 응답<br>다음 수신하기를 기대하는 순서 번호 명시</td>
</tr>
<tr>
<td>제어 비트</td>
<td>control bits</td>
<td>세그먼트에 대한 부가 정보(CWR-FIN)</td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>제어 비트</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>ACK</td>
<td>세그먼트의 승인을 나타내기 위한 비트</td>
</tr>
<tr>
<td>SYN</td>
<td>연결을 수립하기 위한 비트</td>
</tr>
<tr>
<td>FIN</td>
<td>연결을 종료하기 위한 비트</td>
</tr>
</tbody></table>
<h2 id="tcp-연결-수립-및-종료">TCP 연결 수립 및 종료</h2>
<p>TCP는 three-way handshake로 연결을 수립하고 four-way handshake 과정으로 연결을 종료한다.</p>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/df693108-96d6-4008-af36-832b9994a603/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/2548d7de-8b63-416a-ac8b-2287de273db7/image.png" alt=""></p>
<p>위의 과정에서 TCP 상태만 살펴보면 아래 이미지와 같다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/f16bead6-4f85-46c2-81a2-c39d052e29c5/image.png" alt=""></p>
<h2 id="오류-제어-재전송-기법">오류 제어: 재전송 기법</h2>
<p>TCP는 아래 두 가지 상황에서 오류가 발생했다고 판단하고 세그먼트를 재전송한다.</p>
<ul>
<li>중복된 ACK 수신<ul>
<li>클라이언트 측에서 송신 호스트가 보낸 특정 세그먼트를 받지 못한 경우, 다음 세그먼트를 전달해달라는 세그먼트를 여러 번 받게 된다.</li>
</ul>
</li>
<li>타임아웃<ul>
<li>TCP 세그먼트를 송신하는 호스트는 <strong>재전송 타이머</strong> 값을 유지한다.</li>
<li>이 타이머의 카운트다운이 끝날 떄까지 ACK 세그먼트가 오지 않으면(RTT를 넘길 경우) 타임아웃이 발생하고 세그먼트를 재전송한다.</li>
</ul>
</li>
</ul>
<h2 id="흐름-제어-슬라이딩-윈도우">흐름 제어: 슬라이딩 윈도우</h2>
<p>호스트가 한 번에 받아서 처리할 수 있는 세그먼트의 양에는 한계가 있다.
따라서 송신 호스트가 <strong>수신 호스트의 처리 속도를 고려하며 송수신 속도를 균일하게 유지</strong>해야한다.
이때 슬라이딩 윈도우 알고리즘을 활용한다.</p>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/6c6130a0-22df-44f8-870c-ed04495edd7a/image.png" alt=""></p>
<h2 id="혼잡-제어">혼잡 제어</h2>
<p>혼잡(congestion)이란, 트래픽이 많아 세그먼트가 유실되거나 처리속도가 늦어지는 현상을 말한다.
혼잡 윈도우란, 혼잡 없이 전송할 수 있는 데이터의 양을 말한다.
따라서 혼잡 윈도우를 적절히 설정하는 것이 중요하다.</p>
<p>혼잡 제어를 위한 가장 기본적인 알고리즘은 <strong>AIMD(Additive Increase/MultiplicativeDecrease)</strong>이다.
말 그대로 혼잡 감지 X ⇒ 혼잡 윈도우를 RTT마다 1 증가 / 혼잡 감지 ⇒ 혼잡 윈도우 반감</p>
<p>조금 더 정교한 알고리즘으로 아래 세 가지가 있다.</p>
<ul>
<li><p>느린 시작</p>
<ul>
<li><p>혼잡 윈도우를 1부터 시작해, 문제 없이 수신된 ACK 세그먼트 1개당 1씩 증가</p>
</li>
<li><p>혼잡 감지 시</p>
<table>
<thead>
<tr>
<th>상황 분류</th>
<th>해결 방법</th>
</tr>
</thead>
<tbody><tr>
<td>타임 아웃</td>
<td>혼잡 윈도우 = 1<br>느린 시작 임계치를 이전의 절반으로 초기화<br>느린시작 재개</td>
</tr>
<tr>
<td>혼잡 윈도우 &gt;= 느린 시작 임계</td>
<td>느린 시작 종료, 혼잡 회피</td>
</tr>
<tr>
<td>3번의 중복 ACK 세그먼트</td>
<td>빠른 재전송 후 빠른 회복</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p>혼잡 회피</p>
<ul>
<li>RTT마다 혼잡 윈도우를 1MSS만큼 증가</li>
</ul>
</li>
<li><p>빠른 회복</p>
<ul>
<li>세 번의 중복 ACK 세그먼트를 수신했을 때 느린 시작은 건너뛰고 혼잡 회피를 수행</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/e6f74e3a-4228-4de3-b3d9-cc89a1fc0573/image.png" alt=""></p>
<h1 id="숙제">숙제</h1>
<h2 id="필수-숙제">필수 숙제</h2>
<ol>
<li>Ch.04-1 확인 문제 1번<ul>
<li>비신뢰성</li>
<li>비연결형</li>
</ul>
</li>
<li>Ch 04-2 확인 문제 2번<ul>
<li>ACK</li>
</ul>
</li>
</ol>
<hr>
<p>&lt;참고 자료&gt;</p>
<ul>
<li>TCP 세그먼트 구조: <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=1889">http://www.ktword.co.kr/test/view/view.php?m_temp1=1889</a></li>
<li>TCP 연결 수립: <a href="https://haloworld.tistory.com/15">https://haloworld.tistory.com/15</a></li>
<li>TCP 연결 수립 및 종료: <a href="https://www.crocus.co.kr/1362">https://www.crocus.co.kr/1362</a></li>
<li>TCP 상태 전이도: <a href="http://www.ktword.co.kr/test/view/view.php?no=657">http://www.ktword.co.kr/test/view/view.php?no=657</a></li>
<li>TCP 흐름제어(슬라이딩 윈도우): <a href="https://steady-coding.tistory.com/507">https://steady-coding.tistory.com/507</a></li>
<li>혼잡 윈도우 변화에 따른 그래프: <a href="https://blog.skby.net/tcp-%ED%98%BC%EC%9E%A1%EC%A0%9C%EC%96%B4/">https://blog.skby.net/tcp-%ED%98%BC%EC%9E%A1%EC%A0%9C%EC%96%B4/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공네트] 3주차_네트워크 계층]]></title>
            <link>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-3%EC%A3%BC%EC%B0%A8%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-3%EC%A3%BC%EC%B0%A8%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Tue, 22 Jul 2025 15:20:46 GMT</pubDate>
            <description><![CDATA[<h1 id="lan-넘어가기">LAN 넘어가기</h1>
<h2 id="데이터-링크-계층의-한계">데이터 링크 계층의 한계</h2>
<ul>
<li><p>다른 네트워크까지의 도달 경로를 알기 어렵다.</p>
<ul>
<li>라우팅(routing): 패킷이 이동할 최적의 장비를 결정하는 것</li>
<li>물리 계층, 데이터 링크 계층의 장비로는 라욷팅을 수행할 수 없다.</li>
</ul>
</li>
<li><p>MAC 주소로 호스트를 트겅하기 어렵다.</p>
<table>
<thead>
<tr>
<th>MAC 주소</th>
<th>IP 주소</th>
</tr>
</thead>
<tbody><tr>
<td>물리 주소</td>
<td>논리 주소</td>
</tr>
<tr>
<td>NIC마다 할당(고정 주소)</td>
<td>DHCP or 사용자가 직접 할당</td>
</tr>
</tbody></table>
</li>
</ul>
<h2 id="인터넷-프로토콜-ip-internet-protocol">인터넷 프로토콜 (IP, Internet Protocol)</h2>
<p>네트워크 계층의 프로토콜로 IPv4와 IPv6만 있다.
다만 아래에서는 IPv4만 정리할 예정이다.</p>
<p><strong>기능</strong></p>
<ul>
<li>IP 주소 지정 (addressing): IP 주소를 바탕으로 송수신 대상 지정</li>
<li>IP 단편화 (fragmentation): MTU(Maximum Transmission Unit) 보다 패킷 크기가 크면 여러 개의 패킷으로 나누어 전송</li>
</ul>
<p><strong>IPv4</strong></p>
<ul>
<li><p>4byte로 표현</p>
<ul>
<li>e.g.) 192.168.1.1</li>
</ul>
</li>
<li><p>패킷 구조
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/2107dd15-cb52-47f4-baf1-260c8dbbe383/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>영어</th>
<th>한국어</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Identifier</td>
<td>식별자</td>
<td>패킷에 할당된 번호 <br> 패킷이 여러 조각으로 나누어져 전송된 경우 어떤 메시지에서부터 쪼개졌는지 인식하기 위한 용도</td>
</tr>
<tr>
<td>Flags</td>
<td>플래그</td>
<td>순서대로 0 DF MF 비트로 구성된 필드 <br> DF(Don&#39;t Fragment): 1이면 단편화 불가 / 0이면 단편화 가능 <br> MF(More Fragment): 1이면 쪼개진 패킷이 더 있음 / 0이면 마지막 패킷</td>
</tr>
<tr>
<td>Fragment offset</td>
<td>단편화 오프셋</td>
<td>초기 데이터에서 몇 번째로 떨어진 패킷인지를 나타내는 필드</td>
</tr>
<tr>
<td>TTL(Time to Live)</td>
<td>-</td>
<td>패킷이 하나의 라우터를 거칠 때마다 == 한 홉(hop)마다 1씩 감소 <br> 무의미한 패킷이 네트워크 상에 남아있는 것을 방지함</td>
</tr>
<tr>
<td>Protocol</td>
<td>프로토콜</td>
<td>상위 계층의 프로토콜 <br> e.g.) TCP는 6번, UDP는 17번</td>
</tr>
</tbody></table>
</li>
</ul>
<h2 id="arp-address-resolution-protocol">ARP (Address Resolution Protocol)</h2>
<p>IP 주소를 통해 MAC 주소를 알아내는 프로토콜</p>
<p><strong>처리 과정</strong>
호스트 A가 호스트 B의 MAC 주소를 알고싶어하는 상황</p>
<ol>
<li>ARP 요청 (ARP request)<ul>
<li>A가 브로드캐스트 메시지 전송</li>
</ul>
</li>
<li>ARP 응담 (ARP reply)<ul>
<li>B를 제외한 호스트는 응답 X</li>
<li>B만 유니캐스트 메시지 전송</li>
</ul>
</li>
<li>ARP 테이블 갱신</li>
</ol>
<br>

<p>만약 호스트 A와 B가 다른 네트워크에 있다면?
⇒ ARP 테이블에는 라우터의 MAC 주소가 저장된다.</p>
<h1 id="ip-주소">IP 주소</h1>
<h2 id="네트워크-주소와-호스트-주소">네트워크 주소와 호스트 주소</h2>
<h3 id="클래스풀-주소-체계classful-addressing">클래스풀 주소 체계(classful addressing)</h3>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/cd57d9c3-178b-4ff2-907f-08ff2804c568/image.png" alt=""></p>
<ul>
<li>A의 경우 호스트가 할당되지 않은 IP 주소가 낭비될 수 있다.</li>
<li>C의 경우 호스트가 사용할 IP 주소가 부족해질 수 있다.</li>
</ul>
<h3 id="클래스리스-주소-체계classless-addressing">클래스리스 주소 체계(classless addressing)</h3>
<p>클래스풀 주소 체계 보다 정교하게 표현하는 방법으로 8bit 단위로 네트워크/호스트 주소가 구분되지 않고 원하는 크기로 잘게 쪼개어 사용할 수 있다.</p>
<ul>
<li><p>서브넷 마스크(subnet mask)로 서브네팅하기</p>
<table>
<thead>
<tr>
<th></th>
<th>10진수</th>
<th>bit</th>
</tr>
</thead>
<tbody><tr>
<td>IP 주소</td>
<td>192.168.219.103</td>
<td>11000000.10101000.11011011.01100111</td>
</tr>
<tr>
<td>서브넷 마스크</td>
<td>255.255.255.0</td>
<td>11111111.11111111.11111111.00000000</td>
</tr>
<tr>
<td>AND 연산의 결과 <br> (네트워크 주소)</td>
<td>192.168.219.0</td>
<td>11000000.10101000.11011011.00000000</td>
</tr>
</tbody></table>
<ul>
<li>8비트로 호스트를 표현할 수 있다.</li>
<li>네트워크 주소: 192.168.219.0</li>
<li>브로드캐스트 주소: 192.168.219.255</li>
<li>호스트는 2<sup>8</sup> - 2 = 254개</li>
</ul>
</li>
</ul>
<ul>
<li>CIDR 표기법<ul>
<li>&lt;IP 주소/서브넷 마스크 상의 1의 개수&gt; 로 클래스리스 주소 체계를 표현할 수 있다.</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>IP 주소</th>
<th>서브넷 마스크</th>
<th>CIDR</th>
<th>네트워크 주소</th>
<th>호스트 수</th>
</tr>
</thead>
<tbody><tr>
<td>192.168.219.130</td>
<td>255.255.255.0</td>
<td>192.168.219.130/24</td>
<td>192.168.219.0</td>
<td>2<sup>8</sup> - 2 = 254개</td>
</tr>
<tr>
<td>192.168.219.130</td>
<td>255.255.255.128</td>
<td>192.168.219.130/25</td>
<td>192.168.219.128</td>
<td>2<sup>7</sup> - 2 = 126개</td>
</tr>
</tbody></table>
<h2 id="공인-ip와-사설-ip">공인 IP와 사설 IP</h2>
<h3 id="공인-ip">공인 IP</h3>
<p>전 세계에서 고유한 IP  주소를 말한다.</p>
<h3 id="사설-ip">사설 IP</h3>
<p>LAN 내에서 사용하는 IP 주소를 말하며 라우터(router)가 할당한다.
NAT(Network Address Translation)을 거쳐 공인 IP로 변경 ⇒ 외부 호스트와 통신한다.
  e.g.) 171.16.0.0/12, 168.192.0.0/16 </p>
<h2 id="정적-ip와-동적-ip">정적 IP와 동적 IP</h2>
<ul>
<li>정적 할당<ul>
<li>호스트에 직접 부여한 IP</li>
</ul>
</li>
<li>동적 할당<ul>
<li>사용자가 IP를 직접 입력하지 않아도 자동으로 IP를 할당하는 방식</li>
<li><strong>DHCP(Dynamic Host Configuration Protocol)</strong>로 IP를 할당한다.</li>
</ul>
</li>
</ul>
<br>

<p><strong>DHCP로 IP 할당하기</strong></p>
<ul>
<li>DHCP 서버 (일반적으로 라우터 또는 특정 호스트)와 호스트 간에 메시지를 주고 받으며 동적으로 IP가 할당된다.</li>
<li>IP 주소 임대 시간이 정해져 있다.</li>
<li>임대 기간이 끝나면 IP를 반납하고 재할당 받거나 갱신해야한다.</li>
</ul>
<ol>
<li>DHCP Discover (호스트 → DHCP 서버)<ul>
<li>브로드캐스트로 전송</li>
<li>송신지 IP는 0.0.0.0</li>
<li>&quot;나 DHCP 서버 찾고 있어!!&quot;</li>
</ul>
</li>
<li>DHCP Offer (DHCP 서버 → 호스트)<ul>
<li>&quot;IP 주소, 서브넷 마스크, 임대 기간 등을 제안할께!&quot;</li>
</ul>
</li>
<li>DHCP Request (호스트 → DHCP 서버)<ul>
<li>브로드캐스트로 전송</li>
<li>&quot;Offer 잘 받았어. 이 IP 주소 써도 되는거지?&quot;</li>
</ul>
</li>
<li>DHCP ACK (DHCP 서버 → 호스트)<ul>
<li>&quot;승인 완료&quot;</li>
</ul>
</li>
</ol>
<h1 id="라우팅">라우팅</h1>
<p>라우팅이라 패킷이 이동할 최적의 경로를 설정하여 패킷을 이동시키는 과정을 말한다.</p>
<h2 id="라우터와-라우팅-테이블">라우터와 라우팅 테이블</h2>
<p>라우터는 네트워크 계층에서 사용하는 장비이다. 집에 있는 공유기가 라우터의 역할을 한다.</p>
<ul>
<li>패킷이 하나의 라우터를 거치는 과정을 <strong>홉(hop)</strong>이라고 한다.</li>
</ul>
<br> 

<p>라우팅 테이블에선 아래와 같은 정보를 얻을 수 있다.</p>
<ul>
<li>수신지 IP 주소와 서브넷마스크</li>
<li>다음 홉/게이트웨이: 다음으로 거쳐야 할 호스트의 IP 주소나 인터페이스</li>
<li>네트워크 인터페이스: 패킷을 보낼 통로</li>
<li>메트릭(metric): 해당 경로로 이동하는 데에 드는 비용
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/ef656b96-61f8-4d0f-b7c3-84481fd655fc/image.png" alt=""></li>
</ul>
<h2 id="정적-라우팅과-동적-라우팅">정적 라우팅과 동적 라우팅</h2>
<p>정적 라우팅은 사용자가 직접 라우팅 테이블을 채워 넣어 라우팅되는 방식을 말한다.</p>
<p>동적 라우팅은 자동으로 라우팅 테이블을 만들고 활용하는 방식이다.
AS 내부에서 수행되는 지 여부에 따라 라우팅 프로토콜이 나뉜다.</p>
<h2 id="라우팅-프로토콜">라우팅 프로토콜</h2>
<p>AS 내부에서 수행되면 IGP, 외부에서 수행되면 EGP라고 한다.</p>
<p><strong>AS(Autonomous System)란?</strong>
하나의 관리 도메인 안에 있는 라우터의 집합을 AS라고 한다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/426111ac-0aa0-4bfd-ac15-515cca4f6ade/image.png" alt=""></p>
<h3 id="igpinterior-gateway-protocol">IGP(Interior Gateway Protocol)</h3>
<ul>
<li>RIP(Routing Information Protocol)<ul>
<li>거리 벡터 기반</li>
<li>홉 수가 가장 적은 경로를 최적의 경로라고 판단</li>
<li>주기적으로 라우팅 테이블 갱신</li>
</ul>
</li>
<li>OSPF(Open Shortest Path First)<ul>
<li>링크 상태 기반</li>
<li>현재 네트워크의 상태를 그래프의 형태로 최적의 경로 판단</li>
<li>대역폭을 기반으로 메트릭 계산</li>
<li>AS 내 정보가 바뀔 때마다 정보 변경이 이루어진다.</li>
</ul>
</li>
</ul>
<h3 id="egpexterior-gateway-protocol">EGP(Exterior Gateway Protocol)</h3>
<ul>
<li>BGP(Border Gateway Protocol)<ul>
<li>피어링을 통해 다른 AS의 BGP 라우터와 피어 상태를 유지</li>
</ul>
</li>
</ul>
<br> 

<h1 id="숙제">숙제</h1>
<h2 id="필수-숙제">필수 숙제</h2>
<ol>
<li>Ch.03-1 확인 문제 1번
② IP 주소 지정
③ IP 단편화</li>
<li>Ch.03-3 확인 문제 2번
라우팅 프로토콜은 AS 내부에서 수행되는 IGP와 AS 외부에서 수행되는 EGP로 나뉩니다. RIP는 대표적인 거리 벡터 라우팅 프로토콜이고, OSPF는 대표적인 링크 상태 라우팅 프로토콜입니다.</li>
</ol>
<hr>
<p>&lt;참고 자료&gt;</p>
<ul>
<li>IP 패킷 구조 이미지: <a href="https://blog.siner.io/2021/12/13/network-ipv4-packet/">https://blog.siner.io/2021/12/13/network-ipv4-packet/</a></li>
<li>클래스풀 주소 체계: <a href="http://www.ktword.co.kr/test/view/view.php?no=2461">http://www.ktword.co.kr/test/view/view.php?no=2461</a></li>
<li>라우터와 라우팅 테이블: <a href="https://geek-university.com/routing-table-explained/">https://geek-university.com/routing-table-explained/</a></li>
<li>AS: <a href="http://www.ktword.co.kr/">http://www.ktword.co.kr/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공네트] 2주차_물리 계층과 데이터 링크 계층]]></title>
            <link>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-2%EC%A3%BC%EC%B0%A8%EB%AC%BC%EB%A6%AC-%EA%B3%84%EC%B8%B5%EA%B3%BC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%A7%81%ED%81%AC-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-2%EC%A3%BC%EC%B0%A8%EB%AC%BC%EB%A6%AC-%EA%B3%84%EC%B8%B5%EA%B3%BC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%A7%81%ED%81%AC-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Sat, 12 Jul 2025 11:51:33 GMT</pubDate>
            <description><![CDATA[<p>이번 주에 살펴 볼 대표적인 키워드는 아래와 같다.</p>
<ul>
<li>이더넷</li>
<li>MAC 주소</li>
<li>NIC</li>
<li>허브 / 스위치</li>
<li>반이중(half duplex) / 전이중(full duplex)</li>
</ul>
<h1 id="이더넷ethernet">이더넷(Ethernet)</h1>
<p>이더넷이란 다양한 통신 매체의 규격들과 송수신되는 프레임 형태, 프레임을 주고받는 방법 등이 정의된 네트워크 기술이다.</p>
<p>이더넷 표준에 맞추어 통신 =&gt; 서로 다른 네트워크 장비 통신</p>
<h2 id="이더넷-프레임">이더넷 프레임</h2>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/53ef4318-6c04-4978-97e2-1e87f49fea8a/image.png" alt=""></p>
<ul>
<li>프리앰블 + SFD
프리앰블은 10101010, SFD는 10101011
프리앰블이 오는 동안 1과 0이 계속 반복되며 이더넷 프레임이 오고있음을 알 수 있다.</li>
<li>DA(Destination Address), SA(Source Address)
MAC 주소</li>
<li>Data
최소 46byte / 최대 1500byte
46byte보다 작은 경우 Padding(뒤에 붙은 0)을 넣음</li>
<li>FCS(Frame Check Sequence
수납된 데이터의 에러검출을 위한 CRC(Cydle Redundancy Check)</li>
</ul>
<h2 id="nic">NIC</h2>
<p>NIC(Network Interface Controller)는 MAC 주소가 부여되는 네트워크 장비이다.</p>
<p><strong>역할</strong></p>
<ul>
<li>통신 매체를 통해 전달되는 신호 ↔ 컴퓨터가 이해할 수 있는 정보</li>
<li>수신된 프레임의 MAC 주소가 자신의 것이 아니라면 폐기</li>
<li>FCS 필드를 토대로 오류 검출</li>
</ul>
<h2 id="허브">허브</h2>
<p>대표적인 물리계층의 장비이다.</p>
<p><strong>특징</strong></p>
<ul>
<li>물리 계층에는 주소 개념이 없다. =&gt; 전달 받은 신호를 그대로 내보낸다.
송수신된 내용을 조작이나 판단하지 않는다.</li>
<li>반이중 모드로 통신한다. =&gt; 동시에 송수신이 불가능하다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/34c5b63a-e229-44e1-bbe7-44960b7237f5/image.png" alt="">
한 허브에 여러 호스트 장비가 연결 -&gt; 여러 호스트 장비에서 허브에 송신 -&gt; 허브에 여러 신호가 동시에 도착 -&gt; <strong>충돌(collision)</strong> 발생!!
=&gt; 한 허브에 연결된 여러 호스트가 같은 <strong>collision domain</strong>에 속한다고 한다.</li>
</ul>
<h3 id="csmacd">CSMA/CD</h3>
<p>허브가 반이중 모드로 통신해서 충돌이 발생할 수 있다.</p>
<p>충돌 문제를 해결하기 위해 CSMA/CD 라는 프로토콜을 사용한다.</p>
<ul>
<li>CA (Carruer Sense) : 캐리어 감지
호스트는 메시지를 보내기 전에 현재 네트워크 상에 전송 중인 것이 있는 지 확인한다.</li>
<li>MA (Multiple Access) : 다중 접근
여러 호스트가 네트워크에 접근하려는 상황에서</li>
<li>CD (Collision Detection) : 충돌 검출
충돌이 발생하면 호스트에게 충돌이 발생했음을 알리고 호스트는 임의의 시간만큼 대기한 뒤 다시 전송한다.</li>
</ul>
<h2 id="스위치">스위치</h2>
<p>데이터 링크 계층에서는 <strong>MAC 주소</strong>라는 주소체계를 사용한다. 물리 계층과 다르게 <strong>전이중 모델</strong>을 사용한다.</p>
<h3 id="mac주소-학습">MAC주소 학습</h3>
<p>스위치가 연결된 포트의 주소를 학습하는 방법은 다음과 같은 과정을 통해 일어난다.</p>
<ul>
<li>플러딩</li>
<li>포워딩과 필터링</li>
<li>에이징</li>
</ul>
<ol>
<li><p>A -&gt; C로 프레임을 전송하는 상황이다. 최초의 스위치는 아무 주소도 알지 못한다. </p>
<table>
<thead>
<tr>
<th>포트</th>
<th>주소</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td></td>
</tr>
<tr>
<td>B</td>
<td></td>
</tr>
<tr>
<td>C</td>
<td></td>
</tr>
<tr>
<td>D</td>
<td></td>
</tr>
</tbody></table>
</li>
<li><p>A -&gt; 스위치: A의 MAC주소를 MAC 주소 테이블에 기록한다.</p>
<table>
<thead>
<tr>
<th>포트</th>
<th>주소</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>ab:cd:ab:cd:00:01</td>
</tr>
<tr>
<td>B</td>
<td></td>
</tr>
<tr>
<td>C</td>
<td></td>
</tr>
<tr>
<td>D</td>
<td></td>
</tr>
</tbody></table>
</li>
<li><p>플러딩(flooding): A를 제외한 모든 포트에 프레임을 전송한다.</p>
</li>
<li><p>C가 A의 메시지에 응답한다.</p>
<table>
<thead>
<tr>
<th>포트</th>
<th>주소</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>ab:cd:ab:cd:00:01</td>
</tr>
<tr>
<td>B</td>
<td></td>
</tr>
<tr>
<td>C</td>
<td>ab:cd:ab:cd:00:03</td>
</tr>
<tr>
<td>D</td>
<td></td>
</tr>
</tbody></table>
</li>
<li><p>A -&gt; C에 프레임을 다시 한 번 보낸다.</p>
<ul>
<li>필터링(filtering): B, D로는 프레임이 전송되지 않는다.</li>
<li>포워딩(fowarding): C에 프레임을 전송한다.</li>
</ul>
</li>
<li><p>에이징(aging): 특정 포트가 일정 시간동안 메시지를 받지 못했다면 MAC 주소 테이블에서 제거된다.</p>
</li>
</ol>
<h1 id="숙제">숙제</h1>
<h2 id="필수-숙제">필수 숙제</h2>
<ol>
<li>Ch.02-1 확인 문제 2번
㉠ 프리앰블
㉡ 송신지 MAC 주소
㉢ FCS</li>
<li>Ch.02-3확인 문제 4번
① CS (Carrier Sense) -&gt; 캐리어 감지
② MA (Multiple Access) -&gt; 다중 접근
③ CD (Collision Detection) -&gt; 충돌 검출</li>
</ol>
<hr>
<p>&lt;참고 자료&gt;</p>
<ul>
<li>참고문헌: &#39;데이터통신&#39;, 오창환 저, 한국학술정보(주)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공네트] 1주차_네트워크 살펴보기]]></title>
            <link>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-1%EC%A3%BC%EC%B0%A8%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@pyh-dotcom/%ED%98%BC%EA%B3%B5%EB%84%A4%ED%8A%B8-1%EC%A3%BC%EC%B0%A8%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 06 Jul 2025 14:25:42 GMT</pubDate>
            <description><![CDATA[<p>학부생 때 배웠던 네트워크를 다 까먹었다.. 업무를 하면서 이야기를 하다보니 내가 너무 모른다는 느낌이 들어 혼공학습단을 통해 간단하고 빠르게 복습하기로 했다.</p>
<h1 id="네트워크-기초">네트워크 기초</h1>
<h2 id="용어-정리">용어 정리</h2>
<ul>
<li><strong>호스트(Host)</strong><ul>
<li>가장자리 노드</li>
<li>최초로 정보를 생성, 송신 + 최종적으로 수신</li>
<li>e.g) 서버 컴퓨터, 데스크톱 등</li>
</ul>
</li>
</ul>
<h2 id="네트워크-분류">네트워크 분류</h2>
<ol>
<li><p>범위에 따른 분류</p>
<ul>
<li><p><strong>LAN(Local Area Network)</strong></p>
<ul>
<li>근거리 통신망</li>
<li>e.g) 회사 내부망</li>
</ul>
</li>
<li><p><strong>WAN(Wide Area Network)</strong></p>
<ul>
<li>광역 통신망</li>
</ul>
</li>
</ul>
</li>
<li><p>메시지 교환 방식에 따른 분류</p>
<ul>
<li><strong>회선 교환 방식</strong><ul>
<li>두 호스트를 연결하여 해당 경로로 통신</li>
<li>단점) 회선 효율 낮음 - 연결되지 않은 회선은 낭비<ul>
<li><strong>패킷 교환 방식</strong></li>
</ul>
</li>
<li>메시지를 패킷 단위로 쪼개어 전송</li>
<li>장점) 회선을 점유하지 않아 네트워크 효율 높음</li>
<li>네트워크 장비) 라우터, 스위치</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="osi-7계층과-tcpip-4계층">OSI 7계층과 TCP/IP 4계층</h1>
<h2 id="용어-정리-1">용어 정리</h2>
<ul>
<li><strong>Protocol</strong><ul>
<li>노드 간에 정보를 올바르게 주고받기 위해 합의된 규칙이나 방법</li>
<li>e.g) IP, ARP, HTTP, TCP 등</li>
</ul>
</li>
</ul>
<h2 id="네트워크-모델">네트워크 모델</h2>
<table>
<thead>
<tr>
<th>OSI</th>
<th>TCP/IP</th>
</tr>
</thead>
<tbody><tr>
<td>응용 계층</td>
<td>응용 계층</td>
</tr>
<tr>
<td>표현 계층</td>
<td>-</td>
</tr>
<tr>
<td>세션 계층</td>
<td>-</td>
</tr>
<tr>
<td>전송 계층</td>
<td>전송 계층</td>
</tr>
<tr>
<td>네트워크 계층</td>
<td>인터넷 계층</td>
</tr>
<tr>
<td>데이터 링크 계층</td>
<td>네트워크 엑세스 계층</td>
</tr>
<tr>
<td>물리 계층</td>
<td>네트워크 엑세스 계층</td>
</tr>
</tbody></table>
<h3 id="osi-모델">OSI 모델</h3>
<ul>
<li><strong>physical layer(물리 계층)</strong><ul>
<li>비트 신호 전달</li>
</ul>
</li>
<li><strong>data link layer(데이터 링크 계층)</strong><ul>
<li>MAC 주소 사용</li>
<li>물리 계층을 통해 주고 받는 정보 확인</li>
</ul>
</li>
<li><strong>network layer(네트워크 계층)</strong><ul>
<li>다른 네트워크에 속한 수신지까지 전달</li>
<li>최적의 경로 설정</li>
</ul>
</li>
<li><strong>transport layer(전송 계층)</strong><ul>
<li>패킷 흐름 제어 및 전송 오류 점검</li>
<li>신뢰성 있고 안정성 있는 정보를 전달할 때 필요</li>
</ul>
</li>
<li><strong>session layer(세션 계층)</strong><ul>
<li>연결 상태 생성 유지 및 종료 시에 연결 종료</li>
</ul>
</li>
<li><strong>presentation layer(표현 계층)</strong><ul>
<li>문자 -&gt; 코드, 압축, 암호화 등</li>
</ul>
</li>
<li><strong>application layer(응용 계층)</strong><ul>
<li>실제 사용자가 이해할 수 있는 형태의 응용 프로그램</li>
<li>e.g) 웹 페이지 제공, 이메일 서비스</li>
</ul>
</li>
</ul>
<h3 id="tcpip-모델">TCP/IP 모델</h3>
<ul>
<li><strong>network access layer(네트워크 엑세스 계층)</strong><ul>
<li>physical layer + data link layer 와 유사</li>
</ul>
</li>
<li><strong>internet layer(인터넷 계층)</strong><ul>
<li>network layer와 유사</li>
</ul>
</li>
<li><strong>transport layer(전송 계층)</strong></li>
<li><strong>application layer(응용 계층)</strong><ul>
<li>session layer + presentation layer + application layer</li>
</ul>
</li>
</ul>
<h3 id="캡슐화와-역캡슐화">캡슐화와 역캡슐화</h3>
<ul>
<li><strong>캡슐화(incapsulation)</strong><ul>
<li>송신과정에서 헤더 및 트레일러를 추가해나가는 과정</li>
<li>응용 계층 -&gt; 물리 계층 방향으로 데이터 추가</li>
</ul>
</li>
<li><strong>역캡슐화(decapsulation)</strong><ul>
<li>수신과정에서 헤더 및 트레일러 확인 후 제거</li>
<li>물리 계층 -&gt; 응용 계층 방향으로 데이터 제거</li>
</ul>
</li>
</ul>
<h3 id="pduprotocol-data-unit">PDU(Protocol Data Unit)</h3>
<ul>
<li>각 계층에서 송수신 되는 메시지 단위</li>
</ul>
</br>

<table>
<thead>
<tr>
<th>OSI 계층</th>
<th>PDU</th>
</tr>
</thead>
<tbody><tr>
<td>응용, 표현, 세션 계층</td>
<td>data</td>
</tr>
<tr>
<td>전송 계층</td>
<td>[TCP]  세그먼트 (segment), [UDP] 데이터그램 (datagram)</td>
</tr>
<tr>
<td>네트워크 계층</td>
<td>packet</td>
</tr>
<tr>
<td>데이터 링크 계층</td>
<td>frame</td>
</tr>
<tr>
<td>물리 계층</td>
<td>bit</td>
</tr>
</tbody></table>
<h2 id="네트워크-성능-지표">네트워크 성능 지표</h2>
<ul>
<li><strong>처리율(throughput)</strong><ul>
<li>단위 시간당 네트워크를 통해 실제로 전송되는 정보량</li>
<li>bps, Mbps</li>
</ul>
</li>
<li><strong>대역폭(bandwidth)</strong><ul>
<li>단위 시간 동안 송수신할 수 있는 최대 정보량</li>
</ul>
</li>
<li><strong>패킷 손실(packet loss)</strong></li>
</ul>
<h1 id="숙제">숙제</h1>
<h2 id="추가-숙제">추가 숙제</h2>
<ol>
<li>CH01-1 확인 문제 2번
여러 장치가 연결되어 정보를 주고받을 수 있는 통신망을 (컴퓨터 네트워크)라고 합니다.</li>
<li>CH01-3 확인 문제 2번
2번, TCP/IP 모델은 4개의 계층으로 통신 과정을 구분합니다.
network access, internet, transport, application이 있다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 동시성 문제 해결3: Redis💾]]></title>
            <link>https://velog.io/@pyh-dotcom/spring-concurrency-issue-redis</link>
            <guid>https://velog.io/@pyh-dotcom/spring-concurrency-issue-redis</guid>
            <pubDate>Mon, 05 Feb 2024 08:46:51 GMT</pubDate>
            <description><![CDATA[<h1 id="redis를-통해-동시성-이슈-해결">Redis를 통해 동시성 이슈 해결</h1>
<h2 id="🎤-대표적인-라이브러리">🎤 대표적인 라이브러리</h2>
<ol>
<li><strong>Lettuce</strong><ul>
<li><code>setnx</code> (set if not exist) 명령어를 활용하여 분산락 구현<ul>
<li>key와 value를 set할 때, 기존의 값이 없을 때만 set을 진행</li>
</ul>
</li>
<li><u>spin lock 방식</u><ul>
<li>lock을 획득하려는 thread가 lock을 사용할 수 있는지 반복적으로 확인하면서 lock 획득
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/c3a529ed-e941-48ab-8d5c-bd40a368465c/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><strong>Redisson</strong><ul>
<li><u>pub-sub 기반의 lock</u><ul>
<li>채널을 하나 만들고 락을 점유 중인 thread가 락을 해제할 때, 해당 lock을 획득하려고 대기 중인 thread에게 해제를 알려줌</li>
<li>lettuce와 다르게 락 획득 로직을 개발자가 작성할 필요 없음
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/a6cc2931-08c0-4e0d-858a-ac8325f8ad95/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="🥬-lettuce">🥬 Lettuce</h2>
<p>Redis에서 <code>setnx</code>를 통해 키를 획득하는 방식이다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/f7d1c698-0fff-4521-a8c4-587a59276cae/image.png" alt=""></p>
<h3 id="적용-과정">적용 과정</h3>
<ol>
<li><p>Redis dependency 추가
<code>implementation &#39;org.springframework.boot:spring-boot-starter-data-redis&#39;</code></p>
</li>
<li><p>Redis 명령어를 활용하기 위한 redis repository 생성</p>
<pre><code class="language-java">public Boolean lock(Long key) {
  return redisTemplate
          .opsForValue()
          .setIfAbsent(generateKey(key), &quot;lock&quot;, Duration.ofMillis(3_000));
}

public Boolean unlock(Long key) {
  return redisTemplate
          .delete(generateKey(key));
}

private String generateKey(Long key) {
  return key.toString();
}</code></pre>
</li>
<li><p>lock의 획득과 해제를 위한 facade 정의</p>
<pre><code class="language-java"> public void decrease(Long id, Long quantity) throws InterruptedException {
   while (!redisLockRepository.lock(id)) {
     Thread.sleep(100);
   }

   try {
       stockService.decreaseStock(id, quantity);
   } finally {
       redisLockRepository.unlock(id);
   }
}</code></pre>
</li>
</ol>
<h3 id="장·단점">장·단점</h3>
<p><strong>장점</strong></p>
<ul>
<li>구현이 단순하다.</li>
<li>spring data redis를 이용하면 lettuce가 기본이므로 별도의 라이브러리를 사용할 필요가 없다.</li>
<li><em>단점*</em></li>
<li>spin lock 방식이므로 redis에 부하를 줄 수 있다.
 ⇒ <code>Thread.sleep()</code>을 통해 락 획득 재시도에 텀을 주어야하므로</li>
</ul>
<h2 id="🚀-redisson">🚀 Redisson</h2>
<p>Redis가 채널을 통해 통신하고 있는 것이다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/e9b787d2-dfb5-49f2-aae6-ba52ae56598a/image.png" alt="">
Redisson은 자신이 점유하고 있는 락을 해제할 때 <strong>채널에 메세지를 보내줌</strong>으로써 다른 thread에게 락을 획득하라고 전달한다.</p>
<h3 id="적용-과정-1">적용 과정</h3>
<ol>
<li><p>Redisson dependency 추가
<code>implementation &#39;org.redisson:redisson-spring-boot-starter:3.25.2&#39;</code>
해당 의존성에서 lock과 관련된 라이브러리를 제공해주므로 별도의 repository를 작성할 필요가 없다.</p>
</li>
<li><p>lock 획득과 해제를 위한 facade 정의</p>
<pre><code class="language-java">RLock lock = redissonClient.getLock(id.toString());

try {
  boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS);

  if (!available) {
      System.out.println(&quot;lock 획득 실패&quot;);
      return;
  }
  stockService.decreaseStock(id, quantity);
} catch (InterruptedException e) {
  throw new RuntimeException(e);
} finally {
  lock.unlock();
}</code></pre>
</li>
</ol>
<h3 id="장·단점-1">장·단점</h3>
<p><strong>장점</strong></p>
<ul>
<li>라이브러리에서 락 획득 재시로를 기본으로 제공한다.</li>
<li>pub-sub 기반으로 redis의 부하를 줄여준다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>구현이 복잡하다.</li>
<li>별도의 라이브러리 활용 필요하다.</li>
</ul>
<h2 id="⚖️-실무에서의-활용-방안">⚖️ 실무에서의 활용 방안</h2>
<h3 id="lettuce-vs-redisson">Lettuce vs. Redisson</h3>
<p>실무에서는 재시도의 필요성에 따라 혼용하여 사용한다.</p>
<p>재시도가 필요하지 않은 락 ⇒ lettuce
재시도가 필요한 경우 ⇒ redisson</p>
<h3 id="mysql-vs-redis">MySQL vs. Redis</h3>
<p><strong>MySQL</strong></p>
<ul>
<li>이미 MySQL을 사용하고 있다면 별도의 비용이 필요없다.</li>
<li>어느 정도의 트래픽까지는 문제없이 활용이 가능하다.</li>
</ul>
<p><strong>Redis</strong></p>
<ul>
<li>활용중인 Redis가 없다면 별도의 구축비용과 인프라 관리 비용이 발생한다.</li>
<li>MySQL보다 성능이 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 동시성 이슈 해결2: Database💽]]></title>
            <link>https://velog.io/@pyh-dotcom/spring-concurrency-issue-database</link>
            <guid>https://velog.io/@pyh-dotcom/spring-concurrency-issue-database</guid>
            <pubDate>Tue, 23 Jan 2024 01:28:30 GMT</pubDate>
            <description><![CDATA[<h1 id="database의-lock을-통해-해결">Database의 Lock을 통해 해결</h1>
<h2 id="🔒-다양한-lock의-종류">🔒 다양한 lock의 종류</h2>
<ol>
<li><p><strong>Pessimistic lock (비관적 락)</strong></p>
<ul>
<li>실제로 데이터에 Lock을 걸어 정합성을 맞추는 방법이다.</li>
<li>Exclusive lock(베타적 잠금)이 걸리면 다른 트랜잭션에서는 lock이 해제되기 전까지 데이터를 가져갈 수 없다.</li>
<li>다만, 데드락이 걸리는 상황에 주의해서 사용해야한다.</li>
</ul>
<p>⇒ 자원을 요청하면 <strong>동시성 문제가 발생할 것을 예상하고 lock을 걸어버리는 비관적 락</strong>이다.</p>
</li>
<li><p><strong>Optimistic lock (낙관적 락)</strong></p>
<ul>
<li><strong>버전</strong>을 통해 정합성을 맞추는 방법이다.</li>
<li>데이터를 읽은 후에 update 를 수행할 때 현재 내가 읽은 버전이 맞는지 확인하며 업데이트를 한다.</li>
<li>읽은 버전에서 수정사항이 생겼을 경우에는 application에서 다시 읽은 후에 작업을 수행해야 한다.</li>
</ul>
<p>⇒ 자원에 락을 걸어 선점하기보단, <strong>동시성 문제가 발생했을 때 감지하고 처리하는 낙관적 락</strong>이다.</p>
</li>
<li><p><strong>Named lock</strong></p>
<ul>
<li>이름을 가진 metadata locking이다.</li>
<li><strong>이름을 가진 lock을 획득</strong>한 후 해제할 때까지 다른 세션은 이 lock을 획득할 수 없다.</li>
<li>transaction이 종료될 때 lock이 자동으로 해지되지 않아 별도의 명령어로 해제하거나 선점 시간이 끝나야 해제된다.</li>
</ul>
</li>
</ol>
<h2 id="😒-pessimistic-lock-활용">😒 Pessimistic lock 활용</h2>
<h3 id="적용-과정">적용 과정</h3>
<p>Spring data jpa를 활용하면 <strong><code>@Lock</code>을 통해 손쉽게 pessimistice lock을 구현</strong>할 수 있다.</p>
<pre><code class="language-java">public interface StockRepository extends JpaRepository&lt;Stock, Long&gt; {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;select s from Stock s where s.id = :id&quot;)
    Stock findByIdWithPessimisticLock(Long id);
}</code></pre>
<h3 id="결과-확인">결과 확인</h3>
<p>Test가 성공적으로 실행되며, 실행 중 <code>for update</code>라는 부분이 <strong>lock을 걸고 데이터를 가져오는 부분</strong>이다. 
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/a8643c47-c543-4520-9ca5-3ac3341a02ef/image.png" alt=""></p>
<h3 id="장·단점">장·단점</h3>
<p><strong>장점</strong></p>
<ul>
<li><strong>충돌이 빈번한 경우</strong> 롤백의 횟수를 줄일 수 있고, Optimistic lock보다 성능이 좋을 수 있다.</li>
<li>lock을 통해 업데이터를 제어하므로 <strong>데이터의 정합성이 보장</strong>된다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>별도로 lock을 걸어야 하므로 성능 감소가 있을 수 있다.</li>
<li>서로의 자원이 필요한 경우, 데드락이 발생할 수 있다.</li>
</ul>
<h2 id="🌞-optimistic-lock-활용">🌞 Optimistic lock 활용</h2>
<h3 id="적용-과정-1">적용 과정</h3>
<ol>
<li><p>Optimistic lock을 활용하기 위해 <code>Stock Entity</code>에 <strong>version이라는 attribute</strong>을 추가한다.</p>
<pre><code class="language-java">@Version
private Long version;</code></pre>
</li>
<li><p>Spring Data JPA에서 제공하는 <strong><code>@Lock</code>을 통해 Optimistic lock을 구현</strong>한다.</p>
<pre><code class="language-java">@Lock(LockModeType.OPTIMISTIC)
@Query(&quot;select s from Stock s where s.id = :id&quot;)
Stock findByIdWithOptimisticLock(Long id);</code></pre>
</li>
<li><p>Optimistic lock은 <strong>실패했을 때 재시도 과정이 필요하므로 facade를 만들어</strong> 그곳에서 service layer의 함수를 호출한다.</p>
<pre><code class="language-java">@Component
public class OptimisticLockFacade {

  private final OptimisticLockStockService optimisticLockStockService;
  public OptimisticLockFacade(OptimisticLockStockService optimisticLockStockService) {
      this.optimisticLockStockService = optimisticLockStockService;
  }

  public void decrease(Long id, Long quantity) throws InterruptedException {
      while (true) {
          try {
              optimisticLockStockService.decrease(id, quantity);

              break;
          } catch (Exception e) {
              Thread.sleep(50);
          }
      }
   }
}</code></pre>
</li>
</ol>
<h3 id="장·단점-1">장·단점</h3>
<p><strong>장점</strong></p>
<ul>
<li><strong>충돌이 적게 발생할 때</strong>, 별도의 lock을 잡지 않으므로 pessimistic lock보다 성능이 좋다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>update에 실패했을 때의 재시도 로직을 개발자가 직접 작성해야 한다.</li>
</ul>
<h2 id="⚖️-pessimistic-lock-vs-optimistic-lock">⚖️ Pessimistic lock vs. Optimistic lock</h2>
<p>따라서 <strong>충돌의 발생 빈도에 따라</strong> lock을 다르게 사용하는 것을 추천한다.</p>
<ul>
<li>충돌이 빈번한 경우 ⇒ Pessimistic lock</li>
<li>충돌이 적은 경우 ⇒ Optimistic lock</li>
</ul>
<h2 id="🙋🏻-named-lock-활용">🙋🏻 Named lock 활용</h2>
<p>Named Lock은 이름을 가진 metadata lock으로 <strong>이름을 가진 lock을 획득한 후 해제할 때까지 다른 세션은 이 락을 획득할 수 없다</strong>.</p>
<p>트랜잭션이 종료될 때 <strong>lock이 자동으로 해제되지 않기 때문에</strong> 다음과 같은 상황에서 lock을 해제할 수 있다.</p>
<ul>
<li>별도의 명령어 사용</li>
<li>선점 시간이 종료</li>
</ul>
<p>MySQL에서의 Named lock 사용 명령어는 다음과 같다.</p>
<ul>
<li>lock 획득: <code>get-lock</code></li>
<li>lock 해제: <code>release-lock</code></li>
</ul>
<h3 id="강의-예제-vs-실무">강의 예제 vs. 실무</h3>
<p>강의에서는 편의를 위해 JPA의 Native Query를 사용하고 동일한 data source를 사용한다.</p>
<p>실제로 사용할 때에는 <strong>data source를 분리하는 것을 추천</strong>한다.
같은 데이터 소스를 사용하면 <strong>connection pool이 부족해져 다른 서비스에도 영향</strong>을 줄 수 있다.</p>
<h3 id="적용-과정-2">적용 과정</h3>
<ol>
<li><p>lock을 획득하고 종료하는 <code>LockRepository</code>를 정의한다.</p>
<pre><code class="language-java">public interface LockRepository extends JpaRepository&lt;Stock, Long&gt; {
  @Query(value = &quot;select get_lock(:key, 3000)&quot;, nativeQuery = true)
  void getLock(String key);

  @Query(value = &quot;select release_lock(:key)&quot;, nativeQuery = true)
  void releaseLock(String key);
}</code></pre>
</li>
<li><p>StockService 로직을 활용하여 <strong>락을 획득하고 해제할 facade를 정의</strong>한다.</p>
<pre><code class="language-java">try {
  lockRepository.getLock(id.toString());
  stockService.decreaseStock(id, quantity);
} finally {
  lockRepository.releaseLock(id.toString());
}</code></pre>
</li>
<li><p>StockService의 재고 감소 로직은 facade의 transaction과 별도로 실행되어야 하므로 propagation을 변경한다.</p>
<pre><code class="language-java">// 부모(NamedLockStockFacade)의 transaction과 별도로 실행되어야 하므로 propagation 변경
@Transactional(propagation = Propagation.REQUIRES_NEW) 
public void decreaseStock(Long id, Long quantity) {
  // Stock 조회
  Stock stock = stockRepository.findById(id).orElseThrow();
  stock.decrease(quantity);
  stockRepository.saveAndFlush(stock);
}</code></pre>
</li>
<li><p>예제에서는 같은 data source를 활용하여 두 로직(재고 감소 &amp; lock의 획득 및 해제)을 실행하므로 connection pool의 사이즈를 증가시킨다.</p>
<pre><code class="language-yml">// application.yml 파일
spring: 
datasource:
  hikari:
    maximum-pool-size: 40</code></pre>
<h3 id="실무에서의-활용-방안과-장·단점">실무에서의 활용 방안과 장·단점</h3>
<p>Named lock은 주로 <strong>Distributed lock을 구현</strong>할 때에 사용된다.</p>
</li>
</ol>
<p><strong>장점</strong></p>
<ul>
<li>타임아웃을 구현하기 어려운 Pessimistic lock과 달리 Named lock을 타임아웃을 구현하기 쉽다.</li>
</ul>
<p><strong>단점</strong> </p>
<ul>
<li>트랜잭션 종료 시에 락 해제, 세션 관리에 주의해야하기에 구현이 복잡할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 동시성 문제 해결1: Java의 synchronized🫘]]></title>
            <link>https://velog.io/@pyh-dotcom/concurrencyissuejavasynchronized</link>
            <guid>https://velog.io/@pyh-dotcom/concurrencyissuejavasynchronized</guid>
            <pubDate>Mon, 15 Jan 2024 09:01:17 GMT</pubDate>
            <description><![CDATA[<h1 id="java의-synchronized-키워드-활용">Java의 <code>synchronized</code> 키워드 활용</h1>
<h2 id="👯♂️-활용-예제">👯‍♂️ 활용 예제</h2>
<p>method 선언부에 <code>syncrhonized</code> 키워드를 붙이면 <strong>해당 메소드에 하나의 thread만 접근</strong>할 수 있도록 제한한다.</p>
<pre><code class="language-java">public class StockService {
    ...

    // @Transactional
    public synchronized void decreaseStock(Long id, Long quantity) {
        Stock stock = stockRepository.findById(id).orElseThrow();
        stock.decrease(quantity);
        stockRepository.saveAndFlush(stock);
    }
}
</code></pre>
<h2 id="🚫-transactional-사용에-주의하자">🚫 <code>@Transactional</code> 사용에 주의하자</h2>
<p>해당 함수에 <code>@Transactional</code>을 붙이게 되면 <strong>annotation의 동작 방식 때문에</strong> 의도한 대로 동작하지 않는다.</p>
<p>Spring에서는 <code>@Transactional</code>이 붙은 메소드를 실행할 때 해당 클래스를 매핑한 클래스를 생성하여 실행한다.</p>
<p>이 때 트랜잭션 시작 ⭢ method 실행 ⭢ 트랜잭션 종료 순서로 진행 되는데, <strong>트랜잭션이 종료될 때 상태가 데이터베이스에 업데이트</strong>된다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/4a2f7bef-6e75-45f9-b373-6a6126a598db/image.png" alt="">
method 실행은 완료되었지만 데이터베이스에 업데이트되지 않은 상황에서 다른 스레드가 해당 메서드에 접근할 때 동기화 문제가 다시 발생할 수 있다.</p>
<h2 id="❓-synchronized-사용-시-문제점">❓ synchronized 사용 시 문제점</h2>
<p>하나의 키워드를 통해 간단히 구현할 수 있다는 장점이 있지만, Java의 <code>synchronized</code>는 <strong>하나의 프로세스 안에서만 보장</strong>된다.</p>
<p>따라서 <strong>여러 대의 서버에서 동시에 접근할 때는 보장하지 못</strong>하므로 실제 운용 서비스에서는 잘 사용하지 않는 방법이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 🚩동시성 이슈 발생 상황🚩]]></title>
            <link>https://velog.io/@pyh-dotcom/problemoccur</link>
            <guid>https://velog.io/@pyh-dotcom/problemoccur</guid>
            <pubDate>Wed, 10 Jan 2024 05:21:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인프런의 <a href="https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard">재고시스템으로 알아보는 동시성이슈 해결방법</a>강의를 듣고 정리한 글입니다.
강의를 들으며 내용과 코드는 <a href="https://github.com/develop-hani/Stock_concurrency_issue">github</a>에 정리해두었습니다.</p>
</blockquote>
<h1 id="기본-로직">기본 로직</h1>
<p>재고 시스템에서 재고량을 감소시키며 동시성 문제가 발생하는 상황을 가정한다.</p>
<ul>
<li><strong>Stock</strong> 재고를 가진 객체</li>
<li><strong>StockRepository</strong> JPA 사용을 위한 JpaRepository 상속</li>
<li><strong>StockService</strong> 재고 감소 로직</li>
</ul>
<h3 id="📈-stock">📈 Stock</h3>
<pre><code class="language-java">@Entity
public class Stock {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long productId;
    private Long quantity;

    public Stock() {
    }

    public Stock(Long productId, Long quantity) {
        this.productId = productId;
        this.quantity = quantity;
    }

    public Long getQuantity() {
        return quantity;
    }

    // 재고 감소
    public void decrease(Long quantity) {
        if(this.quantity - quantity &lt; 0) {
            throw new RuntimeException(&quot;재고가 부족합니다.&quot;);
        }
        this.quantity -= quantity;
    }
}</code></pre>
<h3 id="📉-stockservice">📉 StockService</h3>
<pre><code class="language-java">@Service
public class StockService {

    private final StockRepository stockRepository;

    public StockService(StockRepository stockRepository) {
        this.stockRepository = stockRepository;
    }

    // 재고 감소
    public void decreaseStock(Long id, Long quantity) {
        // Stock 조회
        Stock stock = stockRepository.findById(id).orElseThrow();
        stock.decrease(quantity);
        stockRepository.saveAndFlush(stock);
    }
}</code></pre>
<h1 id="동시성-문제-concurrency-issue란">동시성 문제 (Concurrency Issue)란,</h1>
<p><strong>하나의 데이터를 둘 이상의 thread나 session이 제어</strong>할 때 발생하는 문제로</p>
<p>하나의 세션이 데이터를 수정 중일때, 다른 데이터에서 수정 전의 데이터를 조회하여 로직을 처리하므로써 데이터의 정합서이 깨지는 문제를 말한다.</p>
<h2 id="✍🏻-예제-코드">✍🏻 예제 코드</h2>
<p>테스트에서 사용되는 라이브러리/클래스는</p>
<ul>
<li><strong>ExecutorService</strong>
  비동기로 실행하는 작업을 단순화하여 사용할 수 있도록 도와주는 Java API이다.</li>
<li><strong>CountDownLatch</strong>
  다른 스레드가 수행하는 작업이 끝날 때까지 기다릴 수 있는 기능을 제공한다.</li>
</ul>
<pre><code class="language-java">@SpringBootTest
public class StockServiceTest {

    @Autowired
    private StockService stockService;

    @Autowired
    private StockRepository stockRepository;

    @BeforeEach
    public void before() {
        stockRepository.saveAndFlush(new Stock(1L, 100L));
    }

    @AfterEach
    public void after() {
        stockRepository.deleteAll();
    }

    @Test
    public void 재고감소() {
        stockService.decreaseStock(1L, 1L);

        Stock stock = stockRepository.findById(1L).orElseThrow();

        assertEquals(99L, stock.getQuantity()); // 100개 - 1개 = 99개
    }

    @Test
    public void 동시에_100개_요청() throws InterruptedException {
        int threadCount = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i &lt; threadCount; ++i) {
            executorService.submit( () -&gt; {
                try {
                    stockService.decreaseStock(1L, 1L);
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();

        Stock stock = stockRepository.findById(1L).orElseThrow();

        assertEquals(0L, stock.getQuantity());
    }

}</code></pre>
<p>위 테스트 코드에서 <code>재고감소()</code>는 성공적으로 실행되지만 <code>동시에_100개_요청()</code>은 실패한다.</p>
<p>그 이유는 <strong>Race condition</strong>이 발생했기 때문이다.
Race condition은 둘 이상의 thread가 공유 자원에 동시에 접근하여 변경하려고 할 때 발생하는 문제이다.</p>
<h2 id="❓-멀티스레드-환경에서-race-condition-발생-이유">❓ 멀티스레드 환경에서 race condition 발생 이유</h2>
<p><strong>기대했던 순서</strong>는
Thread1 재고 확인 🠒 Thread1 재고 감소 🠒 Thread2 재고 확인 🠒 Thread2 재고 감소 🠒 ... 이지만</p>
<p><strong>실제 실행 순서</strong>는
Thread1 재고 확인 🠒 Thread2 재고 확인 🠒 Thread1 재고 감소 🠒 Thread2 재고 감소 🠒  ... 이므로 문제가 발생한다.</p>
<h2 id="🙌-race-condition-해결하기">🙌 race condition 해결하기</h2>
<p>race condition 문제를 해결하기 위해 <u><strong>하나의 스레드 작업이 완료된 이후에 다른 스레드 작업을 하도록</strong></u>한다.</p>
<p>강의에서 소개하는 방법으로는 크게 3가지가 있다.</p>
<ul>
<li>Application level에서의 해결 방안<ul>
<li>Java의 synchronized</li>
</ul>
</li>
<li>Database의 Lock 활용<ul>
<li>Pessimistic lock(비관적 락)</li>
<li>Optimistic lock(낙관적 락)</li>
<li>Named lock</li>
</ul>
</li>
<li>Redis Distributed Lock 활용<ul>
<li>Lettuce 라이브러리</li>
<li>Redisson 라이브러리</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PJT] SSAFY 공통 프로젝트 WCC(웃참클럽) 회고]]></title>
            <link>https://velog.io/@pyh-dotcom/SSAFYWCCretrospective</link>
            <guid>https://velog.io/@pyh-dotcom/SSAFYWCCretrospective</guid>
            <pubDate>Thu, 28 Dec 2023 07:25:56 GMT</pubDate>
            <description><![CDATA[<h1 id="📖-주제-선정">📖 주제 선정</h1>
<p>주제 선정 과정이 정말 힘들었다. 다양한 주제들이 나왔지만 통일되지 않았고, 컨설턴트님의 의견도 더해지니 주제 선정에 오랜 시간이 걸렸다. 그러다 누군가가 막 던진 아이디어인 실시간 코미디 플랫폼에 의견이 모이면서 프로젝트를 진행하게 되었다.</p>
<p>프로젝트의 주제도 중요하지만 주제 선정이 길어진 만큼 개발 기간이 짧아졌기에 되돌아보니 아쉬움이 남는다. 다음부터는 주어진 시간에 맞추어 주제 선정-설계-개발-산출물 도출 기간을 적절히 나누어야겠다. </p>
<h1 id="✍🏻-학습-내용-기록">✍🏻 학습 내용 기록</h1>
<p>처음 진행하는 프로젝트이다 보니 다양한 코드를 읽고 프로젝트에 적용해 보기도 했고 많은 에러를 마주치기도 했다. 인프라 구축 과정, 배포 과정에서 마주한 문제, annotation 제작 방법 등 내가 학습한 내용들을 기록하고 보니 복습하기에 정말 좋았다. </p>
<p>또 문제가 발생했을 때, 내가 어떤 사고 과정을 거쳤고 어떤 코드를 작성해서 해당 문제가 발생했는지 명확하게 팀원에게 공유할 수 있어서 더 빠르게 문제의 원인을 찾고 해결할 수 있었다.</p>
<h1 id="🗣️-소통의-부재">🗣️ 소통의 부재</h1>
<p>소통을 명확하게 하지 않았던 것이 이번 프로젝트에서 가장 아쉬운 점이다. 각자 개발에만 몰두하다 보니 코드를 통합하는 일이 늦어졌다. 따라서 코드를 통합 과정에서 발생하는 문제, 에러에 대해 대화할 시간이 부족했고 시연 과정에서 화면이 공유되기도, 되지 않기도 하는 문제 발생했다.</p>
<p>앞으로의 프로젝트에서는 주기적으로 코드를 통합하여 발생하는 문제들에 대해 모니터링할 수 있도록 해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java][DFS][BOJ] 1405번 미친 로봇]]></title>
            <link>https://velog.io/@pyh-dotcom/boj1405</link>
            <guid>https://velog.io/@pyh-dotcom/boj1405</guid>
            <pubDate>Mon, 25 Dec 2023 10:36:04 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1405">1405번 미친 로봇</a></p>
<h3 id="🤔-문제-이해하기">🤔 문제 이해하기</h3>
<ul>
<li><p>로봇은 사방으로 이동할 수 있으며 각 방향을 이동할 확률이 주어진다.</p>
</li>
<li><p>로봇이 총 n번 이동한다.</p>
</li>
<li><p>이미 방문한 경로에 또 방문하는 경우를 단순하지 않은 경로, 그렇지 않은 경로를 단순한 경로라고 한다.</p>
</li>
<li><p>로봇이 단순한 경로로 이동할 확률을 구해라</p>
</li>
<li><p>n ≤ 14</p>
</li>
</ul>
<h3 id="⭐-알고리즘">⭐ 알고리즘</h3>
<ul>
<li>DFS</li>
<li>백트래킹</li>
</ul>
<h3 id="📖-스토리-라인">📖 스토리 라인</h3>
<ol>
<li>30 x 30 크기의 공간이 있고, 해당 로봇은 15행 15열에서 출발한다.</li>
<li>dfs로 방문여부를 표시하며 진행한다.<ul>
<li>임의의 경로로 이동할 때 다음 방향을 선택할 확률은 <strong>지금까지 이동한 확률 * 다음 방향으로 이동할 확률</strong>이다.</li>
<li>코드의 <code>move()</code> 호출 부분을 부분에 나와있다.</li>
</ul>
</li>
<li>방문한 곳에 다시 방문하다면 되돌아온다. (백트래킹)</li>
<li>목표한 횟수만큼 이동했다면 단순한 경로이므로 해당 경로로 이동할 확률을 더한다.</li>
</ol>
<h3 id="💻-문제를-해결한-코드">💻 문제를 해결한 코드</h3>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {

    static int n;
    static double ans = 0;
    static double[] p = new double[4]; // E(동), W(서), S(남), N(북)쪽으로 이동할 확률
    static int[] dr = {0, 0, 1, -1};
    static int[] dc = {1, -1, 0, 0};

    static boolean[][] visited = new boolean[30][30];

    // 백트래킹으로 모든 경로를 이동해보며 단순한 경로인지 확인
    // move: 지금까지 움직인 횟수, cr: 현재 있는 행, cc: 현재 있는 열
    private static void move(int move, int cr, int cc, double per) {
        if (move == n) { // n번 만큼 이동한 경우
            ans += per;
            return;
        }

        for (int dir = 0; dir &lt; 4; ++dir) {
            int nr = cr + dr[dir];
            int nc = cc + dc[dir];

            if (visited[nr][nc]) continue; // 이미 방문한 곳인 경우

            visited[nr][nc] = true;
            move(move + 1, nr, nc, per * p[dir]);
            visited[nr][nc] = false;
        }
    }

    // 변수 입력 받기
    private static void readData() throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine(), &quot; &quot;);
        n = Integer.parseInt(st.nextToken());
        for (int i = 0; i &lt; 4; ++i) {
            p[i] = Integer.parseInt(st.nextToken()) * 0.01;
        }

        br.close();
    }

    // 데이터 쓰기
    private static void writeData() throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringBuilder sb = new StringBuilder();

        sb.append(ans).append(&quot;\n&quot;);

        bw.write(sb.toString());
        bw.flush();
        bw.close();
    }

    public static void main(String[] args) throws Exception {
        readData();
        visited[15][15] = true;
        move(0, 15, 15, 1);
        writeData();
    }
}</code></pre>
<h3 id="❗-주의할-점">❗ 주의할 점</h3>
<p>각 방향으로 이동할 확률을 나타내는 변수 (<code>double[] p</code>)를 입력받을 때
<code>p[i] = Integer.parseInt(st.nextToken()) / 100;</code> 을 썼더니 모든 확률이 <strong>0.0으로 저장</strong>됐다.</p>
<ol>
<li><code>int</code> / <code>int</code> 연산</li>
<li><code>double</code>로 casting 되는데
1번 과정에서 0이라는 결과가 도출되기 때문이었다.</li>
</ol>
<p>따라서 int * double로 연산하여 의도한 값이 저장되도록 변경하여 문제를 해결할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kafka/Hadoop] kafka로 데이터를 받아 hadoop에 쌓아보자🐘]]></title>
            <link>https://velog.io/@pyh-dotcom/kafkatohadoop</link>
            <guid>https://velog.io/@pyh-dotcom/kafkatohadoop</guid>
            <pubDate>Thu, 23 Nov 2023 06:10:21 GMT</pubDate>
            <description><![CDATA[<p>싸피에서 자율 프로젝트를 진행하며 빅데이터 파이프라인을 구축하게 되었다. 서버의 용량과 처음 다뤄보는 기술들로 처리과정이 어려웠다.</p>
<p>카프카에 저장된 데이터를 hadoop에 적재하는 역할을 맡았는데, 그 과정을 정리해보고자 한다.</p>
<h1 id="❓-왜-hadoop을-사용했나요">❓ 왜 Hadoop을 사용했나요?</h1>
<p>hadoop은 성능이 좋은 컴퓨터 한 대가 아닌, 보통 성능의 컴퓨터를 여러 개 두어 분산환경에서 데이터를 처리한다.</p>
<p>사용자가 wearOS 기기를 착용하고 운동을 시작하면 심박수 데이터를 모아 저강도, 중강도, 고강도 운동을 각각 몇 분씩 진행하였는지 제공하였다.</p>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/d2365ba6-ad17-4246-8122-c6c917decba7/image.png" alt=""></p>
<p>이때 1초에 1개씩 심박수 데이터를 수집하게 되는데,</p>
<ul>
<li>100명의 사용자가</li>
<li>60분씩만 운동했다고 가정해도
하루에만 36만개의 데이터가 발생한다.</li>
</ul>
<p>이렇게 많은 양의 데이터를 처리하기 위해 hadoop을 적용하였다.</p>
<h1 id="🏗️-아키텍처">🏗️ 아키텍처</h1>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/4fac9c3a-a960-4878-ba57-244ed7dff585/image.png" alt=""></p>
<p>간단하게 정리하자면 아래 두 과정으로 나눌 수 있다.</p>
<ol>
<li>워치에서 소켓통신을 통해 데이터 <strong>kfaka에 저장</strong></li>
<li>kafka에 저장된 테이터를 spark를 통해 <strong>hadoop에 적재</strong></li>
</ol>
<h1 id="🛠️-사용-기술">🛠️ 사용 기술</h1>
<h2 id="kafka">Kafka</h2>
<p>카프카는</p>
<ul>
<li><strong>low coupling</strong>
워치와 hadoop이 서로의 정보를 알지 못해도 데이터를 처리한다.</li>
<li><strong>장애 복구성</strong> 
서버에 장애가 생겨도 워치에서 발생한 데이터는 kafka에 버퍼링되어 hadoop에 적재된다는 점이 보장</li>
</ul>
<p>되어 적용하였다.</p>
<h3 id="kafka를-docker로-띄어보자">kafka를 docker로 띄어보자</h3>
<p>아래 docker-compose.yml파일은 kafka를 docker container로 띄우는 코드이다.</p>
<pre><code class="language-yml">version: &#39;2&#39;
services:
  zookeeper:
    container_name: heartbeat_zookeeper
    image: wurstmeister/zookeeper
    ports:
      - &quot;2181:2181&quot;
    networks:
      - deploy
  kafka:
    container_name: heartbeat_kafka
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    ports:
      - &quot;9092:9092&quot;
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
      KAFKA_ADVERTISED_PORT: 9092
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://43.202.219.160:9092
      KAFKA_CREATE_TOPICS: &quot;heartbeat-raw-topic:2:1&quot;
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
            KAFKA_HEAP_OPTS: &quot;-Xmx4G -Xms4G&quot;
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - deploy
networks:
  deploy:
    external: true</code></pre>
<p>Kafka는 크게 Kafka와 Zookeeper로 구분할 수 있다.</p>
<ul>
<li><strong>zookeeper</strong>
메시지 큐의 데이터를 관리한다.</li>
<li><strong>kafka</strong>
메시지를 TCP로 전송하기 위한 브로커를 제공한다.</li>
</ul>
<h2 id="spark">Spark</h2>
<p>kafka에 쌓여있는 데이터를 hadoop에 적재하기 위해 spark를 사용하였다. 빠른 시간 내에 구현해야했기에 레퍼런스가 많은 tool을 선택하였으며, spark의 경우 인메모리 방식의 연산으로 빠른 데이터 처리가 가능하다.</p>
<p><strong>실행 명령어</strong></p>
<ul>
<li>스파크 버전 파악 <code>spark-submit --version</code></li>
<li>스파크 실행 
<code>spark-submit --master spark://43.202.219.160:7077 --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.5.0 kafka_to_hadoop.py</code></li>
</ul>
<h3 id="첫번째-시도binary타입으로-저장">첫번째 시도(binary타입으로 저장)</h3>
<p>아래와 같은 코드를 실행했을 때에는 데이터가 binary 타입으로 저장되었다.</p>
<pre><code class="language-python">from pyspark.sql import SparkSession

# spark session 생성
sc = SparkSession.builder \
    .appName(&quot;gunapang&quot;) \
    .getOrCreate()
#sc.sparkContext.setLogLevel(&#39;ERROR&#39;)

# kafka 서버와 토픽 설정
kafka_params = {
    &quot;kafka.bootstrap.servers&quot;: &quot;http://43.202.219.160:9092&quot;,
    &quot;subscribe&quot;: &quot;heartbeat-raw-topic&quot;,
    &quot;startingOffsets&quot;: &quot;earliest&quot;
}

# kafka에서 데이터를 읽어 DataFrame 생성
df = sc.readStream.format(&quot;kafka&quot;) \
    .options(**kafka_params) \
    .load()

# Hadoop에 쓰기 위한 경로 설정
path = &quot;hdfs://43.202.219.160:9000/user/hadoop/heartbeat&quot;
checkpointLocation = &quot;/home/ubuntu/spark/checkpoint&quot;

# DataFrame을 Hadoop에 쓰기
query = df.writeStream \
    .format(&quot;parquet&quot;) \
    .option(&quot;path&quot;, path) \
    .option(&quot;checkpointLocation&quot;, checkpointLocation) \
    .start()

query.awaitTermination()</code></pre>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/932e6005-003e-4aff-990c-13cf9fbf7e35/image.png" alt=""></p>
<h3 id="두번째-시도의도한-데이터-타입으로-변경">두번째 시도(의도한 데이터 타입으로 변경)</h3>
<p>첫번째 시도와 달리 데이터 프레임을 전처리하여 저장하였다.</p>
<pre><code class="language-python">from pyspark.sql import SparkSession, functions as F

# spark session 생성
sc = SparkSession.builder \
    .appName(&quot;gunapang&quot;) \
    .enableHiveSupport() \
    .getOrCreate()
#sc.sparkContext.setLogLevel(&#39;ERROR&#39;)

# kafka 서버와 토픽 설정
kafka_params = {
    &quot;kafka.bootstrap.servers&quot;: &quot;http://43.202.219.160:9092&quot;,
    &quot;subscribe&quot;: &quot;heartbeat-raw-topic&quot;,
    &quot;startingOffsets&quot;: &quot;earliest&quot;
}

# kafka에서 데이터를 읽어 DataFrame 생성
df = sc.readStream.format(&quot;kafka&quot;) \
    .options(**kafka_params) \
    .load()

# Kafka에서 읽은 데이터를 적절한 스키마로 변환
df = df.selectExpr(&quot;CAST(value AS STRING) as json&quot;)
df = df.select(F.from_json(df.json, &quot;playerId STRING, heartbeat DOUBLE, createdAt ARRAY&lt;INT&gt;&quot;).alias(&quot;data&quot;))
df = df.select(&quot;data.*&quot;)
df = df.withColumn(&quot;createdAt&quot;, F.to_timestamp(
        F.concat(
           F.expr(&quot;createdAt[0]&quot;), F.lit(&quot;-&quot;),
           F.expr(&quot;createdAt[1]&quot;), F.lit(&quot;-&quot;),
           F.expr(&quot;createdAt[2]&quot;), F.lit(&quot; &quot;),
           F.expr(&quot;createdAt[3]&quot;), F.lit(&quot;:&quot;),
           F.expr(&quot;createdAt[4]&quot;), F.lit(&quot;:&quot;),
           F.expr(&quot;createdAt[5]&quot;), F.lit(&quot;.&quot;),
           F.expr(&quot;createdAt[6]&quot;)
    )
))

# Hadoop에 쓰기 위한 경로 설정
path = &quot;hdfs://43.202.219.160:9000/user/hadoop/heartrate2&quot;
checkpointLocation = &quot;/home/ubuntu/spark/checkpoint4&quot;

# DataFrame을 Hadoop에 쓰기
query = df.writeStream \
    .format(&quot;parquet&quot;) \
    .option(&quot;path&quot;, path) \
    .option(&quot;checkpointLocation&quot;, checkpointLocation) \
    .start()

query.awaitTermination()</code></pre>
<p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/8ac7fcfb-6876-43af-8ed6-da3d8c5a6b74/image.png" alt=""></p>
<br/>
<br/>

<hr>
<p>&lt;참고 블로그&gt;</p>
<ul>
<li><a href="https://waspro.tistory.com/645">https://waspro.tistory.com/645</a></li>
<li><a href="https://pythontoomuchinformation.tistory.com/428">https://pythontoomuchinformation.tistory.com/428</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis] 당해버렸다🎯 Redis 탈취]]></title>
            <link>https://velog.io/@pyh-dotcom/docker-redis-hacking</link>
            <guid>https://velog.io/@pyh-dotcom/docker-redis-hacking</guid>
            <pubDate>Mon, 28 Aug 2023 06:57:33 GMT</pubDate>
            <description><![CDATA[<h1 id="⚠️-문제">⚠️ 문제</h1>
<p>싸피 공통 프로젝트를 진행하며 인프라 구축을 담당하였고, 그 과정에서 docker로 redis를 구동하였다.</p>
<p>서비스를 실제로 배포하기 전 환경 점검 해볼 겸 로그를 찍어보았는데.. 이럴수가.. 사용하지도 않은 redis에 뭔가 찍혀있다.</p>
<p><code>docker exec -it wcc_api_cache redis-cli</code>
<code>keys *</code>
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/3d381121-990b-4665-8c5f-423510cc869a/image.png" alt=""></p>
<p><code>docker logs wcc_api_cache</code>
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/d12da639-373f-441e-83c2-cea09e6cf66b/image.png" alt=""></p>
<p>찾아보니 redis 해킹은 꽤 흔한 것 같다.</p>
<h1 id="🔒-redis에-비밀번호를-꼭-설정하자">🔒 redis에 비밀번호를 꼭 설정하자</h1>
<p>redis에 비밀번호를 설정하여 인증 없이는 해당 container에 접근하지 못하도록 막자.</p>
<p>docker-compose.yml에 비밀번호 option을 추가하는 방법은 아래와 같다.</p>
<pre><code class="language-yaml">version: &quot;3.7&quot;

services:

  cache:
    container_name: wcc_api_cache
    image: redis:alpine
    command: redis-server --requirepass ${password} --port 6379
    ports:
      - 6379:6379
    volumes:
      - /var/api_cache/:/data
    networks:
      - deploy

networks:
  deploy:
    external: true
</code></pre>
<h1 id="🛸-port-번호를-변경하자">🛸 port 번호를 변경하자</h1>
<p>기본 포트인 6379가 아닌 다른 포트를 사용하는 것도 한 가지 방법이라고 하니 참고하자.</p>
<br/>
<br/>

<hr>
<p>&lt;참고 자료&gt;</p>
<ul>
<li><a href="https://thxwelchs.github.io/%EB%82%B4redis%EA%B0%80%ED%95%B4%ED%82%B9%EB%8B%B9%ED%96%88%EB%8B%A4%EA%B3%A0%EC%8B%AC%EC%A7%80%EC%96%B4local%EC%9D%B8%EB%8D%B0/">내 redis가 해킹당했다고? 심지어 local인데?</a></li>
<li><a href="https://sungbin.dev/post/Redis%20%ED%95%B4%ED%82%B9%EB%8B%B9%ED%95%9C%20%EC%9D%B4%EC%95%BC%EA%B8%B0">Redis 해킹당한 이야기</a></li>
<li><a href="https://pinggoopark.tistory.com/261">[redis] 도커 컴포즈로 레디스 컨테이너 만들기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Git log를 예쁘게 찍어보자👑]]></title>
            <link>https://velog.io/@pyh-dotcom/good-looking-git-log</link>
            <guid>https://velog.io/@pyh-dotcom/good-looking-git-log</guid>
            <pubDate>Mon, 24 Jul 2023 13:54:29 GMT</pubDate>
            <description><![CDATA[<h1 id="😶🌫️-github에-잔디가-안-심어진다">😶‍🌫️ github에 잔디가 안 심어진다</h1>
<p>언제인가부터 git에서 github에 push를 하면 잔디가 심어지지 않는 현상이 생겼다. 내가 commit을 해도 기록이 안 남으니까 조금 속상했다. </p>
<p><a href="https://12716.tistory.com/entry/Github-%EC%9E%94%EB%94%94-%EC%95%88%EC%8B%AC%EC%96%B4%EC%A7%80%EB%8A%94-%ED%98%84%EC%83%81-%ED%95%B4%EA%B2%B0">해결방법</a>을 찾아보다가 git log에 format을 지정하는 방법을 알아냈다. 이것을 잘 활용한다면 commit log에서 필요한 정보만 가져와서 변화 내용을 파악하기 편리할 것 같다.</p>
<h1 id="📝-git-log의-다양한-option들">📝 git log의 다양한 option들</h1>
<h2 id="한-줄로-보기">한 줄로 보기</h2>
<pre><code>git log --oneline</code></pre><p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/1a3bafbc-6859-4038-9233-ae529043552c/image.png" alt=""></p>
<h2 id="특정-시간에-생성된-커밋-확인">특정 시간에 생성된 커밋 확인</h2>
<pre><code>git log --since=2.weeks
git log --after=&quot;yesterday&quot;
git log --after=&quot;2023-7-1&quot; --before=&quot;2023-7-15&quot;</code></pre><h2 id="format-지정">format 지정</h2>
<pre><code>git log --pretty=format:&quot;%h = %ar : %s&quot;</code></pre><p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/e159fb30-e786-4305-9480-8d253162b7fc/image.png" alt=""></p>
<p>사용할 수 있는 format 일부는 아래와 같다.</p>
<pre><code>%H 커밋 해시
%h 짧은 길이 커밋 해시

%an 저자 이름
%ae 저자 메일

%ad 작성 시각
%ar 상대적 작성 시각

%s 요약
</code></pre><h2 id="그래프로-브랜치-변화-파악">그래프로 브랜치 변화 파악</h2>
<pre><code>git log --graph</code></pre><p><img src="https://velog.velcdn.com/images/pyh-dotcom/post/5444db3c-40fb-4ed9-8bc7-0bf5001252e4/image.png" alt="">(개인 정보는 가려두었다)</p>
<h2 id="모든-브랜치-흐름-파악">모든 브랜치 흐름 파악</h2>
<pre><code>git log --all --graph --decorate --oneline</code></pre><p>all 옵션을 사용하지 않으면 현재 브랜치와 관련한 로그를 보여준다.
all 옵션을 통해 모든 브랜치의 흐름을 파악할 수 있다.</p>
</br>
</br>

<hr>
<p>&lt;참고자료&gt;</p>
<ul>
<li><a href="https://www.atlassian.com/git/tutorials/git-log">https://www.atlassian.com/git/tutorials/git-log</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] Entity class의 타입: Wrapper vs. Primitive]]></title>
            <link>https://velog.io/@pyh-dotcom/JPA-Entity-class-type</link>
            <guid>https://velog.io/@pyh-dotcom/JPA-Entity-class-type</guid>
            <pubDate>Sat, 22 Jul 2023 14:31:51 GMT</pubDate>
            <description><![CDATA[<h1 id="🤔-개요">🤔 개요</h1>
<p>JPA와 관련하여 공부하다 보니 Entity class에 wrapper class를 사용한 것을 볼 수 있었다. 이전에는 항상 primitive class를 사용했기에 어색하게 느껴져서 조사해 보았다.</p>
<pre><code class="language-java">@Entity
public class Employee {
    @Id
    private Long empNo;
    private String name;
    private Integer age;
    private Character gender;
}</code></pre>
<h1 id="💡-wrapper-type과-primitive-type">💡 Wrapper type과 Primitive type</h1>
<h2 id="id">Id</h2>
<p><a href="https://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#entity-pojo-identifier">Hibenate 공식 문서</a>를 살펴보면 Id에 non-primitivive type을 사용하는 것을 권장한다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/817761a9-cbe8-42cc-ae9c-0b834499eab6/image.png" alt="hibernate 공식 문서"></p>
<p><strong>Primitive type</strong>의 경우 null값을 가질 수 없으며 기본 값이 0이다. 반면, Hibernate에서는 데이터가 존재하지 않을 때 null로 표현한다.
즉, 데이터가 없을 때 primitive type은 0으로, hibernate는 null로 표현한다는 것이다.</p>
<p><strong>Wrapper type</strong>은 primitive type 객체를 다루기 위해 사용하는 클래스로 <strong>null이 될 수 있</strong>다. 따라서 Hibernate의 데이터 존재 여부를 제대로 파악하기 위해서 Id는 Wrapper class를 사용하는 것이 바람직하다.</p>
<h2 id="그-외의-attibutes">그 외의 attibutes</h2>
<p>Id가 아닌 값들에 대해선 어떻게 하는 것이 좋을까? 공식 문서에서 정확한 사용 방법에 대해서 알 수는 없었다.</p>
<p>내 생각에는 모든 속성에 Wrapper class를 사용하는 것이 작성하는 것이 코드 작성의 편리함과 가독성 측면에서 더 좋을 것 같다.</p>
</br>
</br>

<hr>
<p>&lt;참고자료&gt;</p>
<ul>
<li><a href="https://hyune-c.tistory.com/44">https://hyune-c.tistory.com/44</a></li>
<li><a href="https://velog.io/@chiyongs/JPA-Entity-Wrapper-class-or-Primitive-type#%EA%B7%B8%EB%9E%98%EC%84%9C-wrapper-class-primitive-type">https://velog.io/@chiyongs/JPA-Entity-Wrapper-class-or-Primitive-type</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] JPA, Hibernate과 Spring Data JPA에 대한 이해]]></title>
            <link>https://velog.io/@pyh-dotcom/JPA-Hibernate-SpringDataJPA</link>
            <guid>https://velog.io/@pyh-dotcom/JPA-Hibernate-SpringDataJPA</guid>
            <pubDate>Tue, 04 Jul 2023 16:18:41 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 시작하여 JPA, Hibernate, Spring Data JPA에 대해 제대로 이해하지 못하고 활용하고 있다는 생각이 들었다. 따라서 이번 기회에 이를 정리해 보고자 한다.</p>
<h1 id="1-jpajava-persistent-api는-인터페이스이다">1. JPA(Java Persistent API)는 인터페이스이다.</h1>
<h2 id="ormobject-relation-mapping">ORM(Object-Relation Mapping)</h2>
<p>JPA를 이해하기 전 ORM(Object-Relation Mapping)의 개념에 대해 이해할 필요가 있다.</p>
<p>간단히 정리하면 아래와 같은 특징이 있다.</p>
<ul>
<li>객체와 RDB(Relational Database, 관계형 데이터베이스)의 데이터를 자동으로 매핑한다.</li>
<li>객체지향프로그램의 클래스와 RDB의 테이블 사용한다.</li>
<li>ORM을 사용하면 SQL Query가 아닌 코드로 데이터 조작할 수 있다.</li>
</ul>
<h2 id="jpajava-persistent-api">JPA(Java Persistent API)</h2>
<p>JPA는 <strong>Java ORM 기술에 대한 명세</strong>이다.
즉, Java 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 <strong>인터페이스</strong>로 이해할 수 있다.
특정 기능을 하는 라이브러리가 아니다!</p>
<p>JPA를 사용하기 위해서는 JPA를 구현한 프레임워크(Hibernate나 EclipseLink 등)을 사용해야 한다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/199b90ee-2fe5-4111-a0c6-a6dc526bd599/image.png" alt="" title="출처:https://victorydntmd.tistory.com/195"></p>
<h1 id="2-hibernate는-jpa의-구현체이다">2. Hibernate는 JPA의 구현체이다.</h1>
<p>Hibernate는 <strong>JPA의 구현체</strong> 중 하나이다.</p>
<p>JPA에 정의된 <code>EntityManagerFactory</code>, <code>EntityManager</code>, <code>EntityTransaction</code>과 같은 인터페이스를
Hibernate의 <code>SesscionFactory</code>, <code>Session</code>, <code>Transaction</code>이 상속받고 있는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/5331d3d5-76f9-4f08-9711-68a3075c668d/image.png" alt="" title="출처: https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html"></p>
<p>Hibernate 내부에서 JDBC API가 동작하고 있어 사용자가 직접 connection을 관리하지 않아도 된다.</p>
<h1 id="3-spring-data-jpa로-jpa-사용을-편하게하는-모듈이다">3. Spring Data JPA로 JPA 사용을 편하게하는 모듈이다.</h1>
<p>Spring Data JPA는 Spring에서 제공하는 모듈 중 하나로 사용자가 JPA를 편하게 사용할 수 있도록 도와준다.
이는 JPA를 추상화한 <code>Repository</code>라는 인터페이스를 통해 가능하다.</p>
<p>만약 Spring Data JPA 없이 Hibernate만 사용한다면 어떨까?
Hibernate만을 사용한다면 JPA의 영속성을 관리하는 EntityManager를 주입 받아 사용해야한다.
(해당 과정은 <a href="https://www.baeldung.com/hibernate-entitymanager">이곳</a>에 자세히 나와있다.)</p>
<p>하지만 Spring Data JPA를 활용한다면 훨씬 간편하게 JPA를 사용할 수 있다.</p>
<pre><code class="language-java">public interface FOODao extends JpaRepository&lt;Foo, Long&gt; {
    Foo findByName(String name);
}</code></pre>
<p>(사용 방법은 <a href="https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa">이곳</a>에 나와있다.)</p>
<h1 id="⭐요약">⭐요약</h1>
<ol>
<li>JPA는 ORM 기술의 명세다.</li>
<li>Hibernate는 JPA의 구현체이다.</li>
<li>Spring Data JPA는 데이터베이스 접근과 같은 반복되는 코드의 사용을 줄여주는 인터페이스이다.
⇒ Spring Data JPA를 사용하기 위해선 Hibernate과 같은 JPA privider가 필요하다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/c36a2001-4442-463b-ab6c-77a36d6b380e/image.png" alt="" title="출처: https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/"></li>
</ol>
<p><strong>Spring Data를 사용하면</strong></p>
<ul>
<li>Hibernate과 같은 JPA provider를 사용할 수 있다.</li>
<li><code>@Transaction</code>을 통해 트랜잭션 영역을 선언하고 관리할 수 있다.</li>
</ul>
<br/>
<br/>

<hr>
<p>&lt;참고 자료&gt;</p>
<p><strong>ORM</strong></p>
<ul>
<li><a href="https://gmlwjd9405.github.io/2019/02/01/orm.html">https://gmlwjd9405.github.io/2019/02/01/orm.html</a></li>
</ul>
<p><strong>JPA, Hibernate과 Spring Data JPA</strong></p>
<ul>
<li><a href="https://dev-coco.tistory.com/74">https://dev-coco.tistory.com/74</a></li>
<li><a href="https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/">https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/</a></li>
<li><a href="https://sas-study.tistory.com/364">https://sas-study.tistory.com/364</a></li>
</ul>
<p><strong>Spring Data JPA와 QueryDSL</strong></p>
<ul>
<li><a href="https://ict-nroo.tistory.com/117">https://ict-nroo.tistory.com/117</a></li>
</ul>
<hr>
<p>&lt;이미지 출처&gt;</p>
<p><strong>JPA 구현 프레임워크</strong></p>
<ul>
<li><a href="https://victorydntmd.tistory.com/195">https://victorydntmd.tistory.com/195</a></li>
</ul>
<p><strong>JPA와 Hibernate의 상속관계</strong></p>
<ul>
<li><a href="https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html">https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html</a></li>
</ul>
<p><strong>Jpa, Hibernate, Spring Data JPA의 상관관계</strong></p>
<ul>
<li><a href="https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/">https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java][조합][BOJ] 15663번 N과 M(9)]]></title>
            <link>https://velog.io/@pyh-dotcom/boj15663</link>
            <guid>https://velog.io/@pyh-dotcom/boj15663</guid>
            <pubDate>Thu, 22 Jun 2023 04:55:28 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/15663">15663번 N과 M(9)</a></p>
<h3 id="🤔-문제-이해하기">🤔 문제 이해하기</h3>
<ol>
<li>N개의 자연 수 중 M개를 아래 조건에 맞추어 선택<ul>
<li>사전 순으로 증가하는 순서</li>
<li>중복되는 수열은 여러 번 선택 X</li>
</ul>
</li>
<li>M ≤ N ≤ 8</li>
</ol>
<h3 id="⭐-알고리즘">⭐ 알고리즘</h3>
<ol>
<li><p>수를 선택하는 문제 =&gt; 조합</p>
</li>
<li><p>중복되는 수를 어떻게 확인할 건지가 관건</p>
<ul>
<li><p>사전 순으로 증가하는 순서</p>
<ul>
<li>주어진 N개 수를 오름차순으로 정렬</li>
<li>Link로 연결하여 순서 보장</li>
</ul>
</li>
<li><p>중복되는 수열은 여러 번 선택 X</p>
<ul>
<li>Hashset의 특성 이용</li>
</ul>
<p>⇒ LinkedHashSet을 사용하여 조건에 부합하는 M개의 숫자를 선택</p>
</li>
</ul>
</li>
</ol>
<h3 id="💻-문제를-해결한-코드">💻 문제를 해결한 코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.StringTokenizer;

public class Main {

    static int n, m, arr[];
    static LinkedHashSet&lt;String&gt; hs = new LinkedHashSet&lt;&gt;();

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringBuilder sb = new StringBuilder();

        // 입력
        StringTokenizer st = new StringTokenizer(br.readLine(), &quot; &quot;);
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        arr = new int[n];
        arr = Arrays.stream(br.readLine().split(&quot; &quot;)).mapToInt(Integer::parseInt).toArray();

        // 정렬
        Arrays.sort(arr);

        // 숫자 선택
        combination(0, new int[m], new boolean[n]);

        // 출력
        for (String str : hs) {
            sb.append(str).append(&quot;\n&quot;);
        }

        bw.write(sb.toString());
        bw.flush();
        bw.close();
        br.close();
    }

    private static void combination(int idx, int[] selected, boolean[] visited) {
        if (idx == m) {
            String str = &quot;&quot;;
            for (int i = 0; i &lt; m; ++i) {
                str += Integer.toString(selected[i]) + &quot; &quot;;
            }
            hs.add(str);
            return;
        }

        for (int i = 0; i &lt; n; ++i) {
            if(visited[i]) continue;
            selected[idx] = arr[i];
            visited[i] = true;
            combination(idx + 1, selected, visited);
            visited[i] = false;
        }
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java][그리디][BOJ] 1464번 뒤집기3]]></title>
            <link>https://velog.io/@pyh-dotcom/boj1464</link>
            <guid>https://velog.io/@pyh-dotcom/boj1464</guid>
            <pubDate>Wed, 31 May 2023 04:53:11 GMT</pubDate>
            <description><![CDATA[<h1 id="boj-1464번-뒤집기3">BOJ 1464번 뒤집기3</h1>
<p><a href="https://www.acmicpc.net/problem/1464">1464번 뒤집기3</a></p>
<h3 id="문제-이해하기">문제 이해하기</h3>
<ul>
<li>문자열을 뒤집거나 그대로 두어 사전순으로 가장 앞선 문자열을 찾는다.</li>
<li>문자열의 길이 &lt;= 50</li>
<li>제한시간 2초</li>
</ul>
<h3 id="문제-접근-방법">문제 접근 방법</h3>
<p><strong>사전 순으로 앞에 있는 단어를 뒷쪽에</strong> 두고 마지막에 문자열을 reverse한다.</p>
<ol>
<li>초기 문자열: a<sub>1</sub>a<sub>2</sub>a<sub>3</sub> ... a<sub>k-1</sub>a<sub>k</sub>a<sub>k+1</sub></li>
<li>k번째 문자까지 뒤집은 문자열: a<sub>k</sub>a<sub>k-1</sub> ... a<sub>2</sub>a<sub>1</sub>a<sub>k+1</sub></li>
<li>k+1번째 문자까지 뒤집은 문자열: a<sub>k+1</sub>a<sub>1</sub>a<sub>2</sub>a<sub>3</sub> ... a<sub>k-1</sub>a<sub>k</sub></li>
</ol>
<p>⇒ 1, 2, 3 과정을 거친 결과 a<sub>k+1</sub>의 위치만 변경되었다.</p>
<hr>
<p>이 점을 문제에 적용해보자.</p>
<p>문자열 a<sub>1</sub>a<sub>2</sub>a<sub>3</sub> ... a<sub>k-1</sub>a<sub>k</sub>a<sub>k+1</sub>에서</p>
<ul>
<li>a<sub>k</sub>가 a<sub>k+1</sub>보다 앞선다면 a<sub>k+1</sub>의 위치를 변경</li>
<li>a<sub>k</sub>가 a<sub>k+1</sub>과 앞서지 않는다면 문자열을 유지</li>
</ul>
<hr>
<p>예시로 이해해보자.</p>
<p>초기문자열 <u>AC</u>AB에서</p>
<ol>
<li>문자열 AC에서 A와 C 비교 ⇒ 위치 변경 ⇒ <u>CAA</u>B</li>
<li>문자열 C<u>AA</u>에서 A와 A 비교 ⇒ 위치 유지 ⇒ <u>CAAB</u></li>
<li>문자열 CA<u>AB</u>에서 A와 B 비교 ⇒ 위치 변경 ⇒ BCAA</li>
<li>사전 순으로 앞에 있는 단어를 뒤에 두었으므로 reverse한다.</li>
</ol>
<h3 id="구현-배경-지식">구현 배경 지식</h3>
<ul>
<li>문자열 비교</li>
</ul>
<h3 id="문제를-해결한-코드">문제를 해결한 코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Main {

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringBuilder sb = new StringBuilder();

        // 입력 및 전처리
        String str = br.readLine();
        String ans = str.substring(0, 1);

        // 로직
        for (int i = 1; i &lt; str.length(); ++i) {
            if (ans.charAt(i - 1) &lt; str.charAt(i)) {
                ans = str.charAt(i) + ans;
            } else {
                ans = ans + str.charAt(i);
            }
        }

        // 출력
        sb.append(ans);
        sb.reverse();
        sb.append(&quot;\n&quot;);

        bw.write(sb.toString());
        bw.flush();
        bw.close();
        br.close();
    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java][LeetCode] Two Sum]]></title>
            <link>https://velog.io/@pyh-dotcom/JavaLeetCode-Two-Sum</link>
            <guid>https://velog.io/@pyh-dotcom/JavaLeetCode-Two-Sum</guid>
            <pubDate>Wed, 01 Mar 2023 16:03:02 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://leetcode.com/problems/two-sum/">https://leetcode.com/problems/two-sum/</a>
정수 배열과 target이 주어졌을 때, 배열에서 선택한 두 값의 합이 target값과 같아지도록 하는 값의 인덱스를 구한다.</p>
<h1 id="on2-85ms">O($n^2$), 85ms</h1>
<p>문제를 처음 보았을 때 직관적으로 이중 for문을 활용하여 풀어야겠다고 생각했다.</p>
<pre><code class="language-java">class Solution {
    public int[] twoSum(int[] nums, int target) {
        for(int i = 0; i &lt; nums.length - 1; ++i){
            for(int j =i+1;j&lt;nums.length; ++j){
                if(nums[i]+ nums[j] == target){
                    return new int[] {i,j};
                }
            }
        }

        return new int[] {0, 0};
    }
}</code></pre>
<p>그 결과 시간 복잡도가 상위 77%가 나왔다.
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/e9210f36-93ab-439e-a034-a0104236b84a/image.png" alt=""></p>
<h1 id="on-2ms">O($n$), 2ms</h1>
<p>자료구조를 잘 활용하면 시간복잡도 개선할 수 있다.
정수 값을 key로 갖고, 배열의 인덱스를 value로 가지는 map을 이용하여 문제를 해결하였다.</p>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;

class Solution {
        public int[] twoSum(int[] nums, int target) {
            Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
            for (int i = 0; i &lt; nums.length; ++i) {
                if (map.containsKey(target - nums[i])) {
                    return new int[] { i, map.get(target - nums[i]) };
                }
                map.put(nums[i], i); // put number and index
            }

            return new int[] {};
        }
    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++][수학][BOJ] 6064번 카잉 달력]]></title>
            <link>https://velog.io/@pyh-dotcom/boj6064</link>
            <guid>https://velog.io/@pyh-dotcom/boj6064</guid>
            <pubDate>Tue, 03 Jan 2023 05:49:32 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/cofls6581/hongjangal/tree/main/%EB%B3%B5%EC%8A%B5/%EB%B0%B1%EC%A4%80_%EC%BD%94%ED%85%8C%EA%B8%B0%EC%B4%88/%EB%B8%8C%EB%A3%A8%ED%8A%B8%ED%8F%AC%EC%8A%A4/%EC%B9%B4%EC%9E%89%20%EB%8B%AC%EB%A0%A5">홍장알 바로가기</a></p>
<h1 id="문제">문제</h1>
<p><a href="https://www.acmicpc.net/problem/6064">https://www.acmicpc.net/problem/6064</a>
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/630dc49a-45f6-4592-8cf1-59d7038ceffa/image.png" alt=""></p>
<h1 id="❌-시간-초과">❌ 시간 초과</h1>
<p><a href="https://github.com/cofls6581/hongjangal/blob/main/%EB%B3%B5%EC%8A%B5/%EB%B0%B1%EC%A4%80_%EC%BD%94%ED%85%8C%EA%B8%B0%EC%B4%88/%EB%B8%8C%EB%A3%A8%ED%8A%B8%ED%8F%AC%EC%8A%A4/%EB%82%A0%EC%A7%9C_%EA%B3%84%EC%82%B0/yehan.cpp">날짜 계산 문제를 풀었던 것</a>처럼 브루트포스를 이용하여 풀어보고자 하였다.
그러나 이 경우 시도해보아야하는 경우의 수는 <strong>M*N</strong>, 최대 16억 개를 주어진 시간 안에 문제를 해결할 수 없다.</p>
<pre><code class="language-c">#include &lt;iostream&gt;
using namespace std;

int getGCD(int a, int b) {
    int c;
    while (b != 0) {
        c = a % b;
        a = b;
        b = c;
    }
    return a;
}

int getLCM(int a, int b) {
    int gcd = getGCD(a, b);
    return a * b / gcd;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);

    int t; cin &gt;&gt; t;
    while (t--) {
        int m, n, x, y;
        cin &gt;&gt; m &gt;&gt; n &gt;&gt; x &gt;&gt; y;
        int cnt = getLCM(m, n);
        int year = 1;

        while (cnt--) {
            x--; y--;
            if (x &lt;= 0 &amp;&amp; y &lt;= 0) break;

            if (x &lt;= 0) x = m;
            if (y &lt;= 0) y = n;
            year++;
        }
        if (cnt &gt; 0) cout &lt;&lt; year &lt;&lt; &#39;\n&#39;;
        else cout &lt;&lt; -1 &lt;&lt; &#39;\n&#39;;
    }

    return 0;
}</code></pre>
<h1 id="⭕-맞았습니다">⭕ 맞았습니다!!</h1>
<p>브루트포스로는 해결할 수 없어 아래 나온 몇가지 조건들을 고려하여 해결하였다.</p>
<ul>
<li>구하고자하는 연도를 M으로 나누었을 때 나머지가 x여야한다.</li>
<li>멸망년도에 도달하기 전까지, 구하고자하는 연도와 y값이 대응하는지 살펴본다.<pre><code class="language-c">#include &lt;iostream&gt;
using namespace std;
</code></pre>
</li>
</ul>
<p>int getGCD(int a, int b) {
    int c;
    while (b != 0) {
        c = a % b;
        a = b;
        b = c;
    }
    return a;
}</p>
<p>int getLCM(int a, int b) {
    int gcd = getGCD(a, b);
    return a * b / gcd;
}</p>
<p>int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);</p>
<pre><code>int t; cin &gt;&gt; t;
while (t--) {
    int m, n, x, y;
    cin &gt;&gt; m &gt;&gt; n &gt;&gt; x &gt;&gt; y;
    int cnt = getLCM(m, n);
    int year = -1;

    for (int i = x; i &lt;= cnt; i += m) {
        int ny = i % n;
        if (ny == 0) ny = n;
        if (ny == y) {
            year = i;
            break;
        }
    }
    cout &lt;&lt; year &lt;&lt; &#39;\n&#39;;
}

return 0;</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++][수학][BOJ] 6588번 골드바흐의 추측]]></title>
            <link>https://velog.io/@pyh-dotcom/boj6588</link>
            <guid>https://velog.io/@pyh-dotcom/boj6588</guid>
            <pubDate>Tue, 06 Dec 2022 06:38:21 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/cofls6581/hongjangal/tree/main/%EB%B3%B5%EC%8A%B5/%EB%B0%B1%EC%A4%80_%EC%BD%94%ED%85%8C%EA%B8%B0%EC%B4%88/%EC%88%98%ED%95%99/%EA%B3%A8%EB%93%9C%EB%B0%94%ED%9D%90%EC%9D%98%20%EC%B6%94%EC%B8%A1">홍장알 바로가기</a></p>
<h1 id="문제">문제</h1>
<p><a href="https://www.acmicpc.net/problem/6588">https://www.acmicpc.net/problem/6588</a>
<img src="https://velog.velcdn.com/images/pyh-dotcom/post/fbd07272-ffe1-4bf4-b214-1b381febfd8b/image.png" alt=""></p>
<h1 id="⭕-맞았습니다">⭕ 맞았습니다!!</h1>
<p>에라토스테네스의 체를 사용하여 소수를 모두 구해두었다.
그 다음, 주어진 숫자를 홀수 소수의 합으로 구할 수 있는지 살펴보았다.
불가능한 경우를 나타내기 위해 투포인터의 개념을 적용하였다.</p>
<pre><code class="language-c">#include &lt;iostream&gt;
#include &lt;math.h&gt;
using namespace std;


int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);

    int primeNum[1000001] = { 0, };

    primeNum[1] = 1;
    for (int i = 2; i &lt;= sqrt(1000000); i++) {
        if (primeNum[i] == 1) continue;
        for (int j = 2 * i; j &lt;= 1000000; j += i) {
            primeNum[j] = 1;
        }
    }

    int n;
    while (1) {
        cin &gt;&gt; n;
        if (n == 0) break;

        for (int left = 3; left &lt;= n / 2 + 1; left += 2) {
            int right = n - left;
            if (left &gt; right) {
                cout &lt;&lt; &quot;Goldbach&#39;s conjecture is wrong.\n&quot;;
                break;
            }
            if (primeNum[left]) continue;
            if (primeNum[right] == 0) {
                cout &lt;&lt; n &lt;&lt; &quot; = &quot; &lt;&lt; left &lt;&lt; &quot; + &quot; &lt;&lt; right &lt;&lt; &#39;\n&#39;;
                break;
            }
        }
    }
    return 0;
}</code></pre>
<h1 id="stack-overflow">Stack Overflow</h1>
<p>문제를 풀며 stack overflow를 마주하였다. 범위가 큰 primeNum 배열을 main 함수 내부에서 정의하여 발생한 것이였다.</p>
<p>그 원인은 배열이 할당되는 위치에 있었다.
main 함수 내에서 선언된 배열은 스택 영역에 생성된다. 스택영역은 프로그램이 사용하는 임시 메모리로 할량할 수 있는 정도가 적다.</p>
<p>해당 배열을 전역 변수로 설정하여 문제를 해결할 수 있었다.
전역변수로 변수를 선언하면 이는 함수가 호출되기 전에 메모리의 데이터 영역에 할당된다.</p>
<p>자세한 내용은 <a href="https://www.acmicpc.net/board/view/34836#:~:text=0_o%203%EB%85%84%20%EC%A0%84,%EC%9C%BC%EB%A1%9C%20%EC%84%A0%EC%96%B8%ED%95%98%EC%8B%A4%20%EC%88%98%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4.">백준 게시판 질문</a>, <a href="https://ko.wikibooks.org/wiki/C_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%9E%85%EB%AC%B8/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EB%B0%B0%EC%97%B4#cite_note-1">위키백과의 참고2</a>에서 살펴볼 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>