<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyun_ji.log</title>
        <link>https://velog.io/</link>
        <description>헤맨만큼 내 땅이다</description>
        <lastBuildDate>Fri, 09 Jan 2026 08:20:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyun_ji.log</title>
            <url>https://velog.velcdn.com/images/hyun_ji/profile/18e807d3-e73d-4e90-abf2-4822f211bc69/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyun_ji.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyun_ji" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Lambda Cold Start 해결]]></title>
            <link>https://velog.io/@hyun_ji/%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%9E%8C%EB%8B%A4-Cold-Start%ED%95%B4</link>
            <guid>https://velog.io/@hyun_ji/%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%9E%8C%EB%8B%A4-Cold-Start%ED%95%B4</guid>
            <pubDate>Fri, 09 Jan 2026 08:20:43 GMT</pubDate>
            <description><![CDATA[<h2 id="lambda의-cold-start">Lambda의 Cold Start</h2>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/fcf27fa5-30a7-4ce5-a8ec-fff691847da3/image.png" alt="">
람다함수 코드는 위 그림과 같은 순서로 실행되는데, 저 과정들 중 코드를 실행하기 위한 환경을 준비하는 파란 부분을 <a href="https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/lambda-runtime-environment.html#cold-start-latency">콜드 스타트(Cold Start)</a>라고 한다. 
이와 반대로 이미 실행 환경이 갖추어져 있다면 Cold Start부분은 스킵하고 바로 노란색 부분부터 실행이 가능한데 이것을 Warm Start라고 한다. 
람다는 실행시간에 비례하여 요금이 청구되므로 Warm Start가 훨씬 실행시간도 단축되고 비용도 적게 든다. </p>
<h2 id="로그-분석">로그 분석</h2>
<h3 id="cold-start">Cold Start</h3>
<p>우선 실행 환경이 하나도 갖추어지지 않은채 처음부터 준비해야 하는 Cold Start를 해보자. </p>
<pre><code>Duration: 31949.48 ms    Billed Duration: 31950 ms    Memory Size: 2048 MB    Max Memory Used: 645 MB    </code></pre><p>첫 번째 실행은 CloudWatch를 이용해 모델 분석 API요청 로그를 보면 무려 <strong>31.9초</strong>나 걸린것을 볼 수 있다. </p>
<p>로그를 자세히 살펴보니, 실제 모델 추론시간보다 모델을 메모리에 적재하는 초기화 시간이 훨씬 더 오래 걸리고 있었다. </p>
<h3 id="warm-start">Warm Start</h3>
<blockquote>
<p>Lambda는 효율성을 위해 일정 시간 동안 실행 환경(컨테이너)을 파괴하지 않고 유지한다. 이 환경이 사라지기 전에 함수를 다시 호출하면 이미 메모리에 모델이 적재된 상태인 Warm Start가 가능하다.</p>
</blockquote>
<pre><code>Duration: 214.72 ms    Billed Duration: 215 ms    Memory Size: 2048 MB    Max Memory Used: 653 MB    </code></pre><p>이에 반해 두 번째 실행 (Warm Start)에서는 첫 번째와는 다른 사진을 분석시켰음에도 <strong>0.2초</strong>로 실행시간이 확 줄어든 것을 볼 수 있다. </p>
<p>이는 YOLO모델 로드 코드를 전역 변수 영역에 배치하였기 때문이다. Warm start로 재실행시, 이미 메모리에 적재된 모델 객체를 즉시 참조하게 되면서 무거운 초기화 과정이 생략되었기 때문이다. </p>
<h2 id="provisioned-concurrency를-통한-warm-start">Provisioned Concurrency를 통한 Warm Start</h2>
<p><strong>그러나 Warm start는 영구적이지 않다.</strong>
람다는 일정시간동안 요청이 없으면 리소스를 회수하기 위해 실행 환경(컨테이너)를 삭제한다. 이 경우 다시 Cold Start가 발생한다는 한계가 있다. 이용자가 많은 서비스라면 요청이 잦아 대부분의 요청에서 Warm Start상태를 유지할 수 있겠지만, 개인 프로젝트처럼 사용자가 거의 없는 경우에는 요청이 잦아봐야 몇시간에 한 번일테니 매번 Cold Start가 되어 사진 하나를 분석하는데 30초를 넘게 기다려야 할 것이다. </p>
<p>이를 해결하기 위한 방법으로는 2가지가 있는데</p>
<p>첫번째는 <strong><a href="https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/lambda-runtime-environment.html#cold-starts-pc">프로비저닝 동시성</a>(Provisioned Concurrency)</strong>이다. 
이 기능은 EC2처럼 계-속 람다 함수를 켜두는 것이다. 
프로비저닝 동시성 기능을 활성화하면, 설정된 개수만큼의 인스턴스를 초기화하여 대기시켜 둔다. 그러면 Cold Start가 발생하지 않고 Warm Start로 즉시 실행되게 된다.</p>
<p>프로비저닝 동시성 기능을 활성시킨후, 람다가 컨테이너를 삭제할만한 시간동안 호출을 하지 않았다. </p>
<p>다시 앱에서 람다 함수를 호출해보았더니 훨씬 단축된 실행시간에, 로그에 Init duration(초기화 시간)도 찍히지 않는 것을 볼 수 있었다. 즉, 성공적으로 Warm Start가 적용된 것이다. </p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/4f99c3cd-8c93-4cf5-9742-979ae5533373/image.png" alt=""></p>
<p><em>[실행시간이 0.27초로 단축되었다]</em></p>
<p>다만 이는 EC2처럼 계속 실행시켜 두는 것이나 다름없다보니 비용이 꽤 있는편이다. 그래서 좀 더 심화된 최적화 방법으로, 프로비저닝 오토스케일을 사용하기도 한다고 한다.</p>
<p>프로비저닝 오토스케일링은 트래픽이 높은 시간대에 맞춰 프로비저닝을 오토스케일링하여 가격을 최적화하는 방법이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS  VPC, EC2]]></title>
            <link>https://velog.io/@hyun_ji/AWS-EC2-VPC</link>
            <guid>https://velog.io/@hyun_ji/AWS-EC2-VPC</guid>
            <pubDate>Wed, 31 Dec 2025 08:10:32 GMT</pubDate>
            <description><![CDATA[<p>AWS를 이용해서 서버를 띄우기 전에 기본적인 지식을 공부하였다.
강의는 <a href="https://www.youtube.com/@AWSClassroom">https://www.youtube.com/@AWSClassroom</a> 에서 들었는데 여타 유료 강의 못지않게 설명을 잘 해주신다.</p>
<h2 id="기본-지식">기본 지식</h2>
<blockquote>
<p><strong>[들어가기전 기본 용어 정리]</strong>
<code>EC2</code> :  AWS에서 제공하는 가상 서버 대여 <strong>서비스</strong>의 이름이다. 
<code>인스턴스</code> : <code>EC2</code>서비스를 사용해서 만들어진 <strong>실제 가상 서버</strong> 한 대 단위를 이르는 말</p>
</blockquote>
<p>스케일링 - 자원을 늘리는 것
vertical scale 수직적 스케일링 - 하나의 자원 자체의 &#39;성능&#39;을 높이는것 
horizontal scale  - 자원의 규모, 수를 늘리는것
    - 대게 이걸 씀 (탄력적으로 쪼개기 쉬움)
    - 대신 아키텍처 복잡</p>
<p>-&gt; Auto Scaling - horizontal scaling 자동화
    EC2 Auto Scaling - 정확한 수의 EC2인스턴스를 보유하도록 보장
        - 최소, 최대 숫자 인스턴스 설정 가능
        - CPU 부하에 따라 인스턴스 크기 조정
        - 가용 영역에 인스턴스가 골고루 분산되도록 인스턴스 분배</p>
<p>NAT(Network Address Translation) - 사설 IP를 공용 IP로 변환해주는 방법
    - Dynamic NAT - NAT Pool에서 현재 사용가능한 공용 IP하나 가져다 씀
    - Static NAT - 이미 사설과 공용 IP가 1:1 매칭이 되어 있음(수가 같음)
    - PAT(Port Address Translation) 
        - 가장 많이 쓰이는 형식
        - 하나의 공용 IP가 사설망 전체를 대표함
        - 포트별로 PC를 다르게 구분함 
        - 포트 포워딩(Port Forwarding) - PAT 원리를 수동으로 설정하여 특정 포트에 특정 기기를 연결시키는 설정작업
        - 보통 공유기가 이 역할을 함</p>
<p>CIDR는 그냥 IP주소 표기법. 클래스 단위로 빌리면 너무 비싸기 때문에 정교하게 수를 조정가능한 CIDR표기법이 생긴것. 할당받은 공용 IP 개수에 x 사설 IP주소로 해서 인터넷 연결 가능한 기기를 많이 만들 수 있음.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/3cc79de1-c838-4417-8d27-4915d34e5395/image.png" alt="">
출처 : <a href="https://www.youtube.com/watch?v=IJgAIbxxJCE">https://www.youtube.com/watch?v=IJgAIbxxJCE</a></p>
<p>보통 EC2 인스턴스로 서버를 띄운다고 한다면, 그 기본 틀의 구조는 위와 비슷하다. 각 구성요소를 하나씩 알아보자</p>
<h2 id="vpcvirtual-private-cloud">VPC(Virtual Private Cloud)</h2>
<ul>
<li>외부와 격리된 가상 네트워크 단위. 쉽게 말해 사설망</li>
<li>원칙상으론 퍼블릭 인터넷에서 접근불가,(하지만 인터넷 게이트웨이를 통해 접근 가능) AWS내부에서도 외부를 통해 접근해야함</li>
<li>다양한 서브넷 구성 가능     </li>
<li>인터넷에 노출되지 않는 EC2구성 등 보안 설정 가능</li>
<li>하나의 VPC는 하나의 리전밖에 커버하지 못하기 때문에 리전과 리전 사이를 하나의 VPC가 커버할 수는 없음. 리전의 수만큼 VPC가 존재해야 함.</li>
<li><strong>EC2를 띄우려면 필수적으로 VPC도 설계해야함</strong> 애초에 계정을 만들면 디폴트 VPC가 자동으로 생성된다.</li>
</ul>
<blockquote>
<p><strong>왜 VPC가 필요할까?</strong>
서버를 냅다 아무 환경에나 띡하고 띄울 순 없다. 일단 네트워크 환경을 만들어주어야 한다. VPC는 쉽게 말하면 거대한 공용 클라우드(AWS)에서 나만의 울타리를 치는거다. 이 울타리가 없으면 밖에서 내 사설 IP주소만 알게 되면 그냥 바로 침범이 가능해진다. 그러나 VPC라는 울타리를 치면 내가 허락한 문(인터넷 게이트웨이)과 규칙(보안 그룹)등을 통과해야지만 들어올수 있기 때문에 보안을 위해서는 필수적이다. 또한 사설 IP대역(내 정원의 크기)도 마음대로 지정이 가능해진다. 그리고 내가 소유한 땅이나 다름없으니 거기 안에서 역할에 따른 구역(서브넷)을 자유롭게 나눌수도 있다. 그렇기 때문에 VPC는 EC2뿐만 아니라 RDS, Lambda등 대부분의 서비스에서도 필수적인 요소이다. </p>
</blockquote>
<h3 id="구성-요소들">&lt;구성 요소들&gt;</h3>
<h3 id="1-서브넷">1. 서브넷</h3>
<ul>
<li>VPC의 하위 단위. VPC에 할당된 IP를 작은 단위로 분할한 개념. 서브 네트워크</li>
<li><strong>하나의 서브넷은 하나의 가용영역 안에 위치</strong></li>
<li>사용 가능한 IP개수는 -5개</li>
<li>외부에서 접근이 가능한 퍼블릭 서브넷은 웹서버 등의 용도로, 외부와 아예 차단된 프라이빗 서브넷은 데이터베이스, 로직 서버등의 용도로 사용됨.(라우트 테이블에 밖으로 나갈 수 있는 IP자체가 없음)</li>
<li>가장 작은 서브넷 단위는 CIDR단위 /24(11개, 16-5)</li>
</ul>
<h3 id="2-인터넷-게이트웨이">2. 인터넷 게이트웨이</h3>
<ul>
<li>기본적으로 외부와 통신이 안 되는 VPC가 외부의 인터넷과 통신할 수 있도록 경로를 뚫어주는 리소스(쉽게 말해 가상환경에 현실세계로 향하는 포탈 뚫어주는 역할)</li>
<li>공유기처럼 사설IP와 공용IP간의 변환 역할을 수행함. 다만 공유기와 IGW의 차이라면 공유기는 자신의 고유 IP로 변환해서 내보내고 IGW는 해당 리소스에 할당된 공용 IP와 매핑해서 보냄</li>
<li>이름에서 알 수 있듯이 폐쇠적으로 인터넷 필요없이 운영할 서비스면 굳이 필요없기도 함</li>
<li>무료임</li>
</ul>
<blockquote>
<p><strong>IGW(인터넷 게이트웨이)도 공유기처럼 그냥 IGW자체가 공용 IP소유하고 있으면 편하지 않나?</strong>
공유기는 대게 안에서 밖으로 나가는 요청이 먼저 시작되므로 라우트 테이블에 먼저 어떤 사설IP가 어떤 포트로 나갔는지 기록이 가능함. 그러나 IGW로 연결된 서비스는 대게 웹 서버와 같은 외부에 노출되어야 하는 서비스다. 그런데 만약 인바운드 요청이 아웃바운드 요청보다 먼저 들어오게 된다면, 아직 라우트 테이블에는 아무 기록이 없으니 어디로 매핑을 해줘야 할지 모르게 된다. 그러므로 외부에서 내부로 들어오는 요청이 원활하게 이루어져야 하기 때문에 그냥 리소스 자체에 공용 IP를 박아서 바로 연결이 되도록 한 것이다.</p>
</blockquote>
<h3 id="3-보안그룹">3. 보안그룹</h3>
<ul>
<li>방화벽의 역할을 하는 서비스</li>
<li>기본적으로 모든 포트 비활성화</li>
<li>특정 포트 허용만 가능, Deny는 불가능</li>
<li>인스턴스에 붙어있음. 하나의 인스턴스에 여러개의 보안그룹 설정 가능</li>
<li>보통 8080번 포트와 3000번 포트 (TCP)를 열어두는데 우리는 웹 애플리케이션 및 API 서버를 주로 만들기 때문에 이와 관련된 통신을 허용하기 위함임</li>
<li>보안그룹은 인바운드 들어왔던걸 기억해서 아웃바운드가 자유롭지만(StateFul), NACL은 인바운드가 통과되었던 것이라도 아웃바운드 할 때 또 체크해야함(Stateless)</li>
</ul>
<h3 id="4-nacl">4. NACL</h3>
<ul>
<li>서브넷 단위(서브넷 들어가기전에 NACL을 거쳐야함)</li>
<li>포트 및 IP 직접 Deny가능(특정 IP 블락 가능)</li>
<li>규칙번호 100번 단위, 작은 수부터 규칙 적용</li>
</ul>
<h3 id="5-라우트-테이블">5. 라우트 테이블</h3>
<ul>
<li>트래픽이 어디로 가야 할지 알려주는 이정표</li>
<li>0.0.0.0/0(외부 아이피 대역)이 목적지일 때, 인터넷 게이트웨이로 나가도록 설정된 라우팅 테이블을 서브넷에 붙이면, 해당 서브넷은 퍼블릭 서브넷이 되는것임</li>
</ul>
<h3 id="6-nat-gateway">6. NAT Gateway</h3>
<ul>
<li>VPC의 프라이빗 서브넷에 있는 인스턴스에서 외부의 인터넷과 통신하기 위한 통로</li>
<li>서브넷 단위(퍼블릭 서브넷에 있어야함)</li>
<li>NAT Gateway는 퍼블릭 서브넷에 위치해서 프라이빗 서브넷에 위치한 인스턴스 대신에 인터넷에 연결해 줌(업데이트나 다운로드 같은 외부 인터넷 연결이 필요할 때)</li>
<li>보안을 위해 아웃바운드(안에서 밖으로 일방통행)만 가능</li>
</ul>
<h3 id="7-bastion-host">7. Bastion Host</h3>
<ul>
<li>Private Subnet의 자원에 접속이 되도록 도와주는 호스트</li>
<li>이건 NAT Gateway랑 반대로 외부에서 프라이빗 서브넷에 접근하기 위한 EC2 인스턴스</li>
<li>퍼블릭 서브넷에 위치해야함(프라이빗 서브넷 중개 역할이니까)</li>
<li>private서비스에 접근할땐 Session Manager를 사용하기도 </li>
</ul>
<h3 id="8-elastic-load-balancer">8. Elastic Load Balancer</h3>
<ul>
<li>다수의 트래픽을 분산 시켜주는 서비스</li>
<li>Health Check를 통해 트래픽을 발생시켜 Instance가 살아있는지 체크</li>
<li>지속적으로 IP주소가 바뀌어 고정 불가능하기 때문에 항상 도메인 기반으로 사용</li>
<li>서버가 죽으면 로드밸런서가 새로운 복제 서버를 만들어(AMI로) 요청을 거기로 우회시키고 기존 죽은 서버는 삭제시킴(고가용성 설계)</li>
<li>얘도 Bastion Host랑 비슷하게 프라이빗 서브넷에 인바운드 요청 전용 길인건 맞지만, 로드밸런서는 외부 요청의 메시지만 서버에 전달하고 답을 받아다주고 외부와 직접 연결되는 것은 막음(reverse proxy). 다시 말해 외부 손님을 위한 길이나 마찬가지라 외부 요청인 HTTP/HTTPS만 알아듣고 80/443번 포트만 열려있음. 
이에 반해 Bastion Host는 관리자가 <strong>직접</strong> 서버 유지보수나 설정을 만지기 위해 길을 뚫어놓는 것에 가까움.(프라이빗 서브넷은 관리자도 함부로 접속 불가능하기 때문에) 그래서 보통 관리용 포트인 22(SSH)번 포트만 열어두고 특정 IP만 들어오게 해 둠.</li>
</ul>
<blockquote>
<p><strong>왜 ELB의 IP는 유동적인가?</strong>
ELB의 IP는 계속해서 바뀌기 때문에 도메인 주소를 기반으로 작동함.
이유는 ALB도 결국 AWS가 관리하는 인스턴스(서버)인데 모든 트래픽이 ALB를 거쳐가기 때문에, 갑자기 트래픽이 늘어나면 AWS는 로드밸런서를 5대, 10대로 늘이고 이렇게 로드밸런서 서버를 늘릴 때마다 새로운 IP를 계속 추가함. 그리고 이 여러 개의 IP 주소를 하나의 DNS 이름(도메인) 아래에 묶어버림. 만약 IP가 하나뿐이라면 모든 트래픽이 거기로 몰려 병목현상이 일어날것임.</p>
</blockquote>
<h2 id="ec2">EC2</h2>
<p>EC2(Elastic Compute Cloud) 구성요소</p>
<h3 id="1-ebs">1. EBS</h3>
<ul>
<li>하드디스크 역할</li>
<li>종료 시 삭제 옵션(인스턴스가 종료될때 같이 삭제할 건지 여부)</li>
<li>암호화(하드디스크 암호화해서 데이터 저장)</li>
</ul>
<h3 id="2-스냅샷">2. 스냅샷</h3>
<ul>
<li>특정 시간의 EBS상태의 저장본(S3에 증분식 저장)</li>
</ul>
<h3 id="3-ami">3. AMI</h3>
<ul>
<li>EC2인스턴스를 실행하기 위해 필요한 정보를 모은 단위</li>
<li>인스턴스 복제할때 커스텀 AMI를 생성해서 복제가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 7 Brackets 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-7-Brackets-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-7-Brackets-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 17 Dec 2025 22:33:20 GMT</pubDate>
            <description><![CDATA[<p>단순한 괄호식 스택 문제</p>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(S):
    stack = []
    mapping = {&#39;)&#39;:&#39;(&#39;, &#39;}&#39;:&#39;{&#39;, &#39;]&#39;:&#39;[&#39;}

    for element in S:
        if element in &#39;{[(&#39;: # 여는 괄호 나올때때
            stack.append(element)
        else: # 닫는 괄호 나올때
            if not stack: # 스택 비어있으면 중첩 문자열 아님
                return 0 

            tmp = stack.pop()
            if tmp != mapping[element]:
                return 0 
            element

    if stack: # 스택에 남아있음
        return 0
    else: 
        return 1</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 6 BinaryGap 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-6-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-6-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 17 Dec 2025 21:46:15 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>배열의 A의 원소 3가지 중 
A[P] + A[Q] &gt; A[R],
A[Q] + A[R] &gt; A[P],
A[R] + A[P] &gt; A[Q].
를 만족하는 숫자 3개 찾기</p>
<p>가장 큰 수가 다른 숫자 2개보다 작아야함
삼각형을 만들 수 있는 3 수가 존재하면 1, 아니면 0
시간복잡도 nlogn 이하, 요소 범위 -2,147,483,648..2,147,483,647(음수 싹 다 제외해야함)</p>
<h3 id="코드">코드</h3>
<p>정렬시켜서 0이상인 수부터 3개씩 묶어서 검사하면 될 듯
시간복잡도는 2N</p>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(A):
    A.sort(reverse = True)

    for idx, num in enumerate(A):
        if idx+2 &gt; len(A)-1 or A[idx+2] &lt; 0: # 음수가 오면 안됨
            return 0
        if A[idx] &lt; A[idx+1] + A[idx+2]: # 삼각형 조건 만족하는지 검사
            return 1

    return 0</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 6 MaxProductOfThree 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-6-MaxProductOfThree-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-6-MaxProductOfThree-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 17 Dec 2025 12:16:15 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>배열 A의 원소 3개의 곱 중 가장 큰 것을 찾는 문제
A의 크기는 10만 이하
배열 A의 요소의 크기는 -1000 ~ 1000</p>
<p>A의 범위가 음수를 포함시키니 아마 가장 큰 수의 조합은 양수x양수x양수 이거나 음수x음수x양수가 될것이다.</p>
<p>정렬시켜서 2가지 버전을 만든 다음에 더 큰수를 반환하면 될 것 같다.</p>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(A):

    A.sort()
    length = len(A)

    return max(A[-1] * A[-2] * A[-3], A[0] * A[1] * A[-1])</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/66e96980-d4a7-43ee-9064-c1c09361db57/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 6 Distinct 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-6-Distinct-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-6-Distinct-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 17 Dec 2025 11:58:52 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>배열에서 나오는 숫자의 종류 가짓수를 계산하는 문제이다. 
N의 범위는 10만, 배열 A의 요소의 크기는 -100만 ~ 100만.</p>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<pre><code class="language-python">def solution(A):
    x = set()

    for i in A:
        x.add(i)

    return len(x)</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/9822742e-8914-4de8-a360-4f4cf02afc0a/image.png" alt=""></p>
<p>set사용법 익히라고 낸 문제인가 풀려서 좀 당황함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 5 PassingCars 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-5-PassingCars-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-5-PassingCars-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 17 Dec 2025 11:44:22 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>솔직히 문제 이해하는게 너무 어려웠다. 예시로 주어진 예제도 이해를 못 해서 계속 ???상태로 문제만 한 10번 읽었다. </p>
<p>문제를 정리해보자면 가로지르는 차의 수를 구하는게 목표이다. 여기서 <code>passing</code>을 내가 이상하게 해석해서 문제 이해가 안 됐던것... 
0은 오른쪽, 1은 왼쪽으로 가는 차이고 차량끼리 서로 <strong>교차</strong>하게 되는 수를 구한다고 생각하면 된다. 
가로지르는 차의 수가 10억개를 넘으면 -1반환하고 수 범위 10만이다.</p>
<h3 id="고민-포인트">고민 포인트</h3>
<p>역시 시간이 포인트인 문제인듯. 이중포문써서 단순 더하기로 세면 쉽게 풀리겠지만 그러면 무조건 타임아웃 뜰 듯. </p>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(A):
    count = 0

    for idx, num in enumerate(A):
        if num == 0:
            for num2 in A[idx+1:]:
                if num2 == 1:
                    count += 1

    return count</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/e69ee417-e8b0-4526-8d3c-33b765a7202a/image.png" alt="">
<del>역시나</del></p>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<p>핵심은 0이 나온 뒤의 수들 중, 1의 출현수를 구하는 것이다. 그래서 배열의 원소들의 종류가 0과 1뿐인걸 이용해서 sum()을 이용해 전체합(=1의 전체 개수)을 구했다. 그 다음 for문으로 A를 순환하며 현재까지 발견한 1의 수를 카운트해서 전체수에서 빼 주는 방식으로 간단하게 이후에 나오는 1의 수를 계산할 수 있었다. </p>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<pre><code class="language-python">def solution(A):
    total_num = sum(A) # 배열의 모든 1을 더한 수
    count_1 = 0 # 여태나온 1의 수
    result = 0 # 반환할 결과

    for num in A:
        if num == 0:
            result += (total_num - count_1)
            if result &gt; 1000000000:
                return -1
        else:
            count_1 += 1

    return result</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/35d957c7-78f6-426d-be75-4afb12cf316c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 4 PermCheck 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-1-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC-t7xcxhzm</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-1-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC-t7xcxhzm</guid>
            <pubDate>Wed, 17 Dec 2025 09:18:52 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>A가 1~N까지의 수가 단 한 번씩만 나오는 순열이 맞으면 1반환, 아니면 0을 반환하는 문제이다. </p>
<blockquote>
<p>N is an integer within the range [1..100,000];
each element of array A is an integer within the range [1..1,000,000,000].</p>
</blockquote>
<p>라는 조건으로 보아 시간복잡도는 O(nlogn)안에 해결해야 할 것 같고,
A의 요소의 범위가 10억인걸 보아 메모리를 신경써야 하는 문제같다.</p>
<p>보통의 코딩문제 메모리 제한이 <code>128MB ~ 512MB 정도</code>이니 리스트는 크기 500만 이내, Set이나 딕셔너리는 크기 100만 이내로 제한해야 한다. </p>
<h3 id="고민-포인트">고민 포인트</h3>
<p>A가 순열인지는 무조건 배열을 다 돌아야지 알 수 있음 -&gt; 순열의 수의 수가 A의 크기이기 때문</p>
<p>그렇다면 순열이 맞는지 확인하는 것보다 순열이 아닌 조건에서 바로 return 0으로 쳐낸 다음, 반복문을 문제없이 다 돌았다면 순열이 맞다는 방식으로 판단하는게 좋을 것 같다.</p>
<p>순열은 1부터 시작해서 N의 최대 크기가 100000이니, 만약 100000이상인 수가 나오면 볼 것도 없이 바로 순열이 아니다. 그리고 중복된 수도 나올 수가 없으니 이 두 조건을 불충족하면 바로 순열 탈락이다.</p>
<p>2가지 방법을 생각해봤다.</p>
<blockquote>
<p><strong>set 방법</strong>
Set생성하고 반복문으로 A순환하며 처음 발견한 수 Set에 add하기
중복된 수 나오면 바로 0반환
100000초과인 수 나오면 바로 0반환
무사히 반복문 끝났다? -&gt; 순열 확정! </p>
</blockquote>
<blockquote>
</blockquote>
<p><strong>체크 배열 방법</strong>
A의 크기만큼(10만 크기 이하)의 False배열 생성
반복문 돌며 해당하는 수의 인덱스 True로 변환
중복된 수가 나오거나 10만 초과인 수 나오면 바로 return 0
반복문 무사히 끝나면 return 1</p>
<p>Set이 해시테이블을 사용하기 때문에 배열보다 메모리를 아주 조금 더 사용한다는 말에 이번엔 Set말고 배열을 이용해 보기로 하였다.</p>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<p><strong>체크 배열 방법</strong></p>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(A):
    check_arr = [False] * (len(A) + 1) # 0 인덱스는 안 쓰니까 +1

    for num in A:
        if num &gt; len(A):
            return 0
        elif check_arr[num]: # 중복인 수 발견
            return 0
        else:  # 해당 수가 미발견 상태이면
            check_arr[num] = True

    return 1

# A가 순열이면 1 반환, 아니면 0반환

# 체크 배열 방법
# A의 크기만큼(10만 크기 이하)의 False배열 생성
# 반복문 돌며 해당하는 수의 인덱스 True로 변환
# 중복된 수가 나오거나 10만 초과인 수 나오면 바로 return 0
# 반복문 무사히 끝나면 return 1</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/75243e79-ccd1-4a37-b5ae-1da5f23756e9/image.png" alt=""></p>
<p>Set으로 하면 훨씬 간결하게 할 수 있다해서 그냥 Set도 해봤다. 리스트를 Set으로 변환하고 변환된 Set의 크기가 기존 배열 크기와 같고 요소들의 크기가 모두 A의 길이 이하이면 된다.</p>
<p><strong>set 방법</strong></p>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(A):
    N = len(A)
    visited = set(A) # 중복 제거된 A

    # 길이가 N과 같고 최대값이 N과 같으면 순열
    if (len(visited) == N) and (max(visited) == N):
        return 1

    return 0
</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/76151203-6b0f-403f-a053-cc002561fbb0/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 4 Counting Elements 파이썬, 시간복잡도
]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-4-Counting-Elements-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-4-Counting-Elements-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84</guid>
            <pubDate>Wed, 17 Dec 2025 09:17:03 GMT</pubDate>
            <description><![CDATA[<h2 id="문제풀이">문제풀이</h2>
<p>0에 위치한 개구리가 X+1로 가기 위한 최단 시간을 구하는 문제이다. 배열 A에 주어진 숫자들은 각 초(인덱스)에 떨어진 나뭇잎의 위치를 가리킨다. 그리고 X+1로 가기 위한 길이 완성된 최단 시간을 구하면 된다. 
 ex.배열 A가 [1,3,1,4,2,3,5,4]라면 0초엔 1번 위치, 1초엔 3번 위치에 떨어진다는 뜻. 최단 시간은 2번 위치에 나뭇잎이 떨어진 다음 초인 5초.</p>
<h2 id="고민-포인트">고민 포인트</h2>
<p><strong>문제 주요 조건</strong>들을 정리해보면
<em><del>1. 1</del>X까지가 매워져야함 
2. 이동 불가능이라면 -1반환
3. 시간 복잡도 효율성 생각하기<del>~</del></em></p>
<p><strong>어떻게 할 지 고민되었던 지점</strong>
이미 나온 위치인지 아닌지 어케 구분하지? 
-&gt; 얘만 구분되면 개수를 세든 해서 길이 완성되었는지 여부 판단은 쉬울듯</p>
<p> 일단 1차원적으로 X크기의 배열을 만든 다음에 전부 -1로 초기화해서, A배열에서 해당하는 위치의 숫자를 발견하면 그 배열의 해당 인덱스를 양수로 바꾸는 방법을 생각해보았다. 그런데 이 방법은 배열A를 X번 순회하는 것도 시간복잡도가 큰데, X크기의 배열이 전부 양수가 되었는지 또한 계속해서 검토를 해주어야 하기 때문에 무조건 시간 초과할 것 같아서 기각. </p>
<p>그래서 1<del>X까지 빠짐없이 매워야 한다는 특징을 이용했다. 즉, 랜덤한 숫자를 찾는게 아니므로 1</del>X까지의 정돈된 수를 차례대로 찾아간다는 느낌.
두 방식의 차이라고 한다면 두 번째 방법은 수를 차례대로 찾기 때문에 첫 번째처럼 이미 메워진 위치인지를 검사할 필요가 없이 마지막 칸(X)를 찾으면 바로 다 찾았음을 알 수 있다.</p>
<ol>
<li>배열에서 1~X까지 하나하나 원소를 찾는데 해당 원소를 찾은 인덱스 위치가 큰 것으로 점점 교체해나가기</li>
<li>만약 중간에 발견되지 않으면 즉시 중단하고 -1반환<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)
</code></pre>
</li>
</ol>
<p>def solution(X, A):
    result_time = 0</p>
<pre><code>for i in range(1, X+1):
    for idx, num in enumerate(A):
        if i == num:
            if result_time &lt; idx:
                result_time = idx
            break # 남은 배열 원소들은 확인할 필요 X

        # 여기까지 왔다는 것은 배열에 해당 요소가 없다는 뜻
        if idx &gt;= (len(A)-1):
            return -1

return result_time</code></pre><pre><code>
## 트러블 슈팅
![](https://velog.velcdn.com/images/hyun_ji/post/d21e6d54-17d2-4158-a4cc-528f63f20d2e/image.png)
정확성 테스트는 모두 통과했는데 성능 테스트는 모조리 타임아웃 에러가 났다.

지금 코드의 시간복잡도는 O(n^2)이니 여기서 더 줄여야한다. 입력 범위가 $N, X \le 100,000$이니, 최악의 경우를 가정했을 때 $O(N)$ 또는 $O(N \log N)$ 복잡도 내에 해결해야한다.

### 데이터 크기에 따른 시간 복잡도
보통 코딩문제 제한시간이 1~6초이고 파이썬의 초당 연산 횟수를 대략 20000000회라고 가정. 
&gt; $N \le 20$    |  $O(2^N)$ 또는 $O(N!)$    | 재귀, 백트래킹, 완전 탐색 (브루트 포스)
$N \le 500$    |     $O(N^3)$    | 플로이드-워셜, 단순 3중 for문
$N \le 2,000$        | $O(N^2)$    | 이중 for문, 삽입 정렬, 거품 정렬
$N \le 100,000$ |    $O(N \log N)$    | 정렬, 이진 탐색, 우선순위 큐(Heap)
$N \le 1,000,000$    | $O(N)$    | 단일 for문, 스택, 큐, 해시(Set/Dict)
$N \ge 10^8$ |    $O(\log N)$    | 유클리드 호제법, 고속 거듭제곱

위의 표는 N의 범위별 적용하기 적절한 시간복잡도이다.  
예를들어 N의 범위가 1,000,000이하라면 시간복잡도가 N이거나 logN인 방법으로 풀 수있다. 이것보다 큰 것들(ex.N^2)을 사용하면 시간초과.

원래 코드는 O(N^2)의 시간복잡도라 100000 x 100000 = 10000000000(100억) 번의 연산이 수행된다. 즉, 100억 % 2천만 = 500(초)가 걸린다는 소리. 

중요한건 N의 입력가능 범위! 가능하면 N의 최악의 연산 횟수가 1000만 단위를 넘지 않게 하는게 좋다. (파이썬의 연산 횟수를 고려했을때)

그래도 이참에 시간복잡도 계산법은 확실히 알고가게 된 듯...

배열을 정렬 후 이진탐색하는것을 생각해봤는데 이건 잎이 떨어진 순서가 섞여버려서 안된다. 

도저히 아이디어가 생각이 안 나서 AI에게 힌트를 달라했더니 Set이나 체크 배열을 활용하여 전체를 단 한 번만 훑어보되 그때그때 새로운 것을 발견하면 찾은 개수를 증가시키라 조언을 주었다. 이러면 언제 다 찾았는지도 쉽게 알 수 있다. 

내가 기존에 짠 코드는 1찾으려고 전체 함 훑고, 2찾으려고 전체 함 훑고 이런 식인데 저 방식은 한 번 쫙 훑을 때 어 3이네? 어 1이네? 하고 새로운 것들 그때그때 찾아내고 한 번 본건 2번 확인하지 않는다. 종료조건 쉽게 알아내는 법에 꽃혀서 효율적인 탐색 방법은 생각 못 한게 패착의 원인인듯. 


## 최종 제출 코드
**Set방식**
```python
# you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)

def solution(X, A):
    visited = set()

    for idx, num in enumerate(A):
        if num &lt;= X:
            visited.add(num) 

        if len(visited) == X:
            return idx # 마지막으로 찾은 수의 인덱스가 젤 클테니까 

    return -1 # for문에서 끝나지 않았다면 해당 위치가 존재하지 않는다는 뜻뜻


# A를 한 번 순회
# 처음 찾는 위치면 set에 추가(X보다 작은수만 추가가능)
# set의 크기가 X와 같을때까지 반복</code></pre><p>set은 탐색할때 Hash알고리즘을 사용하기 때문에 값을 추가할때 이미 중복된 값인지 검사할때의 시간복잡도가 O(1)이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 3 FrogJmp 파이썬
]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-2-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-2-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Tue, 16 Dec 2025 11:05:57 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>정수X, D, Y가 주어졌을 때, X에 위치한 한 번에 D만큼 점프하는 개구리가 Y와 같거나 큰 거리만큼을 가는데 필요한 최소 점프수를 구하는 문제</p>
<p>단순히 X값이 Y보다 커질때까지 D를 더해주면 되나? 라고 생각하고 코드를 짰는데</p>
<pre><code class="language-python">def solution(X, Y, D):
    jump_count = 0

    while X &lt; Y: # 현재 위치가 목표 지점에 닿을때까지 반복
        X = X + D
        jump_count += 1

    return jump_count</code></pre>
<p>뭔가 분명 이렇게 쉬울리가 없다는 생각에 예외를 찾아보려고 생각해봤는데... X, Y, D 전부 양의 정수이고 Y값이 X값 보다 크다는 조건까지 전부 잘 명시되어 있어서 일단 제출해보았다. </p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/11a98127-cd44-4962-b82a-9fea44b997b5/image.png" alt="">
그럼 그렇지... 생각해보니 성능보다 정확성을 요구했던 이전 문제들과는 달리 이 문제의 테마는 <strong>시간 복잡도</strong>다. </p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/5e718a4e-bd42-4922-88a6-f659258387b8/image.png" alt="">
통과 못 한 케이스들 전부 시간 초과. </p>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<p><strong>O(n)</strong>에서는 시간 초과가 나는 것 같으니 시간복잡도를 <strong>O(log n)</strong>이나 <strong>O(1)</strong>으로 줄여야 한다. </p>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<pre><code class="language-python"># you can write to stdout for debugging purposes, e.g.
# print(&quot;this is a debug message&quot;)
import math

def solution(X, Y, D):

    jump_count = (Y - X) / D
    result = math.ceil(jump_count) # 결과값 올림

    return result</code></pre>
<p><code>(최종위치 - 현재위치) / 이동거리</code> 의 결과를 올림하면 Y에 도달하기 위한 최종 이동 횟수가 나온다. 시간복잡도는 O(1).
<img src="https://velog.velcdn.com/images/hyun_ji/post/2165d0bd-6dcb-4638-b185-27d2f905df40/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 2 CyclicRotation 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-2-CyclicRotation-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-2-CyclicRotation-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Tue, 16 Dec 2025 10:36:52 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>배열 A와 정수 K가 주어지면, 배열 A의 원소들을 K번 오른쪽으로 옮기는 문제이다. </p>
<p>조건이 복잡하지 않고 단순히 모든 원소를 다음칸으로 한 칸씩 회전시킨다는 조건에 처음엔 인덱스를 이용해서 [현재 인덱스 + 1]로 자리를 재지정하면 될려나 라고 생각했다. 근데 간단하게 생각하면 결론적으로 <code>회전한 듯한 모양새</code>만 만들면 되는 것이니 마지막 원소를 떼서 앞에다 붙이면 되는거 아닌가?라는 생각이 들었다.</p>
<pre><code class="language-python">def solution(A, K):

    for i in range(K):
        last_num = A.pop() # 맨 뒤의 원소 떼서
        A.insert(0, last_num) # 맨 앞에 붙여넣기

    return A</code></pre>
<p>그래서 이렇게 초단순하게 코드를 짰는데 테스트 케이스 3개가 다 통과하는것이 아닌가. 그래서 설마 하는 마음으로 제출해봤는데.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/259f5924-6211-4a3c-9436-0624f837f43b/image.png" alt=""></p>
<p>역시 최종 테스트 코드를 100% 통과하진 않았다.</p>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<p>통과하지 못한 테스트 케이스를 살펴보니 </p>
<pre><code> File &quot;/tmp/exec_user_s3cqt9mm/solution.py&quot;, line 7, in solution
    last_num = A.pop()
               ^^^^^^^
IndexError: pop from empty list</code></pre><p>라는 에러 메시지로, 빈 배열이 주어졌을때 pop()을 해서 생긴 문제 같았다.  </p>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<p>주어진 배열이 빈 배열일 경우 예외처리하는 코드를 추가하였다. </p>
<pre><code class="language-python">def solution(A, K):

    if len(A) == 0:
        return A

    for i in range(K):
        last_num = A.pop()
        A.insert(0, last_num)

    return A</code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/f5b43bb2-e632-48e4-a489-74f3727ba335/image.png" alt=""></p>
<p>잘 풀었다고 생각했는데 생각지도 못한 예외였다... 앞으로 좀 더 꼼꼼히 예외 케이스를 생각해보아야 할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Codility] Lesson 1 BinaryGap 파이썬]]></title>
            <link>https://velog.io/@hyun_ji/Codility-Lesson-1-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hyun_ji/Codility-Lesson-1-BinaryGap-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Tue, 16 Dec 2025 10:14:25 GMT</pubDate>
            <description><![CDATA[<h3 id="문제풀이">문제풀이</h3>
<p>10진수의 수 N이 주어지면, 이 수의 이진수에서 1로 둘러쌓인 최대 연속된 0의 길이를 구하는 문제이다. </p>
<pre><code class="language-python">def solution(N):

    binary_num = bin(N)[2:]
    max_count_0 = 0 
    count_0 = 0 

    for char in binary_num:
        if char == 1 and count_0 &gt; 0: # 앞에 0을 낀 닫히는 1
            if max_count_0 &lt; count_0:
                max_count_0 = count_0
                count_0 = 0
        elif char == 0:
            count_0 += 1

    return max_count_0 </code></pre>
<h3 id="고민-포인트">고민 포인트</h3>
<ol>
<li>0을 감싸는 시작&#39;1&#39;과 끝나는 &#39;1&#39;의 차이를 무슨 수로 구분하지</li>
<li>이진수 변환 어케 시키지</li>
</ol>
<p>2번은 파이썬에 <code>bin()</code>이라는 편리한 이진수 변환 함수를 이용하였다. 2진수임을 나타내는 접두사 0b가 붙어서 슬라이싱으로 제거해줬다.
1번은 &#39;1&#39;일때 <code>count_0 &gt; 0</code>이라는 조건을 추가해줬는데, 첫 번째 인덱스의 &#39;1&#39;과 &#39;1&#39;이 연속되어서 오는 경우에는 <code>count_0</code>변수가 증가되지 않아 0일 거라는 생각에서였다. </p>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<p>저 상태의 코드로 제출했더니 테스트 케이스 3개중 2개만 자꾸 통과했다. 결과적으로 놓친 부분이</p>
<ol>
<li><code>char == 1</code> 과 <code>char == 0</code> 에서 정수가 아닌 문자열와 비교하는 거라서 작은 따옴표를 붙였어야했다.</li>
<li><code>if max_count_0 &lt; count_0:</code>의 if문 안의 <code>count_0 = 0</code>는 if문 안에 들어있지 말고 밖에 나와야 함. 0의 개수가 <code>max_count_0</code>보다 작을지라도 <code>count_0</code>를 초기화해줘야 하기 때문.</li>
<li><code>if char == 1 and count_0 &gt; 0:</code>에서 <code>count_0 &gt; 0</code>조건은 없어도 됨. 어차피 <code>count_0</code>변수가 0이라면 <code>max_count_0</code>보다 클리가 없으니까.</li>
</ol>
<h3 id="최종-제출-코드">최종 제출 코드</h3>
<p>오류 사항 수정하고 조금 더 가독성 좋게 바꾼 코드 </p>
<pre><code class="language-python">def solution(N):

    binary_num = bin(N)[2:]
    max_count_0 = 0 
    count_0 = 0 

    for char in binary_num:
        if char == `0`:
            count_0 += 1

        elif char == `1`: 
            if max_count_0 &lt; count_0:
                max_count_0 = count_0
            count_0 = 0

    return max_count_0 </code></pre>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/23960458-976a-42bd-aef9-567285344d40/image.png" alt="">
테스트 케이스를 전부 통과했다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Lambda에 FastAPI YOLO 모델 배포하기]]></title>
            <link>https://velog.io/@hyun_ji/AWS-Lambda%EC%97%90-FastAPI-YOLO-%EB%AA%A8%EB%8D%B8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyun_ji/AWS-Lambda%EC%97%90-FastAPI-YOLO-%EB%AA%A8%EB%8D%B8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Nov 2025 09:05:24 GMT</pubDate>
            <description><![CDATA[<h1 id="프로젝트-개요">프로젝트 개요</h1>
<p>본 프로젝트는 기존에 제작해두었던 간단한 물고기 종 분류 YOLO 모델을 활용하여,
백엔드 서비스를 혼자서 설계·배포·운영까지 완주해보는 것을 목표로 한 개인 프로젝트이다.</p>
<h3 id="동기">동기</h3>
<p>데이터 사이언스를 전공하며 모델을 구현하는 경험은 많았지만, 해당 모델이 실제 서비스 환경에서 어떻게 배포되고 운영되는지는 직접 경험해보지 못한 영역이었다.</p>
<p>특히 이전 팀 프로젝트에서는 주로 기능 구현에 집중했기에, 인프라 설계부터 배포까지의 전 과정을 직접 책임지는 경험에 갈증이 있었다.
이에 본 프로젝트에서는 <strong>모델의 성능 고도화보다는 모델을 포함한 백엔드 서비스를 설계부터 배포·운영까지 혼자서 완주하는 경험</strong>을 목표로 삼았다.</p>
<h3 id="서비스-구성-및-구현-범위">서비스 구성 및 구현 범위</h3>
<p>기존에 제작해두었던 물고기 종 분류 YOLO 모델을 기반으로, 해당 모델을 호출할 수 있는 백엔드 API를 <strong>FastAPI</strong>로 구성하고, 모바일 클라이언트에서는 <strong>Kotlin(Android)</strong>을 사용해 서비스를 구현하였다.</p>
<p>또한, 단순히 추론 기능만 있는게 아니라 엄연히 서비스다운 모습을 갖추기 위해 수집 요소를 추가하였다. 포켓몬고의 도감 시스템처럼, 사진 분석 결과로 등장한 종(예: 상어, 해파리)을 사용자가 사전에 등록·확인할 수 있는 기능을 추가하였다.  </p>
<p>서비스 운영에 필요한 데이터와 사용자 수집 정보는 <strong>DynamoDB</strong>에 저장하였으며,클라이언트에서 생성한 랜덤 UUID를 사용자 식별자로 사용하여, 로그인 기능을 따로 구현하지 않고도 사용자 식별이 가능하록 하였다.</p>
<h3 id="lambda-선택-배경">Lambda 선택 배경</h3>
<p>원래 서버를 EC2로 띄우려고 했는데 프리티어 기간이 만료가 되었다(...) </p>
<p>개인 프로젝트라는 특성상 서버 운영과 비용 관리에 많은 리소스를 쓰고 싶지 않았기 때문에 프리티어를 넉넉하게 제공해주고 운영 부담도 적은 <strong>AWS Lambda</strong>를 서버 실행 환경으로 선택하였다.</p>
<p>AWS Lambda 서비스는 DynamoDB와 같은 ServerLess서비스이기 때문에, DynamoDB와 연계되어 서버 관리가 거의 필요 없는 완전한 서버리스(Serverless) 아키텍처를 구성할 수 있다는 장점도 있었다.</p>
<p>서버리스라는 표현은 서버가 존재하지 않는다는 의미가 아니라, 개발자가 서버의 운영·확장·장애 대응을 직접 관리하지 않아도 된다는 의미이다.
AWS에서 알아서 운영, 수요 예측 등을 다 해주기 때문에 서버 안정성이나 스케일링에 대한 고민없이 개발자는 오직 서비스의 개발에만 집중할 수 있다. </p>
<p>게다가 EC2는 서버가 24시간 내내 돌아가기 때문에 서버를 실행하는 동안 비용이 지속적으로 발생하는 반면, Lambda는 이벤트가 발생할 때만 실행되는 온디맨드 방식으로, 월별 <code>320만초 or 40만 GB-Second</code> 까지 무료라 소규모 개인 프로젝트에서는 거의 무료로 평생 사용할 수 있다. </p>
<p>다만 AWS에서 자체적으로 여러 서비스들을 결합해 자동으로 관리해주는 거라 아키텍처가 복잡하게 얽혀있다는 단점과, 한 번 서버리스 방식으로 서버를 구축하면 다른 방식으로 마이그레이션이 쉽지 않다는 단점이 있다. (한마디로 AWS 람다 서비스 종료되면 내 서비스도 영원히 종료된다는것... 근데 람다 서비스가 끝날 가능성도 거의 0%이긴하다.) 그리고 Stateless한 서비스이기 때문에 EC2처럼 EBS같은 메모리에 데이터 저장이 불가능하다. 람다 자체에 저장 기능이 없기 때문에, 기억해야 할 데이터가 있다면 반드시 S3나 DB같은 외부 저장소가 필요하다. </p>
<blockquote>
<p>물론 람다에게도 아주 작은 로컬 디스크 공간인 /tmp 폴더(최소 512MB 제공)가 있긴 하다.
그러나 이 역시 함수가 종료되고 컨테이너가 내려가면 삭제된다. 주로 실행 중에 잠깐 압축을 풀거나, 큰 파일을 임시로 내려받아 처리하는 용도로만 사용된다.</p>
</blockquote>
<h1 id="lambda-이용하여-배포하기">Lambda 이용하여 배포하기</h1>
<p>일반적으로 람다에 서버를 배포하려면 그냥 라이브러리와 코드를 zip파일로 묶어서 올리면 된다. 이 방식은 환경설정에 문제만 없다면(EC2랑은 다르게 OS환경이 리눅스로 고정되어있어 다른 OS를 선택할수 없다.) 매우 간단하지만 파일이 <code>50MB</code> 이하여야 한다.</p>
<blockquote>
<p><strong>하지만 내가 만든 서비스는</strong></p>
</blockquote>
<ol>
<li>모델 파일이 포함되어 있기 때문에 용량이 <code>500MB</code>를 넘어가고</li>
<li>코드 실행에 필요한 머신러닝 관련 라이브러리들<code>(YOLO (Ultralytics), PyTorch, OpenCV, NumPy)</code>은 C언어로 컴파일된 파일이 포함되어 있기 때문에(계산 속도를 향상을 위해). 그래서 내 컴퓨터에 설치된 라이브러리 버전(윈도우용)을 그냥 압축해서 람다에 올려버리면 람다의 OS(리눅스)와 충돌한다.  </li>
</ol>
<p>그리하여 이런 용량과 버전충돌 문제를 해결하기 위해 도커를 이용하여 배포하기로 하였다.(도커는 10GB까지 가능하다!)</p>
<h2 id="mangum-라이브러리-설정">Mangum 라이브러리 설정</h2>
<p>먼저 Mangum라이브러리를 설치해주어야 하는데 이게 왜 필요하냐면...</p>
<p>AWS Lambda는 HTTP 서버가 아닌 <strong>이벤트 기반 실행 환경</strong>이기 때문에,
클라이언트의 HTTP 요청을 직접 처리할 수 없다. 대신 API Gateway가 앞단에서 요청을 받아 이를 Lambda Event(JSON) 형식으로 변환해 Lambda에 전달한다.</p>
<p>이때 람다 안의 FastAPI에게 Lambda Event(JSON)객체를 그대로 전해주면 FastAPI가 무슨 말인지 못 알아먹기 때문에 다시 FastAPI가 이해할 수 있는 <strong>ASGI객체</strong>로 변환해주어야 한다.(FastAPI는 ASGI 기반의 웹 애플리케이션 프레임워크이기 때문)</p>
<p>따라서 API Gateway를 통해 전달된 Lambda Event(JSON)를 FastAPI가 이해할 수 있는 ASGI 요청 객체로 변환하는 어댑터 계층이 필요하며, 이 역할을 <code>Mangum</code>이 해줄것이다.</p>
<blockquote>
<ul>
<li>로컬 환경에서는 주로 Uvicorn이라는 웹 서버가 API 게이트웨이와 Mangum의 역할인 HTTP 소켓 연결과 ASGI 변환을 혼자 수행해준다.</li>
</ul>
</blockquote>
<p>Mangum공식 깃허브에 들어가보면 Fastapi를 사용한 Mangum호출법이 나와있다.
<a href="https://github.com/Kludex/mangum">[Mangum 공식 깃허브]</a></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/b5a3286b-433a-4a1f-8c88-3bb467f2fb37/image.png" alt=""></p>
<p>간단하게 Handler변수를 기존 코드 밑에 추가해주면 되는데, Handler는 람다가 신호를 받았을때 처음 실행시키는 진입 함수이다. </p>
<p>만들어둔 Fastapi의 app객체를 Mangum에 전달하고 이걸 handler변수에 저장한다. 그럼 람다가 실행되면 자동으로 이 handler 변수(app객체가 전달된 Mangum 객체)를 찾아서 실행하게 된다. 
<a href="https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/python-handler.html">[파이썬 람다 핸들러 함수 정의]</a></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/b1286e57-2687-455d-bc97-79528d77c5ca/image.png" alt=""></p>
<blockquote>
<p>클라이언트(https 소켓) -&gt;  API 게이트웨이(JSON 변환) -&gt; Lambda -&gt; mangum(ASGI 변환) -&gt; FastAPI 코드 돌아감</p>
</blockquote>
<p>대략적으로 이런 구조를 예상했다.</p>
<h2 id="dockerfile-이미지-만들기">Dockerfile 이미지 만들기</h2>
<p>이제 handler까지 추가해준 코드파일을 도커 이미지로 구워보자. 우선 도커 설정이 들어있는 <code>Dockerfile</code>을 작성해주어야 한다. </p>
<p>작성한 내용은 다음과 같다</p>
<pre><code class="language-docker">FROM public.ecr.aws/lambda/python:3.10

RUN yum install -y mesa-libGL

COPY requirements.txt ${LAMBDA_TASK_ROOT}

RUN pip install --no-cache-dir -r requirements.txt

COPY . ${LAMBDA_TASK_ROOT}

CMD [ &quot;main.handler&quot; ]</code></pre>
<p><code>yum install -y mesa-libGL</code>는 소스 코드에서 사용된 OpenCV와 YOLO가 이미지를 행렬로 계산할 때 사용하는 그래픽 관련 라이브러리인데 람다용 리눅스에는 이게 빠져있기 때문에 별도로 설치해준다. </p>
<p>그리고 <code>requirements.txt</code>의 라이브러리 목록을 설치할때는 용량 확보를 위해 <code>--no-cache-dir</code>옵션을 넣어준다.(보통 pip은 재설치를 대비해 설치 파일을 임시로 저장(Cache)해 두기 때문에.)</p>
<p>라이브러리 설치는 매우 오래 걸리기 때문에 이후 <code>main.py</code> 소스파일 수정이 있을시, 또 다시 처음부터 설치될일이 없도록 Dockerfile 상단에서 먼저 라이브러리를 설치한다. 
Docker는 위쪽 레이어에 변경이 발생하면 그 아래 모든 레이어의 캐시를 무효화하고 다시 실행하기 때문에, 변경 가능성이 낮은 라이브러리 설치 단계를 앞부분에 배치하고 이후에 소스 코드와 모델 파일을 복사하도록 구성한 것이다.</p>
<p><code>.dockerignore</code>에 쓸데없는 파일들까지 같이 빌드되지 않도록 파일을 추가해준다. 가상환경에서 테스트로 여러 라이브러리들을 설치하고 실행해보았가 때문에, 무조건 제외 목록에 추가해주어야 쓸데없는 용량이 소모되지 않고 빌드 시간이 길어지지 않는다.</p>
<pre><code class="language-dockerignore">__pycache__/
venv/
.venv/
.git/
.env</code></pre>
<p>또한 프로젝트에 사용된 라이브러리 목록을 기재한 <code>requirements.txt</code>도 작성해준다. </p>
<pre><code class="language-python"># FastAPI 및 람다 핸들러
fastapi
mangum
python-multipart

# AWS 서비스 연동
boto3

# AI 모델 및 수치 계산 (CPU 전용 버전으로 용량 최적화)
--index-url https://download.pytorch.org/whl/cpu
torch
torchvision
ultralytics

# 이미지 처리 (람다 에러 방지용 화면 띄우는 GUI기능이 없는 headless 버전)
opencv-python-headless
Pillow
numpy</code></pre>
<p><code>--index-url https://download.pytorch.org/whl/cpu</code>는 torch와 torchvision을 보다 가벼운 CPU버전으로 설치하라는 명령어이다. 그냥 설치하면 도커가 무거운 GPU용 파일(수 GB)까지 다 받아오려고 하는데, 이걸 적어주면 람다에 딱 맞는 가벼운 CPU 전용 파일만 찾아낸다. 
<em>애초 람다는 GPU가 없기 때문에 GPU용 파일을 깔아봤자 쓰지도 못하고 실행 속도만 느려진다...</em></p>
<p>이렇게 준비가 되었으면 <code>docker build -t fish-app .</code> 명령어로 도커를 빌드해준다. </p>
<h3 id="🛠️-troubleshooting-docker-pytorch-라이브러리-버전-오류">🛠️ Troubleshooting: Docker pytorch 라이브러리 버전 오류</h3>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/12c52cdd-3744-4f1a-a3ca-c7de55f36e1e/image.png" alt="">
도커 이미지 빌드 중 에러가 뜨며 빌드가 중단되었다.</p>
<p><strong>원인 :</strong>
뜬 에러 메시지는 다음과 같다.
<code>Looking in indexes: https://download.pytorch.org/whl/cpu ERROR: Could not find a version that satisfies the requirement fastapi</code> </p>
<p>라이브러리들을 설치하는 과정에서 에러가 생겼는데, cpu용 pytorch설치하라고 입력해준 주소에서 쌩뚱맞게 fastapi를 찾아서 다운로드 하려다가 없으니까 뜬 에러 메시지이다. 아무래도 <code>--index-url https://download.pytorch.org/whl/cpu</code>명령이 다른 라이브러리들 설치될때도 적용이 되었던 모양이다. </p>
<p><strong>1차 시도 :</strong></p>
<pre><code class="language-python">fastapi
mangum
python-multipart
boto3
opencv-python-headless
Pillow
numpy
ultralytics

# torch 종류만 아래 url에서 다운
--extra-index-url https://download.pytorch.org/whl/cpu
torch
torchvision</code></pre>
<p><code>--extra-index-url</code>명령어로 변경해준다. 해당 명령어는 일단 <code>pip</code>가 <code>PyPI</code>에서 해당 라이브러리를 찾아본다음 없으면 그때 해당 url에서 다운받는 명령어이다.</p>
<p>이제 다시 빌드를 해준다.</p>
<pre><code>=&gt; [4/5] RUN pip install --no-cache-dir -r requirements.txt                                                                                                450.3s
 =&gt; =&gt; # Installing collected packages: triton, nvidia-cusparselt-cu12, mpmath, urllib3, typing-extensions, sympy, six, pyyaml, python-multipart, pyparsing, psuti 
 =&gt; =&gt; # l, polars-runtime-32, Pillow, packaging, nvidia-nvtx-cu12, nvidia-nvjitlink-cu12, nvidia-nccl-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-ru
 =&gt; =&gt; # ntime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, numpy, networkx, MarkupSafe, kiwisolver, jmespath, idna, fsspec, fonttool 
 =&gt; =&gt; # s, filelock, cycler, charset_normalizer, certifi, annotated-types, annotated-doc, typing-inspection, scipy, requests, python-dateutil, pydantic-core, pol
 =&gt; =&gt; # ars, opencv-python-headless, opencv-python, nvidia-cusparse-cu12, nvidia-cudnn-cu12, mangum, jinja2, exceptiongroup, contourpy, pydantic, nvidia-cusolver
 =&gt; =&gt; # -cu12, matplotlib, botocore, anyio, torch, starlette, s3transfer, ultralytics-thop, torchvision, fastapi, boto3, ultralytics  </code></pre><p>그런데 몇십분이 지나도 빌드가 끝나지 않고 딱 봐도 뭔가 웅장한 라이브러리들이 우후죽순 설치되고 있다는 로그가 뜨더니...</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/a658f2af-6607-40c4-a2fa-329f34678ce0/image.png" alt=""></p>
<p>결국 WSL(윈도우용 리눅스 가상 엔진) 용량 부족 엔딩.</p>
<p><strong>원인 :</strong>
<code>--extra-index-url</code> 명령어는 <strong>먼저 <code>PyPI</code>를 살펴보고 <code>PyPI</code>에 존재하지 않으면</strong> <code>https://download.pytorch.org/whl/cpu</code>에서 cpu버전을 다운받는다고 하지 않았는가? </p>
<p>그런데 <strong><code>PyPI</code>에 pytorch GPU버전이 이미 존재하기 때문에</strong> pip는 응? <code>https://download.pytorch.org/whl/cpu</code> 갈 필요 없겠는데? 하고 그냥 <code>PyPI</code>에서 GPU버전 다운받아 버린 거다.</p>
<p>위의 여러 라이브러리들 중 nvidia-로 시작하는 라이브러리들은 GPU 연산용 부품들인데(람다에서 돌리지도 못하는), 이것들이 다 합쳐지면 이미지 용량이 어마어마하니 연약한 내 컴퓨터가 버틸리가 없다.</p>
<p><strong>2차 시도 :</strong>
일단 <code>wsl --shutdown</code>명령어로 wsl을 강제종료 시키고, <code>docker system prune -a --volumes</code> 명령어로 도커 이미지, 컨테이너, 캐시 등등 전부 다 지워 초기화 시켜 용량을 정상화시킨다. (14GB가 지워졌다ㄷㄷ)</p>
<pre><code class="language-python">--extra-index-url https://download.pytorch.org/whl/cpu
torch==2.0.1+cpu 
torchvision==0.15.2+cpu
ultralytics
fastapi
mangum
python-multipart
boto3
opencv-python-headless
Pillow
numpy</code></pre>
<p>이번엔 GPU용 라이브러리 설치를 막기 위해, <code>torch</code>와 <code>torchvision</code> 뒤에 cpu버전까지 확실하게 명시해주었다. </p>
<p>그리고 빌드....</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/d09b01e4-f721-4ee0-b78a-91ad86a37c1f/image.png" alt=""></p>
<pre><code>C:\Users\khsta\OneDrive\바탕 화면\Python\fish_application&gt;docker build -t fish-app .
[+] Building 685.7s (10/10) FINISHED                                                                                                          docker:desktop-linux
</code></pre><p><code>FINISHED</code>문구와 함께 빌드가 성공했다.</p>
<p>*<em>결과 : *</em></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/d306cbcc-289b-4789-a289-5835e0461671/image.png" alt=""></p>
<p>도커 데스크탑에서도 만들어진 이미지를 볼 수 있다.</p>
<p>빌드가 완료되었으니 <code>docker run -p 8080:8080 fish-app</code>명령어로 람다 환경을 모방한 서버를 내 컴퓨터의 8080포트로 띄운다.</p>
<p>그리고 <code>docker run -p 8080:8080 -e AWS_DEFAULT_REGION=ap-northeast-2 fish-app</code> 도커 실행 명령어를 실행하면(<code>-e AWS_DEFAULT_REGION=ap-northeast-2</code>는 리전 환경 변수 정해주는 것)</p>
<pre><code>C:\Users\khsta\OneDrive\바탕 화면\Python\fish_application&gt;curl -XPOST &quot;http://localhost:8080/2015-03-31/functions/function/invocations&quot; -d &#39;{}&#39;
{&quot;errorMessage&quot;: &quot;Unable to unmarshal input: Expecting value: line 1 column 1 (char 0)&quot;, &quot;errorType&quot;: &quot;Runtime.UnmarshalError&quot;, &quot;requestId&quot;: &quot;57fac8c5-7236-4834-a764-ef6f3dd447b1&quot;, &quot;stackTrace&quot;: []}</code></pre><p>다음과 같은 결과가 나온다. 에러메시지이긴 한데 <code>Runtime.UnmarshalError</code>은 분석할 이미지가 함께 오지 않았다는 뜻이라서 일단 서버가 돌아가고 있다는 뜻이다.</p>
<h2 id="ecr-설치">ECR 설치</h2>
<p><code>ECR</code>은 도커 컨테이너 전용 저장소라고 생각하면 된다. 람다에 도커로 배포하려면 무조건 <code>ECR</code>을 거쳐야 한다.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/5c13ea23-6a26-410a-a947-d929fffc8beb/image.png" alt="">
aws 콘솔에서 ECR 리포지토리를 하나 생성해준다.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/09d08c25-0f78-44e4-8bf2-a0e82e6f2851/image.png" alt=""></p>
<p>AWS CLI로 IAM 사용자 로그인을 해 주고</p>
<p>IAM 권한에 <code>EC2ContainerRegistryFullAccess</code>를 추가해주면 된다.(내 IAM계정은 <code>AdministratorAccess</code>가 부여되어 있어 필요없음)</p>
<p>그리고 ECR콘솔창에 있는 <code>푸시 명령 보기</code>버튼을 누르면 
<img src="https://velog.velcdn.com/images/hyun_ji/post/d480932b-a96e-4fe4-9a49-95ad0059937b/image.png" alt="">
다음과 같은 명령어들을 안내해주는데 그냥 복붙하면 된다.(2번은 이미 빌드했으니 패스)</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/eab4a83f-1c1f-47ca-b0b8-83d0c991fff5/image.png" alt="">
이미지가 ECR에 잘 올라갔다.</p>
<h2 id="람다-함수-생성">람다 함수 생성</h2>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/2b8aad77-5de2-4f67-920f-a0e6d5eb9836/image.png" alt="">
이제 람다 함수를 생성해보자. 컨테이너 이미지 옵션을 선택하고 함수 이름을 입력해준뒤 방금 리포에 올린 이미지를 선택해주면 된다. </p>
<h3 id="🛠️-troubleshooting-lambda의-not-supported-이미지-에러-해결">🛠️ Troubleshooting: Lambda의 &#39;Not Supported&#39; 이미지 에러 해결</h3>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/bde39d2b-3635-43c2-a78f-6d0eed19597e/image.png" alt="">
콘솔에서 람다 함수를 생성하려는데 에러 창이 떴다</p>
<p><strong>원인</strong> : 
<a href="https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images-create.html#images-reqs">AWS 람다 공식문서 요구사항</a> 탭을 보면 </p>
<blockquote>
<p>Lambda는 다중 아키텍처 기본 이미지를 제공합니다. 하지만 함수에 대해 빌드하는 이미지는 아키텍처 중 하나만 대상으로 해야 합니다. <strong>Lambda는 다중 아키텍처 컨테이너 이미지를 사용하는 함수를 지원하지 않습니다.</strong></p>
</blockquote>
<p>라고 기재되어 있다. 
도커의 최신 빌드 엔진인 <code>buildx</code>의 <code>v0.10</code> 이상 버전에서는 여러 CPU 환경을 지원하기 위해 기본적으로 <code>OCI image index</code> 형식을 사용해 <strong>멀티 플랫폼 이미지를 빌드하는데</strong>, 위 문서 내용처럼 람다는 다중 아키텍처 컨테이너 이미지(=멀티 플랫폼 이미지)를 지원하지 않아 <strong>manifest not supported</strong> 에러가 발생한것이다.</p>
<p>최신 <code>Docker Buildx</code> 환경에서는 기본적으로 <code>OCI Image Index</code> 형식을 사용하며, 빌드 메타데이터인 Provenance를 포함시킨다. 하지만 AWS Lambda는 단일 플랫폼의 Docker V2 Manifest 포맷만 지원하기 때문에, manifest not supported 에러가 발생한것이다. </p>
<p><strong>해결 방법</strong> : 
간단히 메타데이터인 Provenance를 제외시키는 <code>--provenance=false</code> 옵션을 붙여 형식을 강제 변환시킨 후 다시 빌드해주면 단일 이미지 포멧으로 다시 빌드된다. </p>
<pre><code>docker build --platform linux/amd64 --provenance=false -t fish-app:latest .
</code></pre><p>위 명령어로 다시 이미지를 구워준다.
<img src="https://velog.velcdn.com/images/hyun_ji/post/dbd2d4ea-5d2d-407e-9ccd-b0d33fe22a5d/image.png" alt="">
<strong>결과</strong>: 
<img src="https://velog.velcdn.com/images/hyun_ji/post/7f4b605f-3c80-433c-8347-9a711f093393/image.png" alt="">
굿. 람다함수가 잘 생성되었다.</p>
<p><strong>람다 함수 설정 변경:</strong></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/e1c83001-608c-43e8-bcb5-12608753c0dd/image.png" alt=""></p>
<p>함수가 생성되었으니 설정을 조금 바꿔준다. 무거운 모델이 돌아가야 하기 때문에 메모리와 제한 시간을 2048MB, 1분으로 늘려주었다.(기본설정은 128MB에 3초)</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/ab578536-f192-42d0-bb41-ca408f154f18/image.png" alt=""></p>
<p>또한, AWS Lambda 콘솔 → [구성] → [권한(Permissions)] → 역할 이름(Role name) → [권한 추가] → [정책 연결]에서 <code>AmazonDynamoDBFullAccess_v2</code> 권한을 검색하여** dynamoDB에 대한 권한을 추가해 준다.**</p>
<h2 id="api-게이트웨이">API 게이트웨이</h2>
<p><del>인터넷 게이트웨이랑 다른거다.</del>
람다는 정확히 말하면 일반 서버처럼 데이터를 stream형식으로 받는게 아니라 이벤트에 의해 실행되는 <strong>함수</strong>이기 때문에 <strong>클라이언트와 TCP연결을 맺지 않는다.</strong> 
이때 람다의 앞에서 <code>API 게이트웨이</code>가 대신 클라이언트와 TCP연결을 맺고, 들어온 HTTP 요청을 JSON 이벤트 객체로 변환하여 람다에 넘겨준다.</p>
<blockquote>
<p><strong>왜 JSON으로 변환해줘야 함?</strong>
람다(Lambda) 함수가 &#39;입력값&#39;으로 받을 수 있는 유일한 표준 규격이 JSON 형식의 이벤트 객체이기 때문에.</p>
</blockquote>
<p>_물론 2022년에 <strong>람다 함수 URL(Function URL)</strong>이라는 람다 내장 기능이 추가되어, API 게이트웨이 없이 람다만으로도 고유한 URL 엔드포인트를 생성하고 JSON 규격(Payload Format 2.0)으로 변환하는게 가능해졌지만, API 게이트웨이는 JSON 이벤트 형식 변환 외에도 요청 속도 제한(Throttling), 캐싱, 그리고 보안 및 인증 등 여러 기능을 수행해주기 때문에 앵간하면 람다랑 같이 쓰는 게 좋다. _</p>
<p><a href="https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/api-gateway-basic-concept.html">[API Gateway 개념 설명] </a></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/6e0f4b47-67a1-4b2c-9dca-a77b7b81e78c/image.png" alt="">
람다 함수의 트리거 추가를 눌러 API 게이트웨이를 생성해준다.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/7bd0318d-2b99-4af9-84b4-c67d33f0a188/image.png" alt=""></p>
<h3 id="🛠️-troubleshooting-api-게이트웨이-스테이지-경로-주소-오류">🛠️ Troubleshooting: API 게이트웨이 스테이지 경로 주소 오류</h3>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/99467519-2901-4be3-b702-b35097e00b70/image.png" alt=""></p>
<p>생성된 주소로 Postman을 이용해서 형식을 맞춰 요청을 보내봤지만 계속해서 
<code>404 Not Found</code> 응답이 뜨며 2ms대의 짧은 실행 이후 끊겼다.</p>
<p><strong>원인 :</strong>
Gemini에게 물어보니 <code>&quot;detail&quot;: &quot;Not Found&quot;</code> 메시지는 FastAPI 프레임워크가 공식적으로 내보내는 404 에러 양식이기 때문에, 람다 함수쪽이 문제가 아니라 FastAPI 쪽의 문제일 확률이 높고, 이 경우엔 <strong>서버(FastAPI)가 요청을 받자마자 담당경로가 아니기 때문에 바로 거절을 한 것</strong> 같다고 했다.</p>
<p>API 엔드포인트 주소를 확인해보면 <code>https://아이디.execute-api.리전.amazonaws.com/default/{proxy+}</code> 라고 되어있다. 호스트주소 바로 뒤에 <code>default</code>라는 경로가 붙은 것이 보인다. (<code>{proxy+}</code>는 FastAPI의 api엔드포인트가 오는 곳)</p>
<p>문제는 FastAPI의 라우팅 방식이다. FastAPI는 호스트 주소 이후의 전체 경로인 <code>/default/{proxy+}</code>를 자신의 라우팅 테이블과 대조한다. 하지만 내 서버 코드는 <code>/analyze</code>처럼 루트 경로를 기준으로 설계되어 있다 보니, 앞에 붙은 /default라는 불청객 때문에 경로를 찾지 못하고 404 Not Found를 뱉으며 즉시 종료되었던 것이다.</p>
<blockquote>
<p>그렇다면 저 <code>default</code>의 정체는 무엇인가?</p>
</blockquote>
<p>API Gateway는 개발용(dev), 테스트용(test), 실제 배포용(prod)등의 <strong>버전(Stage)</strong>을 관리하기 위해 <code>https://아이디.execute-api.리전.amazonaws.com/스테이지명/~~</code> 같이 스테이지 이름을 주소에 강제로 포함한다.</p>
<p>현재 나의 코드 같은 경우 <strong>기본(default)</strong> 스테이지를 사용 중이라 주소 중간에 <code>/default</code>가 자동으로 박혀버린 것이다.</p>
<p><a href="https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/http-api-stages.html">[API 게이트웨이의 스테이지]</a></p>
<p><strong>해결 방법1:</strong>
요청 코드 주소 자체를 바꾸어주는 방법이다. </p>
<p>API 게이트웨이의 주소에 맞추어, FastAPI의 루트 주소에 <code>/default</code>를 포함해준다. 
기존의 <code>app = FastAPI()</code>를 다음과 같이 바꾸어준다. </p>
<pre><code class="language-python">app = FastAPI(root_path=&quot;/default&quot;)</code></pre>
<p>이러면 모든 주소 앞에 <code>/default</code>가 자동으로 붙게되어 <code>/default/{proxy+}</code>형식을 만족시킨다.</p>
<p>*<em>해결 방법2: *</em>
람다에 접속하는 URL을 아예 다른 주소로 사용하는 방법이다.</p>
<p>위에서 명시했듯이 <strong>람다 함수 URL</strong>을 사용하면, API 게이트웨이 없이 람다만으로도 고유한 URL 엔드포인트를 생성하고 자체적으로 JSON으로 변환하는것이 가능하다.</p>
<p>람다 함수의 [구성] 탭에서 쉽게 함수 URL을 생성할 수 있다. </p>
<p>두 방법으로 만들어진 주소들을 모두 테스트 해봤더니 <code>200 OK</code> 결과와 함께 정상적으로 분석 결과가 나온것을 볼 수 있었다.
<img src="https://velog.velcdn.com/images/hyun_ji/post/15d4a8e1-d99e-4d64-a08b-5644d13c5350/image.png" alt=""></p>
<p>최종적으로는 API 게이트웨이 대신 함수 URL을 선택했는데, 그 이유로는</p>
<ol>
<li><p>API 게이트웨이의 스테이지 경로 문제가 해결되어 FastAPI의 라우팅이 꼬이는 문제가 다시 생길 걱정이 없고</p>
</li>
<li><p>API Gateway는 요청 횟수에 따라 별도의 요금이 발생하지만, 함수 URL은 완전히 무료이기 때문에 비용 걱정이 없으며</p>
</li>
<li><p>API Gateway는 최대 타임아웃이 29초로 제한되어 있어, 무거운 인공지능 모델(YOLO 등)을 돌릴 때 시간이 부족할 수 있는데, 반면 함수 URL은 람다 자체의 최대 타임아웃인 15분까지 그대로 사용할 수 있어 타임아웃 제한이 완화된다. </p>
</li>
</ol>
<p>물론 API Gateway를 통해 인가(Authorizer)나 유량 제어(Throttling) 등 정교한 기능을 활용할 수 있으나, 나의 프로젝트 같은 경우 <strong>1. 서비스의 복잡도가 낮고 2. 별도의 사용자 인증 및 인가를 요구하지 않는 서비스</strong>이기 때문에 현재 프로젝트의 규모와 빠른 프로토타이핑이라는 목적을 고려했을 때, 불필요한 복잡성을 줄이고 직관적인 아키텍처를 유지할 수 있는 함수 URL이 더 적합하다고 판단하여 최종 선택하게 되었다.</p>
<h2 id="최종-프로젝트-아키텍처">최종 프로젝트 아키텍처</h2>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/158f0fbe-99e5-4de1-8342-374811b52cf1/image.png" alt=""></p>
<blockquote>
<p>추가적인 성능 개선은 해당 포스팅에서 -&gt; <a href="https://velog.io/@hyun_ji/%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%9E%8C%EB%8B%A4-Cold-Start%ED%95%B4">[Lambda Cold Start 해결]</a> </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[막학기 이공계 국가우수장학금 수령]]></title>
            <link>https://velog.io/@hyun_ji/%EB%A7%89%ED%95%99%EA%B8%B0-%EC%9D%B4%EA%B3%B5%EA%B3%84-%EA%B5%AD%EA%B0%80%EC%9A%B0%EC%88%98%EC%9E%A5%ED%95%99%EA%B8%88-%EC%88%98%EB%A0%B9</link>
            <guid>https://velog.io/@hyun_ji/%EB%A7%89%ED%95%99%EA%B8%B0-%EC%9D%B4%EA%B3%B5%EA%B3%84-%EA%B5%AD%EA%B0%80%EC%9A%B0%EC%88%98%EC%9E%A5%ED%95%99%EA%B8%88-%EC%88%98%EB%A0%B9</guid>
            <pubDate>Tue, 07 Oct 2025 08:46:12 GMT</pubDate>
            <description><![CDATA[<p>4학년 2학기 막학기가 되고 마지막 이공계 국가우수장학금을 수령하였다. 
<img src="https://velog.velcdn.com/images/hyun_ji/post/0764e77d-3faf-4538-bae5-b80aec9c8e35/image.png" alt=""></p>
<p>나는 2년 지원 유형이라 3학년 1학기 부터 장학금을 받았다. 장학금 합격 발표가 꽤나 늦게 났던지라 3학년 1학기 장학금은 선감면 대신 추후에 계좌로 입금되었다. 계절학기를 제외한 이후의 정규학기들은 전부 전액면제로 다닐 수 있었다. </p>
<p>이로서 대학교 8학기중 1-1학기 성적장학금을 포함하여 3학년,4학년까지 총 5번의 장학금을 수혜받았다. (감사합니다 열심히 살게요)  </p>
<p>사실 당시에는 전혀 붙을 기대를 하지 않았는데 붙었다는 소식을 듣고, 부모님에게 부담을 덜어드릴 수 있어서 굉장히 기뻤던 기억이 있다. 
<del>소식 듣고 나보다 좋아하심</del></p>
<p>근데 졸업하고 일정기간안에 관련분야로 취업해서 종사하지 않으면 반환해야 된다고 말씀드리니, 취업 할 수 있겠냐고 걱정하시더라 아놔.... (사실 나도 조금 걱정됐음)</p>
<p>근데 최근에 그 의무사항도 폐지되었다고 들었다. 그래도 규정이 언제 또 바뀌었을지 모르니 본인에게 해당되는 요건을 꼼꼼히 잘 살펴보고 확인하자.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/4f79dd19-2d4e-48f4-b1d4-b651444e4c28/image.png" alt=""></p>
<p>(상장이랑 메달도 준다.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 OSI 7Layer 총정리 ]]></title>
            <link>https://velog.io/@hyun_ji/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-OSI-7Layer-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hyun_ji/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-OSI-7Layer-%EC%B4%9D%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 07 Oct 2025 07:28:45 GMT</pubDate>
            <description><![CDATA[<h2 id="osi-7layer-종류-및-특징">OSI 7Layer 종류 및 특징</h2>
<p><strong>Physical Layer</strong></p>
<ul>
<li>통신 주체끼리 연결이 수립되면 연결이 기하급수적으로 많아짐</li>
<li>Hub로 다수의 기기를 연결 </li>
<li>Hub : 받은 신호를 묶여있는 다른애들한테 뿌림(확성기 같이)<ul>
<li>1:1 통신 불가. 오로지 브로드캐스팅만...</li>
<li>충돌 제어 불가</li>
</ul>
</li>
</ul>
<p><strong>Data Link Layer</strong></p>
<ul>
<li>Frame 단위</li>
<li>CSMA/CD(충돌 방지) </li>
<li>Mac 주소라는게 생김<ul>
<li>고유값, 변동 X, 기계에 새겨진 번호같은거(IP는 변동)</li>
<li>유니캐스트(1:1 통신) 가능해짐</li>
</ul>
</li>
<li>Switch를 사용해서 특정 네트워크 내에서 구간을 따로 관리<ul>
<li>포트별로 디바이스(MAC주소)가 매핑된 테이블이 있음 </li>
</ul>
</li>
</ul>
<p><strong>Nerwork Layer</strong></p>
<ul>
<li>Packet 단위</li>
<li>로컬 네트워크간의 연결을 지원 </li>
<li>IP : 인터넷(네트워크와 네트워크 간)에서의 주소라고 생각하면 됨<ul>
<li>MAC주소는 로컬 네트워크 안에서의 주소</li>
<li>수시로 IP 주소 변동됨</li>
</ul>
</li>
<li>CIDR : 네트워크 영역을 나누기 위해 IP주소를 묶는 방식<ul>
<li>ex) 172.0.1.0/24 b -&gt; 네트워크 주소 + 호스트 주소 / 네트워크 주소 bit수</li>
</ul>
</li>
<li>Router : 네트워크간 패깃을 주고받는 장치<ul>
<li>IP 대역별 최적 경로 수집 및 관리<ul>
<li>로컬 네트워크 주소 아니면 라우터로 전달(외부로 보낸다는 뜻)</li>
</ul>
</li>
</ul>
</li>
<li>but. 한 번에 하나의 애플리케이션만 통신 가능, 인터넷상 패킷 손실 가능성 있음</li>
</ul>
<p><strong>Transport Layer</strong></p>
<ul>
<li><p>Segment 단위</p>
</li>
<li><p>통신 주체끼리의 데이터 신뢰성 확보하는 층(패킷 순서, 에러 처리 등)</p>
</li>
<li><p>Port : IP 프로토콜에서 패킷을 올바른 프로세스로 라우팅 하기 위한 논리적 단위</p>
<ul>
<li>목적지 기기에 도착하더라도 여러 프로세스들이 돌아가고 있음</li>
<li>포트 번호를 통해 알맞은 프로세스를 찾을 수 있음</li>
<li>1~1023번은 보통 잘 알려진 서비스(well-known ports)를 위해 예약되어 있음 -&gt; 사내망이나 서버 테스트용 임의 포트 지정할거면 저거 이외에 다른거 써라(ex.8080, 3000, 9000)</li>
<li>다른 외부 서버에 요청을 보낼 때 사용하는 포트는 보통 임시로 할당받는 동적 포트를 사용 </li>
</ul>
</li>
<li><p>TCP : 신뢰성 있는 전송 보장</p>
<ul>
<li>3 way handShake를 통해 패킷의 순서나 전달여부등을 보장</li>
<li>HTTP/HTTPS/FTP/SSH/DNS 등에 사용</li>
</ul>
</li>
<li><p>UDP : 빠르고 간단하게 데이터를 주고 받을 수 있는 방법을 정의</p>
<ul>
<li><p>TCP와 달리 데이터의 무결성, 순서, 전달여부 노상관</p>
</li>
<li><p>그래서 신뢰성은 낮지만 속도가 빠르고 세그먼트 용량이 작음</p>
</li>
<li><p>DNS/DHCP/VoIP(음성통화) 등에 사용</p>
<h3 id="내-노트북에서-사용중인-port-살펴보기">내 노트북에서 사용중인 Port 살펴보기</h3>
<p>windows는 터미널 창에 <code>netstat -ano</code>라고 입력하면 현재 사용중인 포트들을 볼 수 있다. 내 노트북 열린 포트들도 뭐 있나 궁금해서 한 번 살펴보았다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/986f1f7b-1802-4c4c-93e5-f3d3471b7de2/image.png" alt=""></p>
<p><del>뭐 이리 많아</del>
밑에도 한참 더 있었는데 너무 길어서 잘랐다.
궁금해서 상위 2개 포트가 어따 쓰이나를 검색해봤는데 135번 포트는 주로 Windows 운영체제에서 RPC(원격 프로시저 호출) 서비스에 사용되고, 445번 포트는 SMB(서버 메시지 블록) 프로토콜에 사용되며, 네트워크 상에서 파일 및 프린터 공유를 가능하게 하는 Microsoft-DS 서비스에 사용된다고 한다. </p>
</li>
</ul>
<p><strong>Session Layer</strong></p>
<ul>
<li>통신 주체끼리 연결을 유지할 수 있는 방법을 정의</li>
<li>HTTP Cookie 같은것 </li>
</ul>
<p><strong>Presentation Layer</strong></p>
<ul>
<li>Application layer에서 사용할 수 있도록 데이터를 파싱, 압축 해제 함</li>
</ul>
<p><strong>Application Layer</strong></p>
<ul>
<li>실제 받은 데이터를 어떻게 처리할 것인지를 정의 </li>
<li>Http의 경우 Method(ex.Get, Post, Put), Header등을 처리</li>
</ul>
<h2 id="라우터를-통한-네트워크간-통신-과정">라우터를 통한 네트워크간 통신 과정</h2>
<ol>
<li><p>사용자가 <a href="http://www.naver.com%EC%9D%84">www.naver.com을</a> 입력함</p>
</li>
<li><p>DNS서버를 통해 해당 도메인과 매핑된 IP주소를 반환</p>
</li>
<li><p>목적지 주소를 알았으니 IP패킷 생성</p>
</li>
<li><p>목적지 IP가 같은 네트워크인지 확인</p>
</li>
<li><p>다른 로컬 네트워크라면 라우터(게이트웨이)의 IP로 ARP패킷 브로드캐스트</p>
</li>
<li><p>라우터가 자신의 MAC주소를 알려줌. 해당 주소로 패킷 전송</p>
<ul>
<li>이 때 전송되는 패킷의 목적지 IP : 최종 목적지 IP, 목적지 MAC : 라우터의 MAC</li>
</ul>
</li>
<li><p>라우터에 패킷이 도착하고, 라우터가 목적지 IP(최종 목적지) 확인</p>
</li>
<li><p>라우터가 라우팅 테이블을 통해 최적 경로 탐색</p>
</li>
<li><p>NAT로 출발지의 사설 IP 주소를 라우터 자신의 공인 IP주소로 변경. (다시 돌아올때 이거 보고 돌아옴)</p>
</li>
<li><p>라우터가 출발지, 목적지 MAC주소 변경해서 전달. (라우터의 MAC주소, 다음 라우터의 MAC주소로)</p>
</li>
<li><p>다음 라우터는 목적지 IP와 자신의 네트워크 IP 주소 대조. </p>
</li>
<li><p>도착할때까지 10번 반복(ARP 브로드캐스팅 통해서 계속 MAC주소 물어봄)</p>
</li>
<li><p>해당 로컬 네트워크 라우터에 패킷이 도착하면 라우터는 ARP 브로드캐스팅을 통해 최종 목적지의 MAC주소를 얻고 마지막 전송을 함.</p>
</li>
</ol>
<ul>
<li>패킷에 새겨진 MAC주소는 한 번 이동될때마다 계속 바뀌고 최종 IP 목적지주소는 절대 바뀌지 않음 </li>
</ul>
<h2 id="왜-ip주소-개념이-필요한가">왜 IP주소 개념이 필요한가?</h2>
<p>공부하면서 이런 의문이 들었는데 MAC주소 자체가 완전 고유한 번호인데 얘만을 이용해서도 경로를 찾을 수 있지 않나? 굳이 IP주소를 도입해서 왜 주소를 2개로 만들지? 라는 생각이 들었다.</p>
<p>하지만 MAC주소만을 이용해 통신을 하게 되면 전 세계적 통신이 불가능했을 것이다.</p>
<p>우선, MAC주소는 특정 네트워크에 속해 있지 않아서, 하나하나가 인터넷이라는 방대한 공간의 노드가 되어 버린다. 이렇게 되면 효율적 경로를 통해 찾아가는 것이 불가능해지고 하나하나 대조를 해봐야 한다. 전 세계의 모든 기기에....
그리고 브로드캐스트 한 번 했다간 전 세계 사람의 컴퓨터에 요청이 간다...</p>
<p>IP주소는 이러한 단점을 극복하기 위해 만들어진 논리적 주소라고 할 수 있으며 기기들끼리 묶어 로컬 네트워크를 구성할 수 있게 해준다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹서버? WAS? 톰캣? 아파치? ]]></title>
            <link>https://velog.io/@hyun_ji/%EC%9B%B9%EC%84%9C%EB%B2%84-WAS-%ED%86%B0%EC%BA%A3-%EC%95%84%ED%8C%8C%EC%B9%98</link>
            <guid>https://velog.io/@hyun_ji/%EC%9B%B9%EC%84%9C%EB%B2%84-WAS-%ED%86%B0%EC%BA%A3-%EC%95%84%ED%8C%8C%EC%B9%98</guid>
            <pubDate>Wed, 24 Sep 2025 05:03:14 GMT</pubDate>
            <description><![CDATA[<h2 id="웹서버가-뭘까">웹서버가 뭘까?</h2>
<p>위키피디아에 명시된 정의를 그대로 가져오자면</p>
<pre><code>웹 서버(영어: web server)는 웹 콘텐츠(영어판) 배포를 위해 만든 프로토콜 
즉, HTTP 또는 HTTPS를 통해 클라이언트의 요청을 수행하는 서비스 프로그램 및 기반 하드웨어다.</code></pre><p>라고 적혀있다.</p>
<p>당연히 이해가 가지 않으니 조금씩 해석해보자. 
<code>웹 콘텐츠 배포를 위해 만든 프로토콜</code> 
-&gt; 이건 우리가 평소 인터넷에서 보는 보는 사이트나 서비스페이지같은 콘텐츠들을, 불특정 다수가 이용할 수 있도록(배포) 만든 어떠한 통신 약속(프로토콜)이라는 뜻이다.</p>
<p><code>HTTP 또는 HTTPS를 통해 클라이언트의 요청을 수행하는 서비스 프로그램 및 기반 하드웨어</code> 
-&gt; 이 말은 <code>HTTP</code>와 <code>HTTPS</code>라는 어떠한 통신 규칙(프로토콜)이 있는데 이것을 사용해 통신 요청을 보내는 쪽(클라이언트)의 요청을 수행하는 프로그램이나 컴퓨터 기계(하드웨어)라는 뜻이다.</p>
<p>좀 풀어서 설명하긴 했는데 아직도 뭔가 모호하다.</p>
<p>정말 쉽게 요약하자면, 우리가 어디 사이트에 접속할때, 그 사이트의 콘텐츠를 그려내기 위해 html 파일 같은것들이 필요하다. 이러한 사이트의 파일들을 제공해주는 역할을 하는것이 웹서버이다. </p>
<p>웹서버의 종류에는 pache(아파치), Nginx(엔진엑스)등이 있다. </p>
<h2 id="was가-웹서버랑-다른-건가">WAS가 웹서버랑 다른 건가?</h2>
<p>다르다.</p>
<p>고대의 인터넷에는 웹서버만이 존재했다... 모든 사용자에게 똑같은 화면만을 보여주는 <code>정적 웹(static web)</code>만이 있었다. </p>
<p>하지만 인터넷이 널리 보급되고 사용자 수가 폭발적으로 늘며 웹 서버 만으로는 다양한 서비스를 제공할 수 없었다. 예를 들어 사용자마다 다른 정보를 표시하는 일, 실시간 데이터를 띄워서 보여주는 일. 모두 고정된 정적 문서를 제공하는 웹 서버에서는 불가능한 일이었다. </p>
<p>이때 등장한 것이 웹 애플리케이션 서버 WAS(Web Application Server)이다. Web Server는 기존이랑 똑같은데 그 사이에 Application이라는 단어가 들어갔다. 애플리케이션이 무엇인가? 애플리케이션은 특정 작업을 실행되도록 프로그래밍된 소프트웨어이다. 다시말해, 기존의 딱딱한 웹 서버에 소프트웨어 기능이 붙은 서버이다.</p>
<p>WAS는 <code>정적 웹</code> 페이지와 반대되는 <code>동적 웹</code>페이지를 생성할 수 있다. 
<code>동적 웹</code>은 말 그대로 실시간으로 html문서를 만들어 낼 수 있었다.</p>
<p>이러한 WAS와 웹 서버는 이후 백엔드에서 상호 보완적인 관계를 이루게 되는데 특정 로직이 없는 페이지는 정적 페이지는 웹서버가, 로직이 필요한 동적 페이지는 웹서버가 WAS로 요청을 전달해 WAS가 대신 처리할 수 있도록 한다. </p>
<p>우리가 많이 들어본 Apache Tomcat (아파치 톰캣)또한 WAS의 한 종류이다. </p>
<h2 id="웹서버-만들어보기">웹서버 만들어보기</h2>
<p>이제 FastAPI를 이용해 간단하게 서버를 만들어보자. </p>
<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/&quot;)
async def read_root():
    return {&quot;message&quot;: &quot;Server is connected!&quot;}</code></pre>
<p>이 5줄의 코드를 통해 현재 웹서버가 만들어졌다!
FastAPI는 Uvicorn이라는 WAS 역할을 주로 수행하는 웹서버와 함께 쓰인다.</p>
<p>이제 <code>필자의컴퓨터IP주소:포트번호</code> 를 브라우저 주소창에 입력하면</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/9c99b283-b4be-432c-9b7b-c8fe9a30d7cb/image.png" alt=""></p>
<p>다음과 같이 서버로부터 응답을 받을 수 있다!</p>
<p>이제 <code>동일한 네트워크</code>에서 해당 주소를 입력하면 어떤 기기에서든 동일한 응답값을 받을 수 있다!</p>
<hr>
<p>잠깐, <code>동일한 네트워크</code>라는 게 무슨 뜻일까?</p>
<p>네트워크를 공부한 사람을 알겠지만, 현재 내 IP주소는 공유기가 할당해준 고유한 번호, 말하자면 정확한 <strong>도로명 주소가 아닌 철수네 집, 영희네 집 같은 임시 주소번호</strong>이다. </p>
<p>당연히 우리 집 근처에 사는 사람들은 영희네 집, 철수네 집(IP 내부 주소)라고만 해도 어디인지 알겠지만, 저 멀리 미국 텍사스에 사는 제임스한테 영희네 집이라고 한다면 당연히 어딘지 모른다. 영희라는 이름을 가진 사람이 한 두 명도 아니고...(IP 내부 주소는 서로 다른 네트워크에서 중복이 가능하기 때문)</p>
<p>그래서 같은 공유기에 연결된 기기에서는 방금 만든 서버에 접속이 잘 되지만, 
그 외의 외부 기기들에서는 <code>필자의IP주소:포트번호</code>를 입력해봤자, IP주소가 영희네 집 같은 알 수 없는 주소이기 때문에 입력해도 접속이 안 된다. </p>
<h3 id="그럼-어떡해">그럼 어떡해!!</h3>
<p>나는 전세계적으로 서비스를 하는 초대형 글로벌 서비스를 만들고 싶은데 이러면 구멍가게 밖에 못 만들지 않나!! 싶지만 우리가 많이 들어본 AWS 같은 클라우드 서버를 사용하여 도로명주소같은 공인 IP주소를 할당받을 수 있다. </p>
<p>공인 IP주소는 <code>대한민국 00시 00동</code>같이 전 세계 어디에서나 인터넷을 통해 접근할 수 있는 공식적인 주소이다. 그래서 AWS 서버를 이용한다면 다른 컴퓨터나 스마트폰이 어떤 네트워크에 있든 상관없이 항상 접속할 수 있다.</p>
<p>물론 <code>포트 포워딩</code>등을 사용해 클라우드 서버를 사용하지 않고도 내 컴퓨터 자체를 외부에서 바로 접속이 가능하게 만드는 방법도 있긴 하다. 다만 이 방법은 보안도 취약하고 설정도 좀 만져야해서 꽤나 번거롭다고...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP, TCP/UDP/IP, Socket]]></title>
            <link>https://velog.io/@hyun_ji/HTTP-TCPUDPIP-Socket</link>
            <guid>https://velog.io/@hyun_ji/HTTP-TCPUDPIP-Socket</guid>
            <pubDate>Wed, 24 Sep 2025 05:02:50 GMT</pubDate>
            <description><![CDATA[<h2 id="http-request와-response의-구조">HTTP Request와 Response의 구조</h2>
<p>HTTP는 HyperText Transfer Protocol의 약자로,
웹에서 클라이언트(브라우저 등)와 서버 간에 데이터를 주고받기 위한 통신 규약(Protocol)이다.</p>
<p>쉽게 말하면 웹에서 문서, 이미지, 영상 등을 요청하고 응답받기 위한 약속된 통신 방법.</p>
<p>HTTP는 요청-응답을 기반으로 하기 때문에 HTTP의 메시지 형태도 요청/응답 2가지 이다.</p>
<p>클라이언트가 HTTP request를 서버에 보내면 서버는 HTTP response를 보내는 구조</p>
<p><strong>HTTP Request Message</strong></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/3000c8ce-2ec7-4fb0-b4e6-cd12afaad310/image.png" alt=""></p>
<p><strong>HTTP Response Message</strong></p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/802ea6ef-76de-46e3-a0a8-85f57e6a849e/image.png" alt=""></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Messages">https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Messages</a></p>
<h2 id="tcpudpip">TCP/UDP/IP</h2>
<h3 id="ip">IP</h3>
<p>IP는 컴퓨터(호스트)들 사이에서 데이터 패킷을 목적지까지 전달하는 역할을 하는 인터넷 계층의 핵심 프로토콜이다.</p>
<p><strong>IP의 핵심 역할</strong></p>
<ol>
<li>주소 지정 (Addressing)</li>
</ol>
<ul>
<li>각 컴퓨터(=노드)는 고유한 IP 주소를 갖는다.</li>
<li>예) 192.168.1.12, 8.8.8.8, 2001:db8::1</li>
</ul>
<ol start="2">
<li>패킷 전달 (Routing)</li>
</ol>
<ul>
<li><p>IP는 데이터를 여러 네트워크를 거쳐 목적지까지 전달함.</p>
</li>
<li><p>중간에 <strong>라우터(router)</strong>가 경로를 결정하고, 패킷을 넘겨줌.</p>
</li>
</ul>
<ol start="3">
<li>비신뢰성 (Unreliable)</li>
</ol>
<ul>
<li><p>IP는 전달만 해줄 뿐, 순서를 보장하지 않음.</p>
</li>
<li><p>중간에 패킷이 유실될 수도 있음</p>
</li>
<li><p>중복되거나 깨져도 복구 안 해줌</p>
</li>
<li><p>이건 TCP가 처리해줘야 하는 영역임!</p>
</li>
</ul>
<p><strong>IP 주소 체계</strong></p>
<ol>
<li><p>IPv4
32비트 주소 (총 2³² = 약 43억 개)
형식: 192.168.0.1</p>
</li>
<li><p>IPv6
128비트 주소 (엄청 많음… 340 언쩌구 x 10³⁶ 개)
형식: 2001:0db8:85a3:0000:0000:8a2e:0370:7334</p>
</li>
</ol>
<h3 id="tcp">TCP</h3>
<p>신뢰성 있는 데이터 전송을 보장하는 전송 계층(Transport Layer)의 대표적인 프로토콜. 데이터를 정확하고 순서대로 전송하는 데 최적화되어 있다.</p>
<p><strong>TCP의 핵심 특징</strong></p>
<ol>
<li><p>연결 지향(Connection-Oriented)
데이터를 보내기 전, 3-way handshake로 연결을 먼저 설정함</p>
</li>
<li><p>신뢰성 보장(Reliability)
데이터 유실 시 재전송, 순서 보장, 중복 제거</p>
</li>
<li><p>흐름 제어(Flow Control)
수신 측의 수용 능력을 고려해 전송량 조절</p>
</li>
<li><p>혼잡 제어(Congestion Control)
네트워크 상태에 따라 전송 속도 조절</p>
</li>
</ol>
<h3 id="udp">UDP</h3>
<p>빠르게 데이터를 전송하고 싶은 상황에서 사용하는 전송 계층(Transport Layer)의 대표적인 프로토콜</p>
<p>3-way handshake를 통해 연결 지향적인 통신을 하는 TCP와 달리 비연결성이라는 특징을 띈다. 클라이언트에서 서버로 마구 데이터를 보내는 것이다(받았다는 응답이 없어도).</p>
<p>이런 특성때문에 속도가 중요하고 약간의 데이터 손실은 크게 상관이 없는 통화나 스트리밍 서비스에 많이 사용되는 프로토콜이다.</p>
<h2 id="socket">Socket</h2>
<p>네트워크에서 통신하는 두 개의 프로그램 간의 연결을 설정하고 데이터를 전송하기 위한 인터페이스.</p>
<p>IP주소와 포트 번호를 사용하여 네트워크 상의 특정 컴퓨터와 특정 서비스에 연결된다</p>
<p><strong>소켓 통신</strong></p>
<p>소켓을 이용하여 두 프로그램(서버-클라이언트) 간에 데이터를 주고 받는 과정. TCP와 UDP같은 네트워크 프로토콜을 사용</p>
<p>소켓은 프로토콜,IP주소,포트 넘버로 정의된다.</p>
<p><img src="https://velog.velcdn.com/images/hyun_ji/post/e16d3d4b-223e-44a7-9446-7c70a15079c6/image.png" alt=""></p>
<h2 id="echo-server">Echo Server</h2>
<p>클라이언트가 전송해주는 데이터를 그대로 되돌려 전송해 주는 기능을 가진 서버를 말한다.</p>
<p>데이터를 그대로 되돌려 받기 때문에 클라이언트와 서버가 제대로 연결됐는지 테스트 용도로 많이 쓰인다.</p>
<h2 id="client와-server">Client와 Server</h2>
<p>통신이 일어날 때 항상 요청을 시작하는 쪽은 클라이언트, 응답을 처리하는 쪽은 서버</p>
<h2 id="session과-connection">Session과 Connection</h2>
<p>언듯 비슷한 개념 같지만 그 역할과 범위가 다르다.</p>
<h3 id="connection">Connection</h3>
<p>클라이언트와 서버 사이에 데이터를 주고받기 위한 통신 통로가 만들어진 것.</p>
<p>ex) TCP 3-way handshake를 통한 연결</p>
<h3 id="session">Session</h3>
<ul>
<li>클라이언트와 서버 간에 연속된 상호작용을 하나의 논리적인 단위로 묶은 것.</li>
<li>연결과는 다르게 &quot;이 사용자가 누구고, 어떤 상태인가&quot;를 추적할 수 있어야 함.</li>
<li>즉, 연결 위에서 유지되는 사용자 상태 추적 단위.</li>
<li>연결 위에서 유지되기 때문에 세션은 연결에 의존함.</li>
</ul>
<p>ex) 로그인 세션, 채팅 세션</p>
<h2 id="unicast--broadcast--multicast">Unicast / Broadcast / Multicast</h2>
<p><strong>Unicast</strong></p>
<ul>
<li>유니캐스트는 가장 일반적인 형태의 데이터 전송 방식으로, 한 개의 송신자가 한 개의 수신자에게 데이터를 전송하는 방식.</li>
<li>이 방식은 특정 대상과 1:1로 통신할 때 사용되며, 인터넷에서 흔히 볼 수 있는 데이터 전송 방식.</li>
<li>신자와 수신자 각각의 고유한 IP 주소를 사용</li>
<li>예) 웹 서버에서 클라이언트 컴퓨터로 웹 페이지를 보내는 경우</li>
</ul>
<p><strong>Broadcast</strong></p>
<ul>
<li>브로드캐스트는 한 개의 송신자가 네트워크 내 모든 장치에게 데이터를 동시에 전송하는 방식입니다.</li>
<li>같은 로컬 네트워크(LAN) 내의 모든 장치에게 신호나 메시지를 보낼 때 사용됨.</li>
<li>브로드캐스트에서는 네트워크 내 모든 장치에 데이터를 전송하기 위해 특정한 브로드캐스트 주소를 사용하는데, 반적으로 이 주소는 IP 네트워크의 마지막 주소로 설정됨</li>
<li>예) 네트워크 상의 모든 장치에게 서비스나 알림을 보내야 할 때</li>
</ul>
<p><strong>Multicast</strong></p>
<ul>
<li>멀티캐스트는 한 개의 송신자가 특정 그룹의 수신자들에게만 데이터를 전송하는 방식</li>
<li>데이터 스트리밍이나 실시간 데이터 전송에 효과적</li>
<li>멀티캐스트에서는 특정 멀티캐스트 그룹의 주소를 사용하고 이 주소는 224.0.0.0에서 239.255.255.255 범위에 속한다.</li>
<li>각 멀티캐스트 그룹은 고유한 IP 주소를 갖고 있으며, 그룹의 멤버만이 해당 주소로 보내진 데이터를 수신한다.</li>
<li>예) 라이브 비디오 방송이나 원격 회의 시스템</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[벡터 데이터베이스 ]]></title>
            <link>https://velog.io/@hyun_ji/%EB%B2%A1%ED%84%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@hyun_ji/%EB%B2%A1%ED%84%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Wed, 24 Sep 2025 05:01:14 GMT</pubDate>
            <description><![CDATA[<h2 id="벡터-데이터베이스란">벡터 데이터베이스란?</h2>
<p>텍스트, 이미지, 오디오 등의 데이터를 고차원 벡터로 표현해 저장하는 데이터베이스입니다. 오늘날 대부분의 데이터는 미리 정의된 구조나 형식이 없는 텍스트, 이미지, 오디오 등의 <strong>비정형 데이터</strong>이다. 이러한 형태가 일관적이지 않은 데이터들을 RDBMS에 넣기란 어려운 일이다.</p>
<p>이미지를 예로 들자면 이미지는 픽셀값이 저장되기 때문에 특정 이미지를 찾기 위해선 태그나 속성(ex. 고양이, 강아지)을 수동으로 입력해야 한다.</p>
<p>하지만 벡터 데이터베이스는 벡터 임베딩을 통해 인덱싱하고 저장하여 빠른 검색 및 유사성 검색이 가능하다. 다시말해
데이터의 의미 기반 유사성 검색이 가능해진다는 말이다.</p>
<p>그렇다면 벡터 임베딩은 무엇일까?</p>
<h2 id="벡터-임베딩">벡터 임베딩</h2>
<p>임베딩은 텍스트나 이미지 같은 데이터를 숫자 리스트, 즉 벡터로 바꾸는 방식이다. 이 벡터들은 데이터의 의미나 특성을 담고 있다.</p>
<p>의미가 비슷한 항목은 벡터 공간에서도 가까운 위치에 놓이게 되기 때문에 코사인 유사도나 유클리드 거리 등을 통해 서로 간의 유사도를 계산하여, 유사한 항목을 쉽게 찾을 수 있다.</p>
<p>벡터 데이터베이스는 이러한 임베딩들을 효율적으로 저장하고 검색하기 위해 만들어졌다. 기존 데이터베이스처럼 정확히 일치하는 값을 찾는 게 아니라, 유사한 것을 찾는 방식이다.</p>
<h2 id="word2vec">word2vec</h2>
<p>대표적인 단어 임베딩 신경망 모델이다. Word2Vec에는 CBOW와 Skip-gram이라는 두 가지 대표적인 구조가 있다.</p>
<p><strong>CBOW</strong></p>
<p>CBOW의 기본 아이디어는 주변 단어를 통해 주어진 단어를 예측하는 것이다.
총 단어가 c개라고 할 때 앞뒤로 c/2개의 단어를 통해 주어진 단어를 예측한다.</p>
<p><strong>Skip-gram 구조 (중심 → 주변 예측)</strong></p>
<p>Skip - gram은 CBOW와는 반대로 하나의 단어에서 여러 단어를 예측하는 방법이다. 즉 중심단어에서 주변단어를 예측하는 방식인데 CBOW보다 성능이 좋아 더 많이 쓰인다.</p>
<h2 id="벡터-인덱싱">벡터 인덱싱</h2>
<p>벡터 임베딩이 비정형 데이터를 숫자 벡터로 바꾸는 것이라면, 인덱싱은 벡터 공간에 위치한 이미 만들어진 벡터들끼리 비슷한 걸 빠르게 찾도록 구조화하는 작업이라고 할 수 있다.</p>
<p>벡터 DB에서 데이터를 검색할 때 KNN(기준 벡터와 가장 가까운 K개의 벡터를 찾아주는 방법)이 사용되는데, 벡터 인덱싱은 그 작업을 더 빠르게 하기 위해 사용된다.</p>
<p>벡터 공간을 쪼개거나, 비슷한 벡터끼리 그래프처럼 연결하거나, 트리 구조로 나누거나 이런 다양한 방식으로 비슷한 애들끼리 미리 묶어놓고 검색 범위를 줄인다.</p>
<p><strong>vector Indexing 기법</strong></p>
<ol>
<li><p><strong>Flat Index</strong></p>
<p>별도의 인덱싱 기법 없이 벡터를 저장하는 방법이다.
모든 벡터들과 유사도를 계산해 가장 높은 유사도를 지닌 벡터를 찾는다. 일반적으로 10000~50000개 정도이 벡터에서 적당한 성능과 높은 정확성을 얻을 수 있다.
하지만 실제로 이렇게 사용한다면 벡터 데이터가 더 많아 탐색 속도가 느려질 것이다.</p>
</li>
<li><p><strong>Random Projection</strong></p>
<p>랜덤한 벡터를 만들어서 원래 벡터와의 내적을 시켜서 차원을 줄
이는 방법이다.
원래 벡터와의 유사성도 유지할수 있고 고차원의 원래 벡터보다
탐색속도가 빠를수 있다고 한다.
하지만 Approxiamtion이기 때문에 정확도는 다소 떨어질수 있다.</p>
</li>
<li><p><strong>PQ (Product Quantization)</strong></p>
<p>원래 벡터를 균등하게 몇개의 서브벡터로 쪼개고, 각 서브벡터들
을 Quantization하여 크기를 줄이는 방법이다.
빠르고 정확도도 좋으며 큰 데이터셋에서 사용하기 좋은 기법이라
고 한다. Quantiztion으로 값을 표현하는데 사용하는 bit수를 줄
이기 때문에 메모리 사용량도 적고, 탐색 속도도 빠르다.
하지만 근본적으로 표현하는 숫자의 범위를 제한하는 방법이기 때
문에 정확도는 다소 떨어질수 있다.</p>
</li>
<li><p><strong>LSH (Locality-Sensitive Hashing)</strong></p>
<p>원래 벡터를 hashing 함수에 한번 돌려서 bucket에 매핑하고,
bucket에서 비교하여 찾는 방법이다.
유사도 검색을 할때도 쿼리문에 대한 벡터를 동일한 해싱함수를
사용해서 같은 버킷에 있는 벡터들하고만 비교하여 찾는다.
전체 데이터가 아니라 버킷에 있는 데이터안에서만 찾기 때문에
탐색속도가 빠르다.
하지만 버킷이 많을수록 정확도가 높아지겟지만, 더 많은 메모리
가필요하게 된다.</p>
</li>
<li><p><strong>HNSW (Hierarchical Navigable Small World
grahp)</strong></p>
<p>원래 벡터를 가지고 N개의 레이어를 가진 그래프를 생성하고, 그
래프를 기반으로 가장 가까운(유사도가 높은)결과를 찾아내는 방
법이다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[VCS, GIT 내부 동작 원리, SHA1알고리즘, Fork, Clone]]></title>
            <link>https://velog.io/@hyun_ji/VCS-GIT-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@hyun_ji/VCS-GIT-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Wed, 24 Sep 2025 05:00:45 GMT</pubDate>
            <description><![CDATA[<h2 id="vcs">VCS</h2>
<p>VCS는 Version Control System의 약자로 버전관리 시스템을 이르는 말이다. 이름 그대로 파일의 변화를 버전에 따라 기록하고 관리하기 때문에, 파일을 이전 상태로 쉽게 복구할 수 있다.</p>
<h2 id="git-동작-원리">git 동작 원리</h2>
<p>버전 관리 시스템의 한 종류인 git을 사용하기 위해선 처음에 <code>git init</code>이란 명령어를 실행해야한다. 새 디렉터리나 기존 디렉터리에서 <code>git init</code>을 실행하면 Git은 <code>.git</code>디렉터리를 생성하는데, 이 디렉터리에 Git이 저장하고 조작하는 파일들, 트리 구조, 커밋 등 모든 것이 저장된다.</p>
<p>git 공식 문서를 보면 config, description, hooks/등 여러 파일과 디렉토리가 생성되지만 그 중 가장 중요한 것은 역시 objects 디렉토리이다.(이 단계에서 index파일은 아직 생성되지 않는다.) objects디렉토리는 데이터베이스의 모든 콘텐츠와 버전 정보들을 객체 형태로 저장하는데 이 객체들의 종류는 다음과 같다.</p>
<ul>
<li>blob</li>
<li>tree</li>
<li>commit</li>
</ul>
<p>그럼 이제 git add 명령어와 git commit 명령어를 입력하면 각 객체들이 object파일에 어떻게 저장되는지 흐름을 따라가며 알아보자.</p>
<ol>
<li>git add</li>
</ol>
<ul>
<li>파일1이라는 제목의 파일을 작성한다.</li>
<li><code>git add 파일1</code>이라는 명령어를 실행할시, 파일1의 내용은 <strong>SHA1 해시알고리즘</strong>을 통해 일정한 길이의 문자열로 변환된다.</li>
<li>해시된 값의 앞에서 2글자는 object밑의 디렉토리명이 되고, 나머지 해시값은 해당 디렉토리 밑에 파일명이 된다. 이 파일이 <strong>blob객체</strong>다! 그리고 그 blob객체 파일의 내용에 파일1의 원본 내용이 들어있게 된다.</li>
<li>그러면 blob객체에는 실질적으로 파일의 내용만 담기게 되는데 파일제목같은건 어디에 저장될까? 바로 <strong>index</strong>파일에 저장된다.</li>
<li>index파일은 첫 add 실행시 생성되고, 여기에 Git의 스테이징 영역 정보를 저장한다. (스테이징 영역은 커밋 될 준비가 된 파일들이 위치한 영역을 뜻한다.)</li>
<li>index파일에는 해당 파일의 파일이름, 해시값등이 담기게 된다. 나중에 이 정보들을 보고 커밋을 할 수 있다.</li>
<li>간단하게 말하면 파일의 내용은 objects디렉토리 밑에, 파일의 이름은 index파일에 저장되게 된다.</li>
</ul>
<ol start="2">
<li>git commit</li>
</ol>
<ul>
<li>스테이징 영역에 담긴 파일을 커밋하면, 하위트리와 blob으로 구성된 tree 정보, 커밋한 사용자 정보, 커밋 메시지 등을 담은 commit객체가 만들어진다.</li>
<li>tree는 index정보를 바탕으로 만들어진다.</li>
<li>여기서 만약에 파일을 다시 수정하고 add 한 후에(여기서 index파일의 해시값도 당연히 업데이트 된다.) 또 commit을 하면 새로운 tree가 생성되는데, 만약 커밋한 파일들 중 내용이 변경되지 않은 파일이 있다면 그 파일은 이전 커밋에서의 해시값과 동일한 값을 가진다. 재활용하는 것이다. 그리고 새로 생성된 commit오브젝트는 첫 commit오브젝트를 parent로 가진다.</li>
</ul>
<p><a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects">https://git-scm.com/book/en/v2/Git-Internals-Git-Objects</a>
공식문서에 git의 동작흐름이 이해하기 쉽게 그림과 예시와 함께 자세하게 잘 나와있다.</p>
<h2 id="sha1-zlib">SHA1? zlib?</h2>
<p>blob객체를 생성할때 SHA1알고리즘을 사용한다고 하는데 이게 뭘까?
SHA-1 해시 값은 40자리 고정 문자열로, 내용이 같으면 무조건 같은 해시 값이 나온다. 이걸 통해서 git은 해시 값을 통해 파일이 변경되었는지, 그대로인지 판단해서 변경된 파일(해시값이 바뀐 파일)만 새로 저장한다.</p>
<p>sha256은 SHA-1보다 더 강력한 해시 알고리즘이다. SHA-1은 160비트 40자리 해시값이고, SHA-256은 256비트 64자리 해시값이다. 이 덕분에 sha256는 SHA-1보다 보안에 훨씬 더 강력하다. 또한 같은 해시값이 만들어질 가능성 또한 매우 적다.</p>
<p>zlib는 또 무엇인가. 간단히 말하자면 압축 알고리즘이다. Git은 저장 효율을 높이기 위해, 저장하는 객체를 zlib 압축해서 .git/objects/에 저장한다.</p>
<p>node.js에는 SHA와 zlib 두 개의 알고리즘 모듈이 모두 내장되어 있다!</p>
<h3 id="파일-상태-용어">파일 상태 용어</h3>
<p><strong>&lt;Tracking 단계&gt;</strong>
git init 직후에는 모든 파일이 <strong>Untracked</strong> (추적 안됨) 상태, git add &lt;파일&gt;을 하면 → <strong>Tracked</strong> (추적됨) 상태로 전환됨</p>
<p>Git이 &quot;이 파일은 나중에 커밋할지 말지 확인해야겠군!&quot; 하고 계속 지켜보는 게 Tracking</p>
<p><strong>&lt;Modified 단계&gt;</strong>
추적 중인(Tracked) 파일을 수정해서 저장소와 내용이 다른 상태</p>
<p><strong>&lt;Staged 단계&gt;</strong>
커밋할 준비가 완료된 상태. Git이 “얘는 커밋할 거야” 하고 인덱스(index)에 올려둔 파일</p>
<h3 id="fork-vs-clone">Fork vs Clone</h3>
<p>이건 예전부터 계속 헷갈렸던 개념이었는데 둘 다 대충 남의 코드를 복사해온다는 느낌으로 알고 있어서 차이점을 그리 자세하게 알진 못했다.</p>
<p>좀 더 정확하게 비교를 하자면 <code>Fork</code>는 남의 원격 저장소 -&gt; 내 원격 저장소로 옮기는 작업이다. 이건 당연히 Git의 기능이 아니라 GitHub같은 원격 저장소 서비스에서만 존재하는 기능이다. 보통 외부 프로젝트에 PR을 보내고 싶을때 Upstream저장소(원본 저장소)에 push할 권한을 가지고 있지 않기 때문에 본인의 원격 저장소로 작업물을 복사해오는 걸 뜻한다. 이렇게 되면 원본과 내 저장소 사이에 연결이 생겨 PR요청을 보내는 것이 가능해진다. </p>
<p><code>Clone</code>은 원격 저장소 -&gt; 로컬 저장소로 복사하는 Git의 명령어이다. 복사할 저장소는 내꺼든 남의거든 상관없이 복사해 올 수 있지만 외부 프로젝트에 연결은 안 되어 있기 때문에 나중에 내 원격저장소에 코드를 올려도 PR이 불가능하다.</p>
<p>보통 오픈 소스 프로젝트에 기여할때는 해당 저장소의 push 권한이 없기 때문에 <code>Fork</code> -&gt; <code>Clone</code>의 순서를 따른다.</p>
<p>권한이 부여된 경우 굳이 번거롭게 외부 저장소를 만들 필요 없이 해당 원본 프로젝트 내에서 직접적으로 브랜치를 통해 작업하기 때문에 <code>Clone</code>만으로 프로젝트에 참여하는 경우가 많다고 한다. </p>
<p>결론적으로 둘 다 저장소를 복사하는건 맞는데 그 용도나 복사되는 장소, 목적등이 다르다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로세스, 스레드, 레이스 컨디션, IPC]]></title>
            <link>https://velog.io/@hyun_ji/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8A%A4%EB%A0%88%EB%93%9C-%EB%A0%88%EC%9D%B4%EC%8A%A4-%EC%BB%A8%EB%94%94%EC%85%98-IPC</link>
            <guid>https://velog.io/@hyun_ji/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8A%A4%EB%A0%88%EB%93%9C-%EB%A0%88%EC%9D%B4%EC%8A%A4-%EC%BB%A8%EB%94%94%EC%85%98-IPC</guid>
            <pubDate>Wed, 24 Sep 2025 04:59:12 GMT</pubDate>
            <description><![CDATA[<h2 id="프로세스와-스레드">프로세스와 스레드</h2>
<p>프로세스가 가족이라면 스레드는 그 가족 구성원 한명한명 이라고 볼 수 있다. 프로세스는 실행 중인 프로그램 인스턴스 그 자체를 가리키는데 스레드는 그 인스턴스에서 실행되고 있는 여러 작업 흐름들을 말한다.</p>
<p><strong>멀티 프로세스? 멀티 스레드?</strong></p>
<p>학습을 하며 든 의문이 멀티 프로세스 개념이 있는데 왜 멀티 스레드라는 개념을 또 만든거지? 라는 생각이었다. 여러 작업을 동시에 처리할 수 있도록 해 주는것은 프로세스를 여러 개 사용하는 멀티 프로세스로도 가능하기 때문이다. 하지만 안 쓰는데는 이유가 있었다.</p>
<p>프로세스는 데이터 공유를 하지 않는다. 즉, 하나의 프로세스가 실행될때, 메모리에 고유의 영역을 할당받고 그 안의 데이터를 다른 프로세스와 공유하지 않는다. 이렇게 되면 당연히 컨텍스트 스위칭(쉽게말해 CPU에서 실행중인 프로세스를 변경하는것)을 할 때 데이터들을 전부 넘겨주어야 하니 작업이 무거워질 수밖에 없다.</p>
<p>그러나 스레드는 같은 프로세스의 스레드라면 전부 같은 메모리를 공유한다(Stack영역만 빼고). 그래서 프로세스와는 다르게 컨텍스트 스위칭이 훨씬 가볍다!</p>
<h2 id="경쟁-조건race-condition">경쟁 조건(race condition)</h2>
<p>다만 멀티 스레드도 당연히 단점이 있다. 스레드는 하나의 프로세스에게 할당된 메모리중에서 스택을 뺀 나머지 영역의 데이터를 공유하는데(힙, 텍스트, 데이터) 이 때문에 <code>경쟁 조건(race condition)</code>이 발생할 수도 있다.</p>
<p><code>경쟁 조건(race condition)</code>이란 여러 스레드나 프로세스가 동시에 같은 자원에 접근하면서 실행 순서에 따라 결과가 달라지는 오류가 발생할 수 있는 상황을 말하는데, 다시 말해 자원을 공유하는 스레드가 병렬적으로 여러 개 존재하다 보니 스레드1과 스레드2가 공유 자원을 거의 동시에 접근해서 변경하면 값이 덮어써지거나, 중복처리 되는 등 해당 변수는 결과가 이상해질 수 있다.</p>
<p>그래서 멀티 프로세스와 멀티 스레드는 공유 자원 동기화가 필요하다! --&gt; 경쟁 조건은 공유자원의 문제가 아니라 실행작업을 동시에 하는게 문제인 것임</p>
<h2 id="멀티-프로세스스레드-어떻게-동기화-시켜야-하나">멀티 프로세스/스레드 어떻게 동기화 시켜야 하나?</h2>
<p>경쟁 조건이 발생하는 이유는 프로세스나 스레드가 동일한 작업을 동일한 시점에 하기 때문에 발생하는 것이다. 그렇다면 해당 작업에 하나의 프로세스/스레드만 진입이 가능하게 하면 경쟁조건이 발생할 가능성이 없어지지 않을까? 해당 함수나 모듈 내에서 사용중인 프로세스/스레드의 유무값을 저장하고, 이후 요청이 들어오면 그 유무값에 의해 거절하든 사용하라고 하든 하면 될 것이다!</p>
<p>이러한 개념을 <strong>critical section(임계 영역)</strong>이라고 한다. 공유 데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역이라는 개념이다. 그리고 이러한 임계 영역에 진입하기 위한 요건을 확인하는 영역을 <strong>entry section</strong>이라고 한다.</p>
<p>그렇다면 공유 불가능한 자원의 동시 사용을 방지해주는 상호 배제 알고리즘에는 어떤 것들이 있을까.</p>
<p><strong>스핀락</strong> - lock값을 이용해 관리하는 방법이다. 실행중인 프로세스나 스레드가 있다면, lock 값이 실행 중 상태에서 벗어날 때까지 반복해서 시도하고, 실행중인 프로세스나 스레드가 완료되면, 비로소 lock값을 얻어 임계 영역에 진입이 가능해진다.
다만 이 방식은 기다리는 동안 계속해서 lock을 확인하기 때문에 CPU를 낭비한다는 단점이 있다.</p>
<p><strong>뮤텍스</strong> - 스핀락과는 다르게 계속해서 문을 두드리는게 아니라 예약을 걸어놓는다고 생각하면 이해가 쉽다. 만약 사용중이라면 해당 스레드를 큐에 넣어 예약을 걸어놓고, 기존 작업이 끝나면 큐에서 값을 꺼내 실행을 시킨다.</p>
<p><strong>세마포어</strong> - 뮤텍스와 어느 정도 유사하나 임계 영역에 여러 프로세스나 스레드가 들어갈 수 있음.</p>
<h2 id="ipc">IPC?</h2>
<p>멀티 스레드는 자원을 같이 쓰기 때문에 메모리가 자동적으로 공유된다. 그러나 멀티 프로세스는 메모리가 분리되어 있기 때문에 따로 데이터를 주고받는 통신이 필요하다. 그게 바로 IPC이다.</p>
<p>여러 프로세스가 공동 작업하거나, 하나의 프로세스가 다른 프로세스에게 신호나 명령을 전달할 때나, 분산 시스템, 병렬 처리 등에서 프로세스 간 협력이 필요할 때 IPC가 사용된다. IPC는 OS에서 제공하는 통신 메커니즘을 사용한다.</p>
<p>이러한 IPC는 락, 뮤텍스, 세마포어와 결합되어 상호 배제를 위해서도 사용될 수 있다.</p>
<h2 id="비동기-이벤트-매니저">비동기 이벤트 매니저</h2>
<p>비동기 이벤트 매니저(Asynchronous Event Manager)는 이벤트 기반 프로그래밍에서 이벤트를 등록하고, 발생시키고, 처리하는 과정을 비동기 방식으로 관리하는 시스템 또는 객체를 말한다.</p>
<h2 id="publisher-subscriber-패턴">Publisher-Subscriber 패턴</h2>
<p>🎙️ Publisher
: 어떤 <strong>이벤트(메시지)</strong>가 발생했다고 알리는 쪽</p>
<p>👂 Subscriber
: 어떤 이벤트가 발생하면 <strong>반응(처리)</strong>하는 쪽</p>
<p>이벤트를 발행한 쪽과 구독한 쪽이 서로를 몰라도 작동하는 구조</p>
<h2 id="비동기-대기큐">비동기 대기큐</h2>
<p>지금 당장 실행하지 않고, 나중에 실행해야 할 작업(콜백, 이벤트 처리 등)을 임시로 저장해두는 대기열이다.</p>
<h2 id="event-emitter">Event Emitter</h2>
<p>Node.js에서 이벤트 기반 프로그래밍을 할때 사용되는 클래스이다. Node.js에서 가장 기본적인 이벤트 처리 방식 중 하나이다. EventEmitter 클래스를 상속한 객체를 만들고, on() 메서드를 사용해 이벤트 리스너를 등록하여 이벤트가 발생할 때마다 등록된 콜백 함수가 실행된다.</p>
<p>이벤트는 문자열 형태의 이름과 함께 발생하며, 이벤트에 대한 데이터를 선택적으로 전달할 수도 있다.</p>
<pre><code class="language-javascript">const { EventEmitter } = require(&quot;events&quot;);
const emitter = new EventEmitter();

emitter.on(&quot;hello&quot;, (msg) =&gt; console.log(&quot;받음:&quot;, msg));
emitter.emit(&quot;hello&quot;, &quot;안녕&quot;);</code></pre>
<p><code>emitter</code>인스턴스는 이벤트 이름에 해당하는 콜백 리스트를 Map 형태로 내부에 저장한다.</p>
<p><code>.on</code>메서드를 통해 이벤트를 등록하고(이건 subscriber 등록을 의미한다), <code>.emit</code>메서드를 통해 이벤트를 발생시킨다(Publisher가 이벤트 발생시킴).</p>
<h2 id="promise">Promise</h2>
<p>Promise는 비동기 작업을 수행하는 함수가 반환하는 객체로, 성공하면 결과 값을 반환하고 실패하면 에러를 반환한다. 이러한 특성으로 Promise는 비동기 작업의 성공 또는 실패 상태를 쉽게 확인하고 처리할 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>