<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>개복치의 벨로그</title>
        <link>https://velog.io/</link>
        <description>아무것도 머르게떠염</description>
        <lastBuildDate>Tue, 07 Oct 2025 12:01:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>개복치의 벨로그</title>
            <url>https://velog.velcdn.com/images/chae_yu/profile/d2151b11-d142-441c-9815-76c3b4e89c61/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 개복치의 벨로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/chae_yu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[설계] 1장 사용자 수에 따른 규모 확장성]]></title>
            <link>https://velog.io/@chae_yu/%EC%84%A4%EA%B3%84-1%EC%9E%A5-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</link>
            <guid>https://velog.io/@chae_yu/%EC%84%A4%EA%B3%84-1%EC%9E%A5-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</guid>
            <pubDate>Tue, 07 Oct 2025 12:01:42 GMT</pubDate>
            <description><![CDATA[<p>&#39;가상 면접 사례로 배우는 대규모 시스템 설계 기초&#39; 책 기반 정리!!!!</p>
<hr>
<h1 id="1️⃣-사용자-수에-따른-규모-확장성">1️⃣ 사용자 수에 따른 규모 확장성</h1>
<h2 id="💻-단일-서버">💻 단일 서버</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/7fe41d39-64e9-4010-9b74-8bd836766611/image.png" alt=""></p>
<p>*<em>아주 간단한 서버의 사용자 요청 처리 흐름 *</em></p>
<ol>
<li>사용자는 도메인 이름을 검색해서 웹사이트에 접속. 이때, DNS (Domain Name System)에 질의하여 IP 주소로 변환하는 과정을 거침</li>
</ol>
<ul>
<li>이때 DNS는 제3 사업자가 제공하는 유료 서비스를 이용하기에 우리 시스템의 일부는 아님</li>
</ul>
<ol start="2">
<li><p>DNS 조회 결과로 반환된 IP 주소로 HTTP 요청을 전달</p>
</li>
<li><p>요청을 받은 웹 서버는 HTML 홈페이지나 JSON 형태의 응답 반환</p>
</li>
</ol>
<p>*<em>실제 요청은 두 가지 종류(웹 앱, 모바일 앱)의 단말로부터 오는 실제 요청
*</em></p>
<ul>
<li><p>웹 애플리케이션: 비지니스 로직, 데이터 저장 등을 처리하기 위해 자바, 파이썬 등의 서버 구현용 언어를 사용하고 표현용으로는 자바 스크립트 등의 클라이언트 구현용 언어를 사용</p>
</li>
<li><p>모바일 앱: 모바일 앱과 웹 서버 간의 통신을 위해서는 HTTP 프로토콜을 이용하고 보통 JSON 형식의 응답 데이터가 반환</p>
</li>
</ul>
<hr>
<h2 id="💻-데이터베이스">💻 데이터베이스</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/b1911411-53cf-428c-8cdb-cbb91a16d367/image.png" alt=""></p>
<p>사용자가 증가함에 따라 서버를 확장하면 웹/모바일 트래픽 처리용 서버 하나와 데이터베이스 서버를 분리해서 각각을 독립적으로 확장해나감</p>
<h3 id="🌀-데이터베이스-종류">🌀 데이터베이스 종류</h3>
<h4 id="관계형-데이터베이스">관계형 데이터베이스</h4>
<ul>
<li>관계형 데이터베이스는 관계형 데이터베이스 관리 시스템 &#39;RDBMS&#39;라고도 불림 ex) MySQL, 오라클 데이터베이스, PostgreSQL 등</li>
<li>자료를 텡블과 열, 칼럼으로 표현</li>
<li>SQL을 사용하면 여러 테이블에 있는 테이블을 관계에 따라 조인할 수 있음</li>
</ul>
<h4 id="비관계형-데이터베이스">비관계형 데이터베이스</h4>
<ul>
<li>NoSQL이라고도 불림 ex) CouchDB, Neo4j, Cassandra, Amazon DynamoDB</li>
<li>NoSQL은 &#39;키-값 저장소&#39;, &#39;그래프 저장소&#39;, &#39;칼럼 저장소&#39;, &#39;문서 저장소&#39; 네 가지로 불리</li>
<li>일반적으로 조인 연산은 지원되지 않음</li>
</ul>
<p>대부분 관계형 데이터베이스를 사용하지만 아래의 경우 비관계형 데이터베이스가 적합함</p>
<ol>
<li>아주 낮은 응답 지연시간이 요구되는 경우</li>
<li>다루는 데이터가 비정형이라 관계형 데이터가 아닌 경우</li>
<li>데이터를 직렬화하거나 역질렬화하기만 하면 되는 경우</li>
<li>아주 많은 양의 데이터를 저장할 필요가 있는 경우</li>
</ol>
<hr>
<h2 id="💻-수직적-규모-확장-vs-수평적-규모-확장">💻 수직적 규모 확장 vs 수평적 규모 확장</h2>
<h4 id="수직적-규모-확장">수직적 규모 확장</h4>
<ul>
<li><p>&#39;스케일 업&#39;이라고도 하는 수직적 규모 확장은 <strong>서버에 고사양 자원 (CPU, 더 많은 RAM)을 추가하는 것</strong>으로 서버로 유입되는 트래픽의 양이 적을 때는 수직적 확장이 더 좋은 선택</p>
</li>
<li><p>장점</p>
<ul>
<li>단순함</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li>한 대의 서버에 CPU나 메모리를 무한대로 증설할 방법이 없기에 한계가 있음</li>
<li>장애에 대한 자동복구 방안이나 다중화 방안을 제시하지 않기에 서버에 장애가 발생하면 서비스가 중지됨</li>
</ul>
</li>
</ul>
<h4 id="수평적-규모-확장">수평적 규모 확장</h4>
<ul>
<li>&#39;스케일 아웃&#39;이라고도 하는 수평정 규모 확장은 <strong>더 많은 서버를 추가하여 성능을 개선하는 행위</strong></li>
<li>대규모 애플리케이션을 지원할 때는 수평적 확장이 적합</li>
</ul>
<h3 id="🌀-로드밸런서">🌀 로드밸런서</h3>
<p>: 웹 계층의 장애 복구나 다중화를 지원하는 기술</p>
<ul>
<li>사용자가 웹 서버에 바로 연결하는 경우, 웹 서버가 다운되거나 너무 많은 사용자가 접속하면 사용자가 서비스를 이용하는데 어려움이 있음. 이런 경우를 방지하기 위해 로드밸런서를 도입</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/eb36bb8f-839c-4192-8f9f-69a2564e3024/image.png" alt=""></p>
<ul>
<li>로드밸런서는 부하 분산 집합에 속한 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 함. 쉽게 말하면 트래픽을 분산하기 위해 적절히 서버를 선택해주는 애라고 생각하면 됨</li>
<li>사용자가 웹 서버로 바로 연결되는게 아니라 로드밸런스의 공개 IP 주소로 접속하게 됨</li>
<li>이때, 보안을 위해 서버 간 통신에서는 사설 IP 주소를 이용</li>
</ul>
<blockquote>
<p>사설 IP 주소란?
: 같은 네트워크에 속한 서버 사이의 통신에만 쓰이는 IP 주소로, 인터넷을 통해서는 접속할 수 없음</p>
</blockquote>
<p>로드밸런서가 동작하는 방식은 다음과 같음</p>
<ol>
<li>서버 1이 다운되면 모든 트래픽은 서버 2로 전송되기 때문에 웹 사이트 전체가 다운되는 일이 방지됨. 혹은 새로운 서버를 추가할 수도 있음</li>
<li>웹사이트로 유입되는 트래픽이 많아지면 로드밸런서를 통해 자동적으로 트래픽을 분산하게 됨</li>
</ol>
<h3 id="🌀-데이터베이스의-다중화">🌀 데이터베이스의 다중화</h3>
<p>: 데이터 계층의 장애복구나 다중화를 지원하는 기술</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/d0632de6-de69-490a-84b6-cff6b208c6e8/image.png" alt=""></p>
<ul>
<li><p>보통 서버 사이에 주(master)-부(slave) 관계를 설정하고 데이터 원본은 주서버에, 사본은 부서버에 저장</p>
</li>
<li><p><strong>쓰기 연산</strong>은 마스터에서만 지원하고 부 데이터베이스는 주 데이터베이스로부터 사본을 전달받음</p>
</li>
<li><p><strong>데이터베이스를 변경하는 명령어</strong>는 주 데이터베이스로만 전달되어야 함</p>
</li>
<li><p>대부분의 애플리케이션은 쓰기 연산보다 읽기 연산을 더 많이 하기에 부 데이터베이스의 수가 더 많음</p>
</li>
<li><p>장점</p>
</li>
</ul>
<ol>
<li><strong>성능 향상</strong>: 데이터 변경 연산은 주 데이터베이스 서버로, 읽기 연산은 부 데이터베이스 서버로 분산되어 병렬로 처리되는 쿼리의 수가 늘어나 성능이 좋아짐</li>
<li><strong>안정성</strong> 혹은 <strong>신뢰성</strong>: 데이터베이스 일부가 파괴되어도 데이터는 보존됨. 지역적으로 떨어진 여러 장소에 다중화시켜놓을 수 있음</li>
<li><strong>가용성</strong>: 데이터를 여러 지역에 복제해 둠으로써 하나의 데이터베이스 서버에 문제가 발생해도 다른 서버에 있는 데이터를 사용하면 됨</li>
</ol>
<blockquote>
<p><strong>Q. 데이터베이스 서버 중 하나가 다운되면?</strong></p>
</blockquote>
<ol>
<li>부 서버가 한대 뿐인데 다운된 경우, 읽기 연산은 일시적으로 모두 주 데이터베이스로 전달되고 새로운 부 데이터베이스 서버가 장애 서버를 대체할 것</li>
<li>부 서버가 여러 대인 경우, 읽기 연산은 나머지 부 데이터베이스 서버로 분산될 것</li>
<li>주 서버가 다운되면, 한 대의 부 데이터베이스가 새로운 주 서버로 대체될 것이고 새로운 부서버가 추가될 것임</li>
<li>부 서버에 보관된 데이터가 최신 상태가 아닐 수도 있기에 없는 데이터는 복구 스크립트를 돌려서 추가해야함</li>
</ol>
<p>+) 다중 마스터나 원형 다중화 방식을 도입하면 도움될 수 있으나 복잡함</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/c8c38221-4a84-4360-b538-9cb80a97efab/image.png" alt="">
로드밸런서와 데이터베이스 다중화 둘 다 적용한 설계는 위와 같음</p>
<ol>
<li>사용자는 DNS로부터 로드밸런스의 공개 IP 주소를 받아서 접속함</li>
<li>HTTP 요청이 로드밸런스에 의해 서버1이나 서버2로 전달됨</li>
<li>웹 서버는 사용자의 데이터를 부 데이터베이스 서버에서 읽음</li>
<li>웹 서버에서 데이터 변경 연산이 필요한 경우 주 데이터베이스로 전달됨</li>
</ol>
<h2 id="💻-캐시">💻 캐시</h2>
<p>: 값 비싼 연산 결과나 자주 참조되는 데이터를 메모리 안에 두고 빨리 처리될 수 있도록 하는 저장소</p>
<h3 id="🌀-캐시-계층">🌀 캐시 계층</h3>
<ul>
<li>데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠름</li>
<li>성능 개선 및 데이터베이스의 부하를 줄일 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/1b8fd6b4-e40c-4027-b07e-a91c878e5337/image.png" alt=""></p>
<ol>
<li>요청을 받은 웹 서버가 캐시에 데이터가 있는지 확인</li>
<li>저장되어있다면 해당 데이터 클라이언트에 반환 -&gt; &#39;주도형 캐시 전략&#39;</li>
</ol>
<p>** 캐시 전략은 캐시할 데이터의 종류, 크기, 엑세스 패턴에 따라 다양하므로 맞는 전략 사용하면 됨</p>
<h3 id="🌀-캐시-사용-시-유의할-점">🌀 캐시 사용 시 유의할 점</h3>
<p><strong>Q1. 캐시는 어떤 상황에 바람직한가?</strong>
A1. 데이터 갱신이 자주 일어나지 않지만 참조가 빈번하게 일어나는 경우</p>
<p><strong>Q2. 어떤 캐시를 데이터에 두어야하는가?</strong>
A2. 캐시는 휘발성이기 때문에 영속적으로 보관할 데이터를 두는 것은 바람직하지 않음</p>
<p><strong>Q3. 캐시에 보관된 데이터는 어떻게 만료되는가?</strong>
A3. 캐시 만료 정책을 만료해두어야함. 너무 짧지도, 너무 길어서도 안됨</p>
<p><strong>Q4. 장애에는 어떻게 대처하는가?</strong>
A4. 여러 대의 캐시 서버를 둠으로써 SOF 방지</p>
<p><strong>Q5. 캐시 사이즈의 크기는?</strong>
A5. 캐시 메무리가 너무 작으면 데이터가 너무 자주 캐시에서 밀려나서 성능이 떨어지게 됨. 이를 방지하기 위해 캐시 메로리를 과할당하면 됨</p>
<p><strong>Q6. 데이터 방출 정책은?</strong>
A6. 일반적으로 LRU(Least Recently Used) 정책을 사용하거나 LFU, FIFO 등이 있어 경우에 맞게 사용하면 됨</p>
<hr>
<h2 id="💻-콘텐츠-전송-네트워크-cdn">💻 콘텐츠 전송 네트워크 (CDN)</h2>
<p>: 정적 콘텐츠를 전송하는 데 쓰이는 지리적으로 분산된 서버의 네트워크</p>
<ul>
<li>이미지, 비디오, CSS, Javascript 등을 캐시할 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/42a237f0-e858-4d5d-8eea-334ab2b80577/image.png" alt=""></p>
<ol>
<li>사용자 A가 이미지 URL을 이용해 image.png에 접근. 이때 URL의 도메인은 CDN 서비스 사업자가 제공</li>
<li>CDN 서버 캐시에 해당 이미지가 없는 경우, 서버는 원본 서버에 요청하여 파일을 가져옴 </li>
</ol>
<p>+) 원본 서버는 웹 서버이거나 S3같은 온라인 저장소
3. 원본 서버가 파일을 CDN 서버에 반환하고 응답 HTTP 헤더에는 해당 파일의 TTL 값이 들어있음
4. CDN 서버는 파일을 캐시하고 사용자 A에게 반환
5. 다른 사용자가 해당 이미지에 대한 요청을 하게 되면 만료되지 않은 이미지의 경우 캐시를 통해 처리됨</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/5a6690c8-74cd-47de-827b-75bccd823a93/image.png" alt=""></p>
<p>CDN과 캐시가 추가되면 정적 콘텐츠는 웹 서버를 통해 서비스되지 않고 CDN을 통해 제공하고 캐시를 통해 데이터베이스 부하를 줄임으로써 성능 개선</p>
<hr>
<h2 id="💻-무상태-웹-계층">💻 무상태 웹 계층</h2>
<ul>
<li>웹 계층을 수평적으로 확장하기 위해서는 사용자 세션 데이터 등의 상태 정보를 웹 계층에서 제거해야 함</li>
<li>상태 정보를 관계형 데이터베이스나 NoSQL 같은 지속성 저장소에 보관하고 필요할 때 가져오도록 하는 전략으로 구성된 웹 계층을 무 상태 웹 계층이라함</li>
</ul>
<h3 id="🌀-상태-정보-의존적인-아키텍처">🌀 상태 정보 의존적인 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/1a81ec24-e18e-4f00-8b74-2c3aadd651c9/image.png" alt=""></p>
<p>상태 정보를 보관하는 아키텍처의 경우 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송되어야하는 문제가 존재함</p>
<h3 id="🌀-무상태-아키텍처">🌀 무상태 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/1a8a05de-0e4e-4344-be2e-94af82e70da6/image.png" alt=""></p>
<ul>
<li>상태 정보가 필요할 경우 공유 저장소로부터 데이터를 가져옴</li>
<li>상태 정보가 웹 서버로부터 물리적으로 분리되어있기에 단순하고, 안정적이며 규모 확장이 쉬움</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/58bbdf0c-83f6-4ba1-a627-d74332ce3d05/image.png" alt=""></p>
<ul>
<li>세션 데이터를 웹 계층에서 분리하고 지속성 데이터 보관소에 보관하도록 함. 공유 저장소는 관계형 데이터베이스나 캐시 시스템, NoSQL일 수 있음</li>
<li>&#39;1 자동 규모 확장&#39;은 상태 정보가 웹 서버로부터 제거되었기 때문에 트래픽 양에 따라 웹 서버를 넣거나 뺌으로써 자동으로 규모를 확장할 수 있게됨 즉 오토 스케일링이 가능케됨</li>
</ul>
<hr>
<h2 id="💻-데이터-센터">💻 데이터 센터</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/c56a867d-f0aa-476f-9b61-1713e967bd17/image.png" alt=""></p>
<ul>
<li>장애가 없는 상황에서 사용자에게 가까운 데이터 센터를 이용하게 되는데, 이를 <strong>지리적 라우팅</strong>이라고함</li>
<li>지리적 라우팅에서 getoDNS는 사용자의 위치에 따라 도메인 이름을 어떤 IP 주소로 변환할지 결정할 수 있도록 해주는 서비스</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/07d6d5f6-281b-4efe-ba37-54533ca55a1d/image.png" alt=""></p>
<ul>
<li>하나의 데이터 센터에 장애가 발생하면 모든 트래픽이 장애가 없는 데이터 센터로 전송됨</li>
</ul>
<h3 id="🌀-다중-데이터센터-아키텍처-고려사항">🌀 다중 데이터센터 아키텍처 고려사항</h3>
<p><strong>1. 트래픽 우회</strong>
: 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야함</p>
<p><strong>2. 데이터 동기화</strong>
: 데이터 센터마다 별도의 데이터베이스를 사용한다면 데이터센터마다 다른 데이터가 존재할 수 있음. 이런 상황을 막기 위해 데이터를 여러 데이터 센터에 걸쳐 다중화함</p>
<p><strong>3. 테스트와 배포</strong>
: 여러 데이터 센터를 사용하는 경우 웹 사이트를 여러 위치에서 테스트하는 것이 중요함</p>
<hr>
<h2 id="💻-메시지-큐">💻 메시지 큐</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/97054357-406b-437d-a398-4ded8426bac8/image.png" alt=""></p>
<ul>
<li>메시지 큐란, 메시지의 무손실을 보장하는 비동기 통신 컴포넌트로 생산자가 메시지 큐에 메시지를 발행하면 큐에 연결되어있는 소비자가 메시지를 받아 그에 맞는 동작을 수행함</li>
<li>메시지 큐를 이요하면 서비스, 서버 간 결합이 느슨해져서 규모 <strong>확장</strong>하기에 용이함</li>
<li>또, 프로세스가 다운되어 있어서 메시지를 발행할 수 있음</li>
<li>예를 들어, 시간이 오래 걸리는 프로세스인 사진 보정 애플리케이션의 경우 비동기적으로 사진 보정 작업 프로세스를 처리하면 됨</li>
</ul>
<hr>
<h2 id="💻-로그-메트릭-자동화">💻 로그, 메트릭, 자동화</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/a10d454c-afea-4979-865e-589c540b637e/image.png" alt=""></p>
<ul>
<li>로그: 시스템의 오류와 문제를 보다 쉽게 찾아낼 수 있도록 모니터링이 필요</li>
<li>메트릭: 메트릭을 수집하면 사업 현황에 관한 유용한 정보 및 시스템의 현재 상태를 손쉽게 파악할 수 있음<ul>
<li>호스트 단위 메트릭: CPU, 메모리, 디스크 I/O에 관한 메트릭</li>
<li>종합 메트릭: 데이터베이스 계층의 성능, 캐시 계층의 성능</li>
<li>핵심 비지니스 메트릭: 일별 능동 사용자, 수익, 재방문 등</li>
</ul>
</li>
<li>자동화: CI/CD 등의 자동 빌드, 테스트, 배포 등으로 개발 생산성 향상 가능</li>
</ul>
<hr>
<h2 id="💻-데이터베이스의-규모-확장">💻 데이터베이스의 규모 확장</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/90748142-1ca8-44d9-952f-8191c51f6d25/image.png" alt=""></p>
<h3 id="🌀-수직적-확장">🌀 수직적 확장</h3>
<ul>
<li>고성능의 자원을 확장하는 방법으로 하드웨어 증설에는 한계가 있기도 하고 비용이 많이 드는 문제점이 있음</li>
</ul>
<h3 id="🌀-수평적-확장">🌀 수평적 확장</h3>
<ul>
<li>데이터베이스의 수평적 확장은 &#39;샤딩 (sharding)&#39;이라고 부름</li>
<li>샤딩은 대규모 데이터베이스를 샤드라고 부르는 작은 단위로 분할하는 기술</li>
<li>샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/579ba954-6d1e-44dc-a527-67ff71611753/image.png" alt=""></p>
<ul>
<li>위 사진의 경우, 사용자 아이디에 따라 정함 </li>
<li>샤딩 전략을 구현할 때 <strong>샤딩 키</strong>를 어떻게 정하는지가 가장 중요함 -&gt; <strong>데이터를 고르게 분할하는 것이 목적</strong><ul>
<li><strong>데이터의 재 샤딩</strong>: 데이터가 너무 많아져서 하나의 샤드로 감당하기 어려울 때, 샤드 간 데이터 분포가 균등하지 못해 특정 샤드의 공간 소모가 다른 샤드에 비해 빨리 진행될 때 </li>
<li><strong>유명인사 문제</strong>: 특정 샤드에 쿼리가 집중되어 서버에 과부하가 걸림. 
ex) 유명인사가 같은 샤드에 저장되는 데이터베이스의 경우 이 샤드에 많은 읽기 연산이 몰려 과부하가 걸리게 될 것임</li>
<li><strong>조인과 비정규화</strong>: 하나의 데이터베이스를 여러 샤드 서버로 나누게 되면 여러 샤드에 걸친 데이터베이스를 조인하기 어려워짐. 이를 해결하기 위해 데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 함</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/829a16f4-9f66-4140-9e5b-c4db068972c1/image.png" alt=""></p>
<hr>
<h2 id="💻-정리">💻 정리</h2>
<ul>
<li>웹 계층은 무상태 계층</li>
<li>모든 계층에 다중화 도입</li>
<li>가능한 한 많은 데이터 캐시</li>
<li>여러 데이터 센터 지원</li>
<li>정적 콘텐츠는 CDN을 통해 서비스</li>
<li>데이터 계층은 샤딩을 통해 규모 확장</li>
<li>각 계층은 독립적 서비스로 분할</li>
<li>시스템의 지속적인 모니터링과 자동화 도구 활용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 자바 정렬]]></title>
            <link>https://velog.io/@chae_yu/TIL-%EC%9E%90%EB%B0%94-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@chae_yu/TIL-%EC%9E%90%EB%B0%94-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Tue, 23 Sep 2025 06:33:11 GMT</pubDate>
            <description><![CDATA[<p>파이썬은 정렬이 참 간단한데 자바도 물론 메소드로 지원해주긴 하지만 타입에 따라 달라서..,. 정리해두겠다!!!!</p>
<p>백준 20006번 문제를 푸다가 nickname을 기준으로 정렬을 해야하는 상황이 있었다.
<img src="https://velog.velcdn.com/images/chae_yu/post/afa2befd-a544-4e9f-a56b-9ad83ea2dee5/image.png" alt=""></p>
<pre><code class="language-java">static class Player {
        int level;
        String nickname;

        public Player() {
        }

        public Player(int level, String nickname) {
            this.level = level;
            this.nickname = nickname;
        }

        @Override
        public String toString() {
            return level + &quot; &quot; + nickname;
        }
    }

    static class Room {
        int baseLevel;
        List&lt;Player&gt; players = new ArrayList&lt;&gt;();

        public Room() {
        }

        // 추가 가능 여부
        boolean canJoin(Player p, int m) {
            return players.size() &lt; m &amp;&amp;
                    p.level &gt;= baseLevel - 10 &amp;&amp;
                    p.level &lt;= baseLevel + 10;
        }
    }

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

        List&lt;Player&gt; playerList = new ArrayList&lt;&gt;();

        int p = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        // 플레이어 레벨, 닉네임
        for (int i = 0; i &lt; p; i++) {
            st = new StringTokenizer(br.readLine());
            int l = Integer.parseInt(st.nextToken());
            String n = st.nextToken();
            Player player = new Player(l, n);
            playerList.add(player);
        }

        List&lt;Room&gt; roomList = new ArrayList&lt;&gt;();

        for (Player player : playerList) {
            boolean conJoin = false;
            // 방 리스트 돌면서 들어갈 수 있는 방 찾기
            for (Room room : roomList) {
                if (room.canJoin(player, m)) {
                    room.players.add(player);
                    conJoin = true;
                    break;
                }
            }

            // 들어갈 수 있는 방 없으면
            if (!conJoin) {
                Room room = new Room();
                room.baseLevel = player.level;
                room.players.add(player);
                roomList.add(room);
            }
        }

        // 출력
        // 닉네임은 사전 순
        for (Room room : roomList) {
            if (room.players.size() == m) {
                System.out.println(&quot;Started!&quot;);
            } else {
                System.out.println(&quot;Waiting!&quot;);
            }

            // 닉네임 순으로 정렬해서 출력
            room.players.sort((o1, o2) -&gt; o1.nickname.compareTo(o2.nickname));

            for (Player player : room.players) {
                System.out.println(player);
            }

        }
    }
</code></pre>
<p>바로 이 부분!!! 이였다</p>
<pre><code class="language-java">room.players.sort((o1, o2) -&gt; o1.nickname.compareTo(o2.nickname));</code></pre>
<p>자바는 <strong>타입</strong>별로 정렬하는 방식이 다르다.</p>
<h3 id="💭-기본-타입-배열-정렬">💭 기본 타입 배열 정렬</h3>
<p>int, double 등 기본 타입의 배열을 정렬하기 위한 방법은 다음과 같다</p>
<p>🌀 <strong>오름차순</strong></p>
<pre><code class="language-java">int[] arr = {3, 1, 2};
Arrays.sort(arr);  </code></pre>
<p>🌀 <strong>내림차순</strong>
내림차순은 오름차순보다 복잡하다. int를 Integer로 변경해야한다. 그리고 <code>Collections.reverseOrder()</code>를 사용해서 정렬해준다.</p>
<pre><code class="language-java">int[] arr = {3, 1, 2};
Integer[] intergerArr = new Integer[arr.length];
for (int i=0; i &lt; arr.length; i++){
     intergerArr[i] = arr[i];
}
Arrays.sort(intergerArr, Collections.reverseOrder());</code></pre>
<blockquote>
<p><code>Collections.reverseOrder()</code>은 Integer[], Double[] 과 같은 객체 타입 배열에만 사용 가능</p>
</blockquote>
<h3 id="💭-객체-타입-배열-정렬">💭 객체 타입 배열 정렬</h3>
<p>🌀 <strong>오름차순</strong></p>
<pre><code class="language-java">Integer[] nums = {3, 1, 2};
Arrays.sort(nums);  // 오름차순 정렬</code></pre>
<p>🌀 <strong>내림차순</strong></p>
<pre><code class="language-java">Integer[] nums = {3, 1, 2};
Arrays.sort(nums, Collections.reverseOrder());  // 내림차순 정렬</code></pre>
<h3 id="💭-list-t--정렬">💭 List&lt; T &gt; 정렬</h3>
<p><code>List&lt;Integer&gt;</code>, <code>List&lt;String&gt;</code>과 같은 기본 타입 객체 리스트는 객체 타입 배열과 같지만 <code>Collections.sort()</code>를 사용하면 된다</p>
<p>🌀 <strong>오름차순</strong></p>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;d&quot;);
list.add(&quot;c&quot;);
list.add(&quot;a&quot;);
Collcetions.sort(list);</code></pre>
<p>🌀 <strong>내림차순</strong></p>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;d&quot;);
list.add(&quot;c&quot;);
list.add(&quot;a&quot;);
Collcetions.sort(list, Collcetions.reverseOrder());</code></pre>
<h3 id="💭-객체-리스트-정렬">💭 객체 리스트 정렬</h3>
<p>이게 바로 오는 알고리즘을 푸면서 마주친 형태!!! <code>List&lt;Room&gt;</code>, <code>List&lt;Player&gt;</code>와 같은 클래스가 타입일때는 다음과 같이 하면 된다.</p>
<p>예를 들어 </p>
<pre><code class="language-java">static class Player {
        int level;
        String nickname;

        public Player() {
        }

        public Player(int level, String nickname) {
            this.level = level;
            this.nickname = nickname;
        }

        @Override
        public String toString() {
            return level + &quot; &quot; + nickname;
        }
    }

    static class Room {
        int baseLevel;
        List&lt;Player&gt; players = new ArrayList&lt;&gt;();

        public Room() {
        }

        // 추가 가능 여부
        boolean canJoin(Player p, int m) {
            return players.size() &lt; m &amp;&amp;
                    p.level &gt;= baseLevel - 10 &amp;&amp;
                    p.level &lt;= baseLevel + 10;
        }
    }</code></pre>
<p>이렇게 Player, Room 클래스가 있다고 하자. </p>
<p>여기서 player의 nickname을 오름차순 정렬하고 싶다면</p>
<pre><code class="language-java">room.players.sort((o1, o2) -&gt; o1.nickname.compareTo(o2.nickname));
</code></pre>
<p>이런식으로 람다식을 사용해주면 된다. 또 만약 내림차순 정렬을 원하면</p>
<pre><code class="language-java">room.players.sort((o1, o2) -&gt; o2.nickname.compareTo(o1.nickname));
</code></pre>
<p>해주면 된다. </p>
<p>근데 만약 level을 기준으로 즉 정수를 기준으로 오름차순 정렬하고 싶다면</p>
<pre><code class="language-java">room.players.sort((o1, o2) -&gt; Integer.level.compare(o1.level, o2.level));
</code></pre>
<p>내림차순 정렬하고 싶다면</p>
<pre><code class="language-java">room.players.sort((o1, o2) -&gt; Integer.level.compare(o2.level, o1.level));
</code></pre>
<p>이런식으로 해주면된다!! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] ArrayList vs HashSet]]></title>
            <link>https://velog.io/@chae_yu/TIL-ArrayList-vs-HashSet</link>
            <guid>https://velog.io/@chae_yu/TIL-ArrayList-vs-HashSet</guid>
            <pubDate>Thu, 18 Sep 2025 06:54:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>기록을 남기지 않으면 내 기억 속에서 다 사라지는걸 너무나도 느껴서!!!.. 틈틈히 공부한 것 중에서 알게된걸 기록으로 남겨보겠다.</p>
</blockquote>
<p>최근에 파이썬에서 자바로 코테 언어를 바꿔서 공부하고 있는데 모르는 메소드랑 개념들이 꽤 있어서 정리를 해야할거같다</p>
<p>백준 22233 문제를 푸는데 시간초과가 떴다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/7e475408-03a5-471e-b386-50b3240bbcb8/image.png" alt=""></p>
<h4 id="🙅🏻♀️--시간초과-코드">🙅🏻‍♀️  시간초과 코드</h4>
<pre><code class="language-java">package 구현;

import java.util.*;
import java.io.*;

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

        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken()); // 메모장에 적은 키워드 수
        int M = Integer.parseInt(st.nextToken()); // 블로그에 쓴 글

        List&lt;String&gt; keywords = new ArrayList&lt;&gt;(); // 메모장에 적은 키워드
        List&lt;String&gt; relateds = new ArrayList&lt;&gt;(); // 관련된 키워드

        for (int i = 0; i &lt; N; i++) {
            keywords.add(br.readLine());
        }

        for (int i = 0; i &lt; M; i++) {
            st = new StringTokenizer(br.readLine(), &quot;,&quot;);
            while (st.hasMoreTokens()) {
                String relate = st.nextToken();
                relateds.add(relate);
            }

            List&lt;String&gt; toRemove = new ArrayList&lt;&gt;();
            for (String keyword : keywords) {
                if (relateds.contains(keyword)) {
                    toRemove.add(keyword);
                }
            }

            keywords.removeAll(toRemove);
            System.out.println(keywords.size());
        }
    }

}
</code></pre>
<h4 id="🙆🏻♀️-정답-코드">🙆🏻‍♀️ 정답 코드</h4>
<pre><code class="language-java">package 구현;

import java.util.*;
import java.io.*;

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

        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken()); // 메모장에 적은 키워드 수
        int M = Integer.parseInt(st.nextToken()); // 블로그에 쓴 글

        Set&lt;String&gt; keywords = new HashSet&lt;&gt;(); // 메모장에 적은 키워드

        for (int i = 0; i &lt; N; i++) {
            keywords.add(br.readLine());
        }

        for (int i = 0; i &lt; M; i++) {
            Set&lt;String&gt; relateds = new HashSet&lt;&gt;(); // 관련된 키워드

            // 쉼표로 구분 받아서 저장
            st = new StringTokenizer(br.readLine(), &quot;,&quot;);

            while (st.hasMoreTokens()) {
                String relate = st.nextToken();
                relateds.add(relate);
            }

            keywords.removeAll(relateds);

            System.out.println(keywords.size());
        }
    }

}
</code></pre>
<h3 id="why">Why?</h3>
<p>왜 시간 초과가 떴을까 보니 ArrayList는 선형 탐색으로 검색 속도가 O(N)이고 HashSet은 O(1)로 월등히 빨랐다. 이 문제의 경우 입력 수가 2×10^5 까지로 컸기 때문에 시간 초과가 난 것 같다.</p>
<p>만약 키워드가 100,000개 있는데 관련 키워드를 3개 삭제한다고 치자.
그럼 ArrayList는 O(N)의 시간 복잡도로 3x100,000 = 30만번이 걸리고 HashSet은 O(1)로 3번으로 찾을 수 있다. </p>
<p>추가로, Hashset끼리의 연산은 O(1)으로 매우 빠르다. 이 경우 hashSet 끼리 저장 여부를 비교해서 삭제하는 것이기 때문에 시간 측면에서 매우 유리하다.</p>
<h4 id="arraylist-vs-hashset">ArrayList vs HashSet</h4>
<table>
<thead>
<tr>
<th align="center">항목</th>
<th align="center">ArrayList</th>
<th align="center">HashSet</th>
</tr>
</thead>
<tbody><tr>
<td align="center">저장 구조</td>
<td align="center">순차적인 리스트 구조</td>
<td align="center">중복 없는 집합 구조</td>
</tr>
<tr>
<td align="center">순서</td>
<td align="center">삽입 순서 유지</td>
<td align="center">순서 유지 안됨 (필요하면 LinkedHashSet)</td>
</tr>
<tr>
<td align="center">중복 허용 여부</td>
<td align="center">허용</td>
<td align="center">불가</td>
</tr>
<tr>
<td align="center">검색 속도</td>
<td align="center">끝에 추가하는 경우: O(N) / 중간이나 앞에 삽입하는 경우: O(N)</td>
<td align="center">O(1)</td>
</tr>
<tr>
<td align="center">삽입 속도</td>
<td align="center">O(1)</td>
<td align="center">O(1)</td>
</tr>
<tr>
<td align="center">삭제 속도</td>
<td align="center">O(1)</td>
<td align="center">O(1)</td>
</tr>
<tr>
<td align="center">정렬</td>
<td align="center">Collections.sort() 로 정렬 가능</td>
<td align="center">자체 정렬 기능 x (정렬하려면 List로 변경해야함)</td>
</tr>
</tbody></table>
<h4 id="그럼-언제-어떤-걸-써야할까">그럼 언제 어떤 걸 써야할까?</h4>
<ul>
<li><p><strong>중복이 허용</strong>되고 <strong>순서</strong>가 중요하다면 ArrayList</p>
</li>
<li><p>중복 제거가 필요하고 순서가 상관 없다면 HashSet</p>
</li>
<li><p>정렬이 필요하거나 인덱스로 접근해야한다면 ArrayList
그래서 만약에 HashSet으로 인덱스 접근해야하면 List로 변경해야함</p>
<pre><code>List&lt;String&gt; list = new ArrayList&lt;&gt;(set);
System.out.println(list.get(0));</code></pre></li>
<li><p>빠른 검색과 삭제가 필요할 때 (입력 값이 클 때) HashSet</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Swagger] Swagger로 API 명세서 자동화하기]]></title>
            <link>https://velog.io/@chae_yu/Swagger-Swagger%EB%A1%9C-API-%EB%AA%85%EC%84%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@chae_yu/Swagger-Swagger%EB%A1%9C-API-%EB%AA%85%EC%84%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 11 Jul 2025 12:00:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/chae_yu/post/bc69163e-2be9-4818-b223-2008b8fafda2/image.png" alt=""></p>
<p>껄껄쓴.. 오늘은 스웨거로 api 명세서를 문서화하는 방법에 대해 정리해보겠습니다.!!!!
기본적으로 나는 postman을 별로 선호하지 않기에..,.. 스웨거를 늘 활용하는데 지난 학기에 백엔드가 아닌 프론트로 팀플에 참여해보게 된 경험 후로 느낀 것이 참 많다...
api 명세서와 같이 문서화가 정말정말 중요하고 프론트분들에게 폐를 끼치지 않기 위해 노력하고 노력해야겠다는 것을 배웠다. 그래서 스웨거로 api 명세서를 평소에도 활용하고 있었지만 에러 응답과 같이 세부적인 것도 보여줄 수는 없을까?? 하는 생각이 들었다. </p>
<p>그때!! 몇 달 전에 선배가 자기가 정리했다면서 보여준게 생각나서 나도 에러 코드도 스웨거에서 확인할 수 있도록 추가해보기로 결심하였다.</p>
<hr>
<h1 id="swagger-적용하기">Swagger 적용하기</h1>
<h3 id="기본-세팅">기본 세팅</h3>
<p>우선 필요한 dependency를 적용해준다</p>
<pre><code>    implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0&#39;
</code></pre><p>참고로 낮은 버전을 사용하면 springdoc-openapi 라이브러리와 spring-web의 버전 충돌이 일어나서 <code>jakarta.servlet.ServletException: Handler dispatch failed: java.lang.NoSuchMethodError</code> 가 발생한다. 꼭 확인하고 최근 버전으로 해주자!!</p>
<p>그리고 SwaggerConfig를 설정한다.</p>
<pre><code>@OpenAPIDefinition(
        info = @Info(
                title = &quot;API 명세서&quot;,
                description = &quot;BlockGuard API 명세서&quot;,
                version = &quot;v2&quot;
        )
)
@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI openAPI() {

        Server prodServer = new Server();
        prodServer.setUrl(&quot;https://www.blockguard.shop&quot;);
        prodServer.setDescription(&quot;AWS EC2 서버&quot;);

        Server localServer = new Server();
        localServer.setUrl(&quot;http://localhost:8080&quot;);
        localServer.setDescription(&quot;Local server for testing&quot;);

        return new OpenAPI()
                .components(new Components()
                .addSecurityItem(securityRequirement)             .servers(List.of(localServer, prodServer));
    }</code></pre><ul>
<li><code>@OpenAPIDefinition</code> : 전체 API 문서의 이름, 설명, 버전 등을 정의</li>
<li><code>public OpenAPI openAPI()</code> : Swagger 문서의 서버 정보 및 JWT 인증 헤더 정보 등을 설정해준다.
여기서는 EC2 서버와 Localhost 서버를 등록해주었다. </li>
</ul>
<p>이렇게만 해줘도 기본 Swagger를 사용할 준비가 끝난다. 하지만 여기서 더 자세한 문서화를 위해 몇 가지를 추가해주도록 하겠다!!</p>
<h3 id="jwt-헤더-추가">JWT 헤더 추가</h3>
<p>JWT 토큰을 이용해서 회원가입, 로그인 로직을 사용하는 경우가 많을 것이다. 이때 헤더에 jwt 토큰을 포함하는 경우가 많은데, 스웨거에서 api 테스트를 할 때
<img src="https://velog.velcdn.com/images/chae_yu/post/9003bdb0-a13c-4793-a5d0-43a73a8a2540/image.png" alt=""></p>
<p>사진에 보이는 &#39;Authorize&#39; 버튼을 누르면
<img src="https://velog.velcdn.com/images/chae_yu/post/44a60848-2315-4221-a4e3-529b19f7848d/image.png" alt="">
이런 식으로 로그인 시 발급받았던 토큰을 입력할 수 있다! 그러면 jwt 토큰이 필요한 api도 편하게 테스트 가능~</p>
<pre><code>    @Bean
    public OpenAPI openAPI() {

        SecurityScheme apiKey = new SecurityScheme()
                .type(SecurityScheme.Type.HTTP)
                .in(SecurityScheme.In.HEADER)
                .name(&quot;Authorization&quot;)
                .scheme(&quot;bearer&quot;)
                .bearerFormat(&quot;JWT&quot;);

        SecurityRequirement securityRequirement = new SecurityRequirement()
                .addList(&quot;Bearer Token&quot;);

        Server prodServer = new Server();
        prodServer.setUrl(&quot;https://www.blockguard.shop&quot;);
        prodServer.setDescription(&quot;AWS EC2 서버&quot;);

        Server localServer = new Server();
        localServer.setUrl(&quot;http://localhost:8080&quot;);
        localServer.setDescription(&quot;Local server for testing&quot;);

        return new OpenAPI()
                .components(new Components().addSecuritySchemes(&quot;Bearer Token&quot;, apiKey))
                .addSecurityItem(securityRequirement)
                .servers(List.of(localServer, prodServer));
    }</code></pre><p>아까 코드에서 SecuritySchema 부분을 추가해주면 간단하게 설정 가능하다. </p>
<h3 id="에러-코드-추가">에러 코드 추가</h3>
<p>여기까지는 프로젝트에서 늘 사용하던 부분이다. 이제는 더 나아가보자~
<img src="https://velog.velcdn.com/images/chae_yu/post/6a7bda35-2b52-46a4-b2e2-411749af7412/image.png" alt="">
이런식으로 성공 시 예시 응답뿐만 아니라 관련 에러 응답까지 표시되도록 하는 코드를 추가하는 방법을 작성해보자!</p>
<p>우선 ErrorCode와 같이 예외 시 발생하는 에러 코드들을 관리하는 클래스가 있을 것이다.!
나 같은 경우</p>
<pre><code>@Getter
@AllArgsConstructor
public enum ErrorCode {
    /// 4000 ~ : client error
    DUPLICATED_EMAIL(HttpStatus.BAD_REQUEST, 4001, &quot;이미 가입된 이메일입니다.&quot;),
    INVALID_EMAIL(HttpStatus.BAD_REQUEST, 4002, &quot;존재하지 않는 이메일입니다.&quot;),
    INVALID_PASSWORD(HttpStatus.BAD_REQUEST, 4003, &quot;비밀번호가 일치하지 않습니다.&quot;),
    INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 4004, &quot;유효하지 않은 토큰입니다.&quot;),

    // 5000~ : server error
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 5000, &quot;서버 오류가 발생했습니다.&quot;);

    private final HttpStatus status;
    private final int code;
    private final String msg;

}
</code></pre><p>이런식으로 에러 코드를 관리하고 있는데 현재 위에 코드는 로그인 관련 에러, 회원가입 관련 에러 그리고 공통 에러가 존재한다.</p>
<p>이 에러들을 적절한 API에 맞춰서 스웨거에 표시하기 위해</p>
<pre><code>package com.blockguard.server.global.config.swagger;

import com.blockguard.server.global.common.codes.ErrorCode;
import lombok.Getter;

import java.util.LinkedHashSet;
import java.util.Set;

@Getter
public enum SwaggerResponseDescription {
    REGISTER_FAIL(new LinkedHashSet&lt;&gt;(Set.of(
            ErrorCode.DUPLICATED_EMAIL
    ))),

    LOGIN_FAIL(new LinkedHashSet&lt;&gt;(Set.of(
            ErrorCode.INVALID_EMAIL,
            ErrorCode.INVALID_PASSWORD
    )));

    private final Set&lt;ErrorCode&gt; errorCodeList;

    SwaggerResponseDescription(Set&lt;ErrorCode&gt; errorCodes) {
        // 공통 에러 추가
        errorCodes.addAll(Set.of(
                ErrorCode.INVALID_TOKEN,
                ErrorCode.INTERNAL_SERVER_ERROR
        ));

        this.errorCodeList = errorCodes;
    }
}
</code></pre><p>위와 같이 <code>SwaggerResponseDescription</code> 라는 클래스에 회원가입 관련 에러 해시셋, 로그인 관련 해시셋으로 묶어주었다. 
즉, 각 api기능 별로 관련된 ErrorCode 집합을 정의한다.</p>
<p>그리고 API 명세 문서에 요청이나 응답 예시 데이터를 명시할 때 사용하는 Example 객체를 사용해서 <code>ExampleHolder</code>를 만들어준다.</p>
<pre><code>@Getter
@Builder
public class ExampleHolder {
    private Example holder;
    private String name;
    private int code;
}</code></pre><p>위와 같이 사용하게 되면 스웨거 문서에 이름과 응답코드가 뜨게된다. 예시를 담는 DTO 라고 생각하면된다.</p>
<p>그리고 메서드별로 스웨거 예외 응답 예시를 주석처럼 지정해주기 위해서</p>
<p><code>CustomExceptionDescription</code> 클래스를 만들어준다.</p>
<pre><code>@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomExceptionDescription {
    SwaggerResponseDescription value();
}</code></pre><p>api에</p>
<pre><code>    @Operation(summary = &quot;회원가입&quot;)
    @CustomExceptionDescription(SwaggerResponseDescription.REGISTER_FAIL)
    @PostMapping(&quot;/register&quot;)
    public ResponseEntity&lt;BaseResponse&lt;RegisterResponse&gt;&gt; register(@RequestBody RegisterRequest registerRequest) {
...

    }</code></pre><p>이런식으로 어노테이션이 달려있다.</p>
<p>그러면 최종으로 <code>SwaggerConfig</code> 를 아래와 같이 완성해주면된다.</p>
<pre><code>@OpenAPIDefinition(
        info = @Info(
                title = &quot;API 명세서&quot;,
                description = &quot;BlockGuard API 명세서&quot;,
                version = &quot;v2&quot;
        )
)
@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI openAPI() {

        SecurityScheme apiKey = new SecurityScheme()
                .type(SecurityScheme.Type.HTTP)
                .in(SecurityScheme.In.HEADER)
                .name(&quot;Authorization&quot;)
                .scheme(&quot;bearer&quot;)
                .bearerFormat(&quot;JWT&quot;);

        SecurityRequirement securityRequirement = new SecurityRequirement()
                .addList(&quot;Bearer Token&quot;);

        Server prodServer = new Server();
        prodServer.setUrl(&quot;https://www.blockguard.shop&quot;);
        prodServer.setDescription(&quot;AWS EC2 서버&quot;);

        Server localServer = new Server();
        localServer.setUrl(&quot;http://localhost:8080&quot;);
        localServer.setDescription(&quot;Local server for testing&quot;);

        return new OpenAPI()
                .components(new Components().addSecuritySchemes(&quot;Bearer Token&quot;, apiKey))
                .addSecurityItem(securityRequirement)
                .servers(List.of(localServer, prodServer));
    }

    @Bean
    public OperationCustomizer customize() {
        return (operation, handlerMethod) -&gt; {
            CustomExceptionDescription customExceptionDescription = handlerMethod.
                    getMethodAnnotation(CustomExceptionDescription.class);

            if (customExceptionDescription != null) {
                generateErrorCodeResponseExample(operation, customExceptionDescription.value());
            }
            return operation;
        };
    }

    // SwaggerResponseDescription에서 ErrorCode에 대해 ExampleHodler 생성
    private void generateErrorCodeResponseExample(Operation operation, SwaggerResponseDescription type) {
        ApiResponses responses = operation.getResponses();

        Set&lt;ErrorCode&gt; errorCodeList = type.getErrorCodeList();

        Map&lt;Integer, List&lt;ExampleHolder&gt;&gt; statusWithExampleHolders =
                errorCodeList.stream()
                        .map(
                                errorCode -&gt; {
                                    return ExampleHolder.builder()
                                            .holder(
                                                    getSwaggerExample(errorCode))
                                            .code(errorCode.getCode())
                                            .name(errorCode.toString())
                                            .build();
                                }
                        ).collect(groupingBy(ExampleHolder::getCode));
        addExamplesToResponses(responses, statusWithExampleHolders);
    }


    // 주어진 ErrorCode로부터 Swagger에 넣을 예시 객체 생성
    private Example getSwaggerExample(ErrorCode errorCode) {
        ErrorResponse errorResponse = ErrorResponse.of(errorCode);
        Example example = new Example();
        example.description(errorCode.getMsg());
        example.setValue(errorResponse);
        return example;
    }

    // 상태코드에 대해 Swagger의 ApiResponse 생성 &amp; application/json 타입에 여라 개의 예시 추가
    private void addExamplesToResponses(ApiResponses responses, Map&lt;Integer, List&lt;ExampleHolder&gt;&gt; statusWithExampleHolders) {
        statusWithExampleHolders.forEach((status, holders) -&gt; {
            Content content = new Content();
            MediaType mediaType = new MediaType();
            ApiResponse apiResponse = new ApiResponse();

            holders.forEach(holder -&gt; mediaType.addExamples(holder.getName(), holder.getHolder()));
            content.addMediaType(&quot;application/json&quot;, mediaType);
            apiResponse.setContent(content);
            responses.addApiResponse(status.toString(), apiResponse);
        });
    }
</code></pre><ul>
<li><p><code>customize()</code>: API 메서드에 <code>@CustomExceptionDescription</code> 어노테이션을 감지하여 예외 응답 예시를 스웨거 문서에 동적으로 추가</p>
</li>
<li><p><code>generateErrorCodeResponseExample()</code>: SwaggerResponseDescription에 등록된 ErrorCode들을 기반으로 예시를 만들고 operation 객체에 상태코드별로 응답 추가</p>
</li>
<li><p><code>getSwaggerExample()</code>: 하나의 ErrorCode를 Swagger에 넣을 수 있는 Example 객체로 변환하여 Json 형태로 표시해줌</p>
</li>
<li><p><code>addExamplesToResponses()</code>: 같은 상태코드에 여러 개의 예시를 ApiResponse로 묶어서 스웨거 문서에 추가</p>
</li>
</ul>
<p>즉, SpringDoc이 API 명세를 스캔할 때, OperationCustomizer가 작동 -&gt; customize()에서 메서드에 붙은 <code>@CustomExceptionDescription</code> 어노테이션을 감지 -&gt; <code>generateErrorCodeResponseExample()</code>에서 등록된 에러 코드를 기준으로 예시 목록 생성 -&gt; <code>getSwaggerExample()</code>에서 각 상태 코드 별로 Example 객체 생성 -&gt; 최종적으로 <code>addExamplesToResponses()</code>이 <code>operation.getResponse()</code>에 예시 추가</p>
<p>스웨거 자동화 관련 코드들은 !
<img src="https://velog.velcdn.com/images/chae_yu/post/1d6ef0f9-3795-46f3-8b61-2d3037068289/image.png" alt="">이런 식으로 구성되어있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] Docker로 github CI/CD 배포하기]]></title>
            <link>https://velog.io/@chae_yu/AWS-Docker%EB%A1%9C-github-CICD-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@chae_yu/AWS-Docker%EB%A1%9C-github-CICD-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 03 Jul 2025 08:02:22 GMT</pubDate>
            <description><![CDATA[<p>이제 docker로 CI/CD를 설정해줄 것이다.
그전에!! 해야하는 것들을 해줘야한다. </p>
<h3 id="swap-설정">Swap 설정</h3>
<p>우선 스왑 메모리를 설정해준다. </p>
<pre><code>sudo fallocate -l 2G /swapfile # 2GB 크기의 swap 파일 생성
sudo chmod 600 /swapfile # 권한 설정 (소유자만 읽기/쓰기)
sudo mkswap /swapfile # swap 형식으로 포맷
sudo swapon /swapfile # swap 활성화</code></pre><h3 id="javamysql설치">JAVA/Mysql설치</h3>
<p>자바랑 mysql을 사용해줄것이니까 필요한 것들을 ec2내에 설치한다. </p>
<pre><code>sudo apt install mysql-server
mysql -u {마스터 사용자 이름} -p -h {엔드포인트}
</code></pre><pre><code>sudo apt install openjdk-21-jdk-y
echo &quot;export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))&quot; &gt;&gt; ~/.bashrc
echo &quot;export PATH=\$JAVA_HOME/bin:\$PATH&quot; &gt;&gt; ~/.bashrc
source ~/.bashrc</code></pre><p>로 자바 설치 및 자바 홈 경로까지 설정해준다. Gradle은 21로 빌드하기 때문에 21로 해야 나중에 오류 안 남!!</p>
<h3 id="docker-설치">Docker 설치</h3>
<p>Ubuntu에서는 Docker를 설치하기 위해 공식 Docker 저장소를 먼저 추가해야하기 때문에 아래 명령어를 통해 도커 저장소를 설치해준다.</p>
<pre><code>sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable&quot;</code></pre><pre><code>sudo apt install docker -y
</code></pre><p>로 도커를 설치해준다. </p>
<h2 id="docker로-배포">Docker로 배포</h2>
<p>이제 설치해야하는 것을 다 설치해줬으면!! 본격적으로 CI/CD를 위해 필요한 것들을 작성해준다.</p>
<h3 id="dockerfile-작성">Dockerfile 작성</h3>
<p>Dockerfile로</p>
<pre><code>FROM openjdk:21-jdk-slim
EXPOSE 8080
COPY ./build/libs/*.jar ./app.jar
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre><p>작성해주는데 자바 버전같은거랑 다른 부분은 맞춰서 수정해야한다.</p>
<h3 id="deployyml-작성">deploy.yml 작성</h3>
<pre><code>name: CI/CD for Spring Boot with Docker

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: 코드 체크아웃
        uses: actions/checkout@v3

      - name: JDK 설정
        uses: actions/setup-java@v3
        with:
          distribution: &#39;temurin&#39;
          java-version: &#39;21&#39;

      - name: application.yml 설정
        run: |
          mkdir -p ./src/main/resources
          echo &quot;${{ secrets.APPLICATION }}&quot; &gt; ./src/main/resources/application.yml

      - name: Gradle 빌드
        run: ./gradlew clean build -x test

      - name: Docker 이미지 빌드 및 푸시
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/blockguard_server .
          echo &quot;${{ secrets.DOCKER_PASSWORD }}&quot; | docker login -u &quot;${{ secrets.DOCKER_USERNAME }}&quot; --password-stdin
          docker push ${{ secrets.DOCKER_USERNAME }}/blockguard_server:latest

  deploy:
    runs-on: ubuntu-latest
    needs: build

    steps:
      - name: EC2에 SSH 접속하여 Docker 컨테이너 재배포
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          script: |
            docker pull ${{ secrets.DOCKER_USERNAME }}/blockguard_server:latest
            docker stop blockguard_server || true
            docker rm blockguard_server || true
            docker run -d -p 8080:8080 --name blockguard_server ${{ secrets.DOCKER_USERNAME }}/blockguard_server:latest</code></pre><p>github에 workflows/.deploy.yml 파일을 올려준다.
여기서 <code>APPLICATION</code>, <code>DOCKER_USERNAME</code>, <code>DOCKER_PASSWORD</code>, <code>EC2_HOST</code>, <code>EC2_PRIVATE_KEY</code> 는 github setting에 Secretes로 등록해준다.</p>
<p><code>APPLICATION</code>: application.yml 파일 전체
<code>DOCKER_USERNAME</code>: docker id
<code>DOCKER_PASSWORD</code>: docker password
<code>EC2_HOST</code>: EC2 퍼블릭 IP
<code>EC2_PRIVATE_KEY</code>: .pem 내용 복사 &lt;- <code>cat {.pem 이름}.pem</code> 로 내용 확인 가능 (---BEGIN 부터 END-- 까지 전체 내용 복사) </p>
<h3 id="ec2에서-확인">EC2에서 확인</h3>
<p>github repository에 있는 프로젝트를 클론해서 EC2에 받아온다.</p>
<pre><code>git clone {repository 주소}
</code></pre><p>그리고 application.yml 파일을 작성해준다. application.yml 파일은 절대! 깃허브에 안 올리기 때문에 따로 작성해줘야한다.</p>
<pre><code>mkdir -p src/main/resources
nano src/main/resources/application.yml
vi src/main/resources/application.yml
</code></pre><p>다 작성했으면 github action 돌리기 전에 수동으로 실행해보고 확인해본다!</p>
<pre><code>./gradlew clean build -x test
docker build -t blockguard_server .
docker run -d -p 8080:8080 --name blockguard_server blockguard_server
docker ps</code></pre><p>정상 작동 확인하면 이후에 Github actions 실행하면 됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] NignX & certbot]]></title>
            <link>https://velog.io/@chae_yu/AWS-NignX-certbot</link>
            <guid>https://velog.io/@chae_yu/AWS-NignX-certbot</guid>
            <pubDate>Thu, 03 Jul 2025 07:33:36 GMT</pubDate>
            <description><![CDATA[<p>이건 나중에 프론트랑 연결할 때를 위해 설정해두는 것!!
EC2는 기본으로 HTTP로 연결되는데 프론트에서는 HTTPS로 작동해서 백엔드 쪽에 HTTPS를 적용시켜보겠다.</p>
<h3 id="nginx와-certbot">NginX와 Certbot</h3>
<p>Nginx는 웹 서버 소프트웨어로 리버시 프록시 기능을 가지고 있다. 그리고 Certbot은 무료로 HTTPS 인증서를 발급해준다.
프론트에서 요청이 들어오면 NginX 서버가 해당 요청을 스프링부트로 전달한다. NginX는 certbot로 HTTPS가 적용되어있기 때문에 정상적으로 다른 도메인을 사용하는 프론트와 백엔드에서 세션 쿠키가 사용이 된다. </p>
<h3 id="ec2에-nginx에-설치">EC2에 NginX에 설치</h3>
<pre><code>sudo apt update
sudo apt install nginx</code></pre><pre><code>sudo service nginx status</code></pre><p>로 잘 설치되었는지 확인하고 EC2의 IP를 브라우저에 입력해보면 NignX 서버가 실행되고 있는것을 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/b4e2ec46-c9b9-4a17-a55f-2943ea7d30e3/image.png" alt=""></p>
<h3 id="certbot-설치해서-nginx에-https-적용">Certbot 설치해서 NginX에 HTTPS 적용</h3>
<pre><code>sudo snap install --classic certbot</code></pre><p>위 명령어로 설치해주고 SSL 인증서를 발급받아야하는데, SSL 인증서를 발급받기 위해서는 도메인 주소가 필요하다. 
&#39;가비아&#39;나 &#39;내 도메인 한국&#39;과 같은 도메인 발급 서비스를 이용하여 발급한다. 
나는 가비아를 사용했는데, 
<img src="https://velog.velcdn.com/images/chae_yu/post/87cacab7-1780-434e-b56e-67d628f26cf9/image.png" alt="">
원하는 도메인 구입 후에 도메인 설정에 들어간다. 사진과 같이 설정하면 되는데 값/위치에는 EC2의 IP주소를 기입한다. 
근데 도메인 구입 후에도 그렇고 설정 후에도 그렇고 시간이 좀 걸리니까 기다려야한다!!</p>
<h3 id="인증서-발급">인증서 발급</h3>
<pre><code>sudo certbot --nginx -d {도메인}
</code></pre><p>위 명령어를 입력해서 이메일 인증까지 하면 설정이 완료된다. 도메인에는 www.<del>.</del> 입력해주면된다.</p>
<h3 id="nginx-설정">NginX 설정</h3>
<pre><code>sudo vi /etc/nginx/sites-available/default
</code></pre><p>위 경로로 이동해서 default 파일을 수정해줘야한다. certbot 파일은 자동으로 https 관련 설정을 해주는데 리버스 프록시의 역할 등 추가적인 설정을 해야한다.
내리다보면 내가 발급받은 도메인이 적혀있는 부분이랑 <code>try_files $uri $uri/ =404;</code> 부분이 나오는데 해당 부분을 주석 처리하고 <code>proxy_pass &lt;서버IP&gt;:&lt;스프링부트 포트번호&gt;$request_uri;</code>를 적어준다. 
이건 NginX 서버로 들어오는 모든 요청을 스프링부트 서버로 연결해준다는 뜻이다. </p>
<pre><code>sudo nginx -t</code></pre><p>위 명령어로 문법에 오류 있는지 확인하고 성공했다면 아래 명령어를 통해 nginx 재시작을 해주면 된다.</p>
<pre><code>sudo systemctl reload nginx</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] EC2 & RDS 연결]]></title>
            <link>https://velog.io/@chae_yu/AWS-EC2-RDS-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@chae_yu/AWS-EC2-RDS-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Thu, 03 Jul 2025 07:18:29 GMT</pubDate>
            <description><![CDATA[<p>이제는 EC2와 RDS를 연결해보겠다!! 사실 데이터베이스를 연결하는 방식은 굉장히 다양한데 나는 이 사진과 같이 rds는 private subnet으로 ec2 내부에서만 접근할 수 있게 해볼 것이다.
<del>다른 방법은 나중에 해봐야지렁이</del>
<img src="https://velog.velcdn.com/images/chae_yu/post/2f12fac5-4514-4f2d-b7f5-38fd36c41825/image.png" alt=""></p>
<p>RDS는 서브넷 그룹을 할당해야하기 때문에 서브넷 그룹에 다른 가용영역에 존재하는 서브넷 두 개를 생성해서 할당할 것이다. 
서로 다른 가용 영역을 두는 것을 멀티 AZ라고 하는데 프리티어는 이렇게 할 수가 없움... </p>
<p>그래서 실제로는 단일 인스턴스에 하나의 가용 공간을 이용해서 해보겠따.</p>
<h3 id="1-서브넷-생성">1. 서브넷 생성</h3>
<p>서브넷 두 개를 생성해준다. 이전에 만들어둔 VPC를 선택해서 하나는 가용영역 &quot;ap-northeast-2a&quot; 로 10.0.2.0/24 의 CIDR 블록으로 설정해준다.
<img src="https://velog.velcdn.com/images/chae_yu/post/0b2b7972-3196-467f-b275-7222a0caf03a/image.png" alt=""></p>
<p>또 다른 하나는 가용영역 &quot;ap-northeast-2b&quot; 로 10.0.3.0/24 의 CIDR 블록으로 설정해준다.
<img src="https://velog.velcdn.com/images/chae_yu/post/72bbb02e-416d-4870-be0c-9a7f9ab0e46e/image.png" alt=""></p>
<h3 id="2-서브넷-그룹-생성">2. 서브넷 그룹 생성</h3>
<p>RDS 설정 부분으로 넘어와서 DB 서브넷 그룹 생성을 눌러준다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/6d108bd8-21fd-48cb-b812-98642d53db0b/image.png" alt="">
가용영역은 앞서 만든 서브넷이 속한 곳을 선택해주고 서브넷도 선택한다. </p>
<h3 id="3-보안-그룹-생성">3. 보안 그룹 생성</h3>
<p>그리고 마찬가지로 보안그룹을 생성해줄건데 이건 private subnet으로 설정해줄 것이기 때문에 인터넷 게이트 웨이 연결은 해주지 않을 것이다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/90a953f1-65e3-4863-be34-087d09d56d78/image.png" alt=""></p>
<p>만들어둔 VPC를 선택해주고 인바운드 규칙은 MYSQL/Aurora를 선택해준다. 소스는 EC2 인스턴스가 존재하는 보안그룹을 선택해준다. (아까 만들어둔 public 보안그룹!!)</p>
<h3 id="4-데이터베이스-생성">4. 데이터베이스 생성</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/3f9143db-4117-46a6-a0ec-83f7a3c73c1b/image.png" alt="">
MYSQL을 사용해줄거라 해당 엔진을 선택해주고 프리티어로 선택한다. 
여기서 설정해주는 마스터 사용자 이름과 마스터 암호는 나중에 연결해줄 때 필요하니까 설정하고 잘 기록해둬야한다. </p>
<p>연결에서는 EC2 컴퓨팅 리소스에 연결 안함을 설정해주고 VPC를 잘 선택한다. 만들어놓은 DB 서브넷 그룹을 선택해주고~</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/6250d83f-1ebd-4d82-97f2-0b50859ee30b/image.png" alt="">
<del>EC2 컴퓨팅 리소스에 연결하면 편리하게 할 수 있다고 하는데 나는 아직 안 해봐서.. 나중에 해보게따</del></p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/85fea1e6-05a0-4a52-8007-e02f2174f9b7/image.png" alt="">
VPC 보안 그룹도 private 보안 그룹 만들어놓은거 선택해주기! 그리고 가용영역도! (프리티어는 멀티 AZ를 사용못해서 선택해줘야함 ㅠ ㅠ)</p>
<h3 id="5-데이터베이스-연결">5. 데이터베이스 연결</h3>
<p>자 그럼~~ 생성이 다 되었으면 연결 잘 되었는지 보면된다!
<code>mysql -u [마스터 사용자 이름] -p -h [데이터베이스 엔드포인트]</code> 입력하면 패스워드 입력하라고 할건데 잘 입력하면 접속이 될 것이다!</p>
<p>근데 매번 이렇게 보는건 불편하니까 MYSQL workbench로 연결해두면 편한데,
connection method를 Standard TCP/IP over SSH로 선택한다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/fcaf3b77-1b5a-4a4a-ad9f-16e19b726cb8/image.png" alt="">
그리고 사진에 있는 것처럼 각각의 항목을 채워주면된다. 연결 잘 되면 성공!</p>
<p>참고 : <a href="https://growth-coder.tistory.com/170">https://growth-coder.tistory.com/170</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] EC2 생성하기]]></title>
            <link>https://velog.io/@chae_yu/AWS-EC2-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@chae_yu/AWS-EC2-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 03 Jul 2025 06:58:15 GMT</pubDate>
            <description><![CDATA[<p>계속 할 때마다 요리저리 참고하면서 하는게 귀찮아서 정리해놓는다!!
EC2 생성부터 도커로 CI/CD 까지!!</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/5121e7b8-7432-4de0-82c4-4639e83fa0a9/image.png" alt="">
위와 같은 아키텍처로 만들어볼 것이다</p>
<h1 id="🌎-ec2-생성">🌎 EC2 생성</h1>
<p>EC2 생성하는거까지는 금방할 수 있을 것이다</p>
<h3 id="1-vpc-생성">1. VPC 생성</h3>
<p>우선 VPC를 생성한다.  ex) <code>10.0.0.0/16</code>
<img src="https://velog.velcdn.com/images/chae_yu/post/04568213-9bb5-43be-8cfd-7863e1ee2d5b/image.png" alt="">
vpc란 클라우드 환경에서 사용자가 마치 독립된 개인 네트워크처럼 사용할 수 있는 가상 네트워크 공간이라고 생각하면 된다.</p>
<h3 id="2-public-subnet-생성">2. public subnet 생성</h3>
<p>1번 단계에서 만들어놓은 vpc 를 선택해서 public subnet을 생성해준다.
가용영역은 ap-northeast-2a 로 선택해준다.
ex) <code>10.0.1.0/24</code>
<img src="https://velog.velcdn.com/images/chae_yu/post/a126350b-ccea-43fd-bbaf-a0c3c28664a1/image.png" alt="">
참고로, 해당 서브넷은 외부와 통신할 수 있도록 할 것이기 때문에 인터넷 게이트웨이와 연결해야한다. 만약 외부와 통신할 수 없도록 하려면 인터넷 게이트웨이와 연결 안 하면 된다!</p>
<h3 id="3-인터넷-게이트웨이-생성">3. 인터넷 게이트웨이 생성</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/6f2b2af6-fc8c-4278-837c-d8436bbab565/image.png" alt=""> 
이제 외부와 통신하기 위한 인터넷 게이트웨이를 연결해준다. 생성한 인터넷 게이트 웨이를 VPC와 연결해주면 된다. </p>
<h3 id="4-라우팅-테이블-생성">4. 라우팅 테이블 생성</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/6fb25b64-ddbb-418a-a03e-911383f0b336/image.png" alt=""> 라우팅 테이블을 생성해서 해당 인터넷 게이트 웨이를 통해 라우팅해줄 것이다. 
마찬가지로 만들어놓은 VPC를 선택하고 생성한다.</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/85663c1d-69af-45db-b957-8437d1f06485/image.png" alt="">
<img src="https://velog.velcdn.com/images/chae_yu/post/444289af-37d1-40bc-8c97-b014b967165e/image.png" alt="">
만든 후에는 라우팅 편집을 눌러 0.0.0.0/0과 만들었던 인터넷 게이트웨이를 추가하고 변경 사항을 저장한다. 이 의미는 10.0.0.0/16 CIDR 블록을 제외한 접근에 대해서는 인터넷 게이트웨이로 라우팅 한다는 뜻이다.</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/9a44a0e7-5e0f-4744-9987-4cbff08f446c/image.png" alt="">
그리고 서브넷 연결 편집을 눌러 만들어둔 서브넷을 연결해준다. </p>
<h3 id="5-보안-그룹-생성">5. 보안 그룹 생성</h3>
<p>EC2를 생성할 때 만들어도 되지만 미리 만들어놓고 들어가겠다. 나중에 private 보안 그룹도 만들거기 때문에 public 인 경우는 이름에 public이라고 명시하는게 편하다.</p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/5a21709b-3dda-497d-ac6b-a6a54bbb4627/image.png" alt="">
마찬가지로 만들어둔 VPC를 선택하고 인바운드, 아웃바운드 규칙을 설정하면 된다.
인바운드는 안으로 들어오는 트래픽에 관한 것이고 아웃바운드는 밖으로 나가는 트래픽에 관한 것인데 아웃바운드는 모든 트래픽에 대해 허용하면 된다.</p>
<p>인바운드 규칙은 HTTPS, HTTP, SSH를 기본으로 설명해주고, 나는 스프링부트를 사용할 것이기 때문에 스프링 부트 포트번호 8080 또한 설정해준다.
그리고 이후 nginx 설정도 할 것이기 때문에 ::/0 도 추가해준다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/146972c8-0d8e-4d1f-925d-e53eb284d8cb/image.png" alt=""></p>
<h3 id="6-ec2-인스턴스-생성">6. EC2 인스턴스 생성</h3>
<p>이제 EC2 인스턴스를 생성해준다!!
나는 프리티어를 사용할 것이기 때문에 적절한 인스턴스 이름으로 작성해주고 Ubuntu로 할 것이다. 
<img src="https://velog.velcdn.com/images/chae_yu/post/17810d6d-45a2-46c1-b90a-046f27c4474d/image.png" alt="">
t2.micro로 설정되어있는 것을 유지해주고
키페어는 RSA &amp; .pem 으로 생성해준 다음에 적절한 위치에 잘 이동시켜둔다.
<img src="https://velog.velcdn.com/images/chae_yu/post/0bdc28c5-a8b4-4229-8bbe-7c78ab684c33/image.png" alt="">
네트워크 설정은 만들어둔 VPC를 설정해주고, 서브넷도 만들어둔 public subnet을 선택한다. 그렇게 되면 만들어놓은 보안그룹이 나올 건데 해당 보안그룹으로 설정해준다.
<img src="https://velog.velcdn.com/images/chae_yu/post/6537a855-8338-433b-aac5-82c629d217e6/image.png" alt="">
마지막으로 스토리지 설정을 해주면 되는데 프리티어는 30GiB까지 지원해준다. 나중에 변경 가능하긴 한데 그냥 처음부터 해두는게 편하다.. CPU 넘치고 난리나면 EC2 접속이 안돼서 힘들어짐
<img src="https://velog.velcdn.com/images/chae_yu/post/39056b0a-4cd3-4249-aabb-4104fd162188/image.png" alt=""></p>
<p>이렇게 설정해주면 EC2 생성이 끝난다.</p>
<h3 id="7-탄련적-ip-연결">7. 탄련적 IP 연결</h3>
<p>그리고 고정된 IP 주소를 할당받기 위해 탄력적IP 주소를 할당해주면되는데, 만들고 나서 꼭꼭!! 탄력적 IP 주소 연결을 눌러서 만들어둔 EC2를 연결해준다. 
얘는 할당받아놓고 연결 안 하면 요금 나오니까 꼭~ 연결해주기</p>
<h3 id="8-ssh로-ec2-접근">8. SSH로 EC2 접근</h3>
<p>이제 터미널에서 아까 받은 .pem 이 저장되어있는 위치로 이동한 다음에 
<code>chmod 400 {.pem이름}.pem</code>으로 권한 설정을 해준다. 그 다음에</p>
<p><code>ssh -i &quot;{.pem이름}&quot; ubuntu@{IP주소}</code> 를 입력해주면 연결이 될 것이다!!
<img src="https://velog.velcdn.com/images/chae_yu/post/5d831119-fc3e-48c9-a0a5-dcde4c94c89e/image.png" alt=""></p>
<p><code>curl http://checkip.amazonaws.com</code> 을 입력해 IP 주소가 출력된다면 올바르게 연결된 것이다. 
아니면
<code>ping 15.165.194.164</code> 로 핑테스트해보기!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] DTO / DAO / Entity]]></title>
            <link>https://velog.io/@chae_yu/Spring-DTO-DAO-Entity</link>
            <guid>https://velog.io/@chae_yu/Spring-DTO-DAO-Entity</guid>
            <pubDate>Thu, 15 Aug 2024 12:47:37 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하며 도메인 별로 DTO, DAO, Entity를 구분하는데 정확히 개념을 짚고 넘어가야할 것 같아서 정리해본다.
특히 왜 이렇게 구분해서 사용해야하는지를 집중적으로 공부한 내용에 대해 적어보겠다.</p>
<hr>
<h2 id="💻-entity--dto--dao-란">💻 Entity / DTO / DAO 란?</h2>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/cfec5be9-3272-4f45-86f9-5445d1e8cb65/image.png" alt=""></p>
<h3 id="🌀-entity">🌀 Entity</h3>
<p>: 데이터베이스에 저장되는 데이터 객체로 데이터베이스에 직접적으로 연결된다. DB 테이블에 존재하는 컬럼들을 필드로 가지고 있다. </p>
<pre><code class="language-java">@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, nullable = false)
    private Long id;
    private String nickName;
    private String introduce;
    private String profileImg;</code></pre>
<p>예를 들어 User 라는 테이블에 id, nickName, introduce, profileImg라는 컬럼을 가지고 있다면 해당 Entity 클래스는 위와 같이 작성되게된다. 
JPA에서는 <code>@Entity</code> 어노테이션을 붙여주면 해당 클래스가 Entity임을 인지하게된다. </p>
<p>추가로 Entity 클래스에서는 Setter를 만드는 것을 지양하고 도메인 로직이 필요하다면 이를 포함한다. 또, 일반적으로 필요한 값만 사용할 수 있는 <code>@Builder</code> 패턴을 사용한다. </p>
<h3 id="🌀-dto-data-transfer-object">🌀 DTO (=Data Transfer Object)</h3>
<p>: 계층 간 데이터 교환 역할을 담당하여 Entity를 가지고 만드는 일종의 Wrapper 역할로 기능한다. Entity로 직접 데이터에 접근하지 않고 Dto를 통해서 접근하도록 한다. 
그렇기 때문에 Dto는 다른 특별한 로직 없이 이루어진 순수한 데이터 객체이다. </p>
<pre><code class="language-java">@Getter
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
@AllArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class UserUpdateRequest {
    private String nickName;
    private String introduce;
    private String profileImg;
}</code></pre>
<p>추가로 DTO 클래스에서 toEntity(), toDto() 같은 메서드로 Presentation 로직을 구현하여 사용하곤한다. </p>
<h3 id="🌀-dao-data-access-object">🌀 DAO (=Data Access Object)</h3>
<p>: 실제 데이터베이스에 접근하는 객체로 JPA에서 데이터베이스에 데이터를 &#39;CRUD&#39;하는 레포지토리 객체들이 Dao로써 기능한다. </p>
<hr>
<h2 id="💻-왜-entity와-dto를-구분해야할까">💻 왜 Entity와 DTO를 구분해야할까?</h2>
<p>우선 요청과 응답 처리를 담당하는 Controller(API)에서 데이터를 DTO의 형태로 받아서 Service(Application)에 전달한다. 그럼 Service에서 DTO를 Entity로 변환하고 로직을 수행한 후에 Repository로 전달한다. 이렇게 Entity를 받은 Repository가 데이터베이스와의 인터페이스 역할을 하여 결론적으로 데이터를 전달한다.</p>
<h3 id="🌀-entity를-직접-반환하게-되었을-때의-단점">🌀 Entity를 직접 반환하게 되었을 때의 단점</h3>
<p><strong>1. 유지보수</strong>
요구사항이 변경된 경우 API 스펙이 변경되는 일이 생기면 코드 수정이 불가피하기에 효과적인 유지보수를 위해 DTO를 사용한다. </p>
<p><strong>2. 데이터의 효율적인 전송</strong>
Entity를 직접 반환하는 경우 안에 존재하는 모든 데이터가 반환되기 때문에 필요없는 데이터도 전달될 수 있다. </p>
<p><strong>3. 책임 분리</strong>
유지보수와도 비슷한 맥락의 이야기인데 Entity의 값이 변하게 되면 flush가 호출될 때 데이터베이스에 반영이 되며 다른 로직에도 영향을 끼치게 된다. 그렇기 때문에 데이터의 변경과 수정이 많이 일어나는 DTO 클래스를 분리해줌으로써 책임을 나눠줄 수 있다. </p>
<hr>
<p>사실 아직까지도 dto를 어느 단위로 구분해야 할지 등 어렵고 헷갈리는 부분이 많지만 개념을 정리해나가며 차차 공부하면서 익혀야겠다..😓</p>
<hr>
<p>참고 : <a href="https://hstory0208.tistory.com/entry/SpirngJPA-Dto%EC%99%80-Entity%EB%A5%BC-%EB%B6%84%EB%A6%AC%ED%95%B4%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">Dto와 Entity를 분리해서 사용하는 이유</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Spring Security 로그인 화면 제거 ]]></title>
            <link>https://velog.io/@chae_yu/Spring-Spring-Security-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%99%94%EB%A9%B4-%EC%A0%9C%EA%B1%B0</link>
            <guid>https://velog.io/@chae_yu/Spring-Spring-Security-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%99%94%EB%A9%B4-%EC%A0%9C%EA%B1%B0</guid>
            <pubDate>Fri, 09 Aug 2024 04:09:41 GMT</pubDate>
            <description><![CDATA[<h2 id="😭-사건의-발단">😭 사건의 발단</h2>
<p>이번 방학에 기회가 되어서 프로젝트를 하게 되었다! </p>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/c57aaa3e-211b-4f85-81b8-3f7664cf8e28/image.png" alt=""></p>
<p>그런데 서버를 실행하면 계속 이런 화면이 떠서 Swagger 홈페이지도 안 뜨고 아무것도 되지 않아 애를 먹었다. 
게다가 나는 어째서인지 username, password를 입력해도 아무것도 돌아가지 않았다.. </p>
<p>검색해보니 아주 간단하게 해결할 수 있어서 나중에 또 프로젝트 셋업 과정에서 비슷한 상황이 발생하면 써먹으려고 기록해둔다!!</p>
<h2 id="🤔-원인">🤔 원인</h2>
<pre><code class="language-java">  implementation(&#39;org.springframework.boot:spring-boot-starter-oauth2-client&#39;) </code></pre>
<p> 해당 화면은 build.gradle에 스프링 시큐리티 의존성이 주입되는 과정에서 나오는 기본 로그인 화면이라고 한다. </p>
<h2 id="🥴-해결">🥴 해결</h2>
<p>해당 화면이 필수적으로 나타나야하는 경우도 있다고 하는데 우선 현 시점 나에게는 swagger 홈페이지가 나타나야하는게 우선이였기 때문에 간단하게 없애는 방법을 알아봤다. </p>
<p>서버가 실행되는 Main 클래스에서 <code>@SpringBootApplication</code>에 <code>(exclude = SecurityAutoConfiguration.class)</code>를 추가해주면 간단하게 해결된다. </p>
<pre><code class="language-java">@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }

}</code></pre>
<p>이외에도 SecurityConfig 클래스에 코드를 추가해서 해결하는 방법도 있었는데 이 방법이 가장 간단해서 사용해주었다. </p>
<hr>
<p>프로젝트 관해서는 나중에 정리하고 틈틈히 기록해두어야 할 것만 정리해야겠다. 아주아주 감사한 선생님 한 분이 계셔서 감자에서 고구마로 거듭나기 위해 노력하는 중이다..🥹😻💪🏻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SE] 1. What is Software Engineering?]]></title>
            <link>https://velog.io/@chae_yu/SE-1.-What-is-Software-Engineering</link>
            <guid>https://velog.io/@chae_yu/SE-1.-What-is-Software-Engineering</guid>
            <pubDate>Thu, 25 Jul 2024 05:58:55 GMT</pubDate>
            <description><![CDATA[<p>이번 게시물에서는 소프트웨어공학의 정의와 간단한 특징에 대해 알아볼 것이고 각 부분에 대해 자세한 내용은 뒤에 차차 다룰 예정이다.</p>
<hr>
<h1 id="💻-software-engineering-se">💻 Software Engineering (SE)</h1>
<p>: 소프트웨어를 <strong>비용 효율적 (cost-effective)</strong>으로 만들고 <strong>professional</strong> 하게 만들고자 하는 것으로 쉽게 말해 소프트웨어를 잘 개발하고 유지보수하기 쉽게 만들고자하는 공학이라고 볼 수 있다</p>
<p>현재 소프트웨어는 </p>
<ul>
<li>큰 시스템, 여러 사람의 개발, 지속적인 유지 보수(maintenance, reuse) 로 인해 힘듦</li>
<li>특히 개발 보다 <strong>유지보수 (maintenance)</strong>가 더 어렵고 가격이 많이 듦</li>
</ul>
<p>이러한 어려움을 해결하기 위해 소프트웨어 공학 (software engineering)은 <strong>software production</strong>의 모든 부분과 관련되어있음</p>
<ul>
<li><strong>“All aspects of software Production”</strong><pre><code>: technical process of development &amp; project management &amp; support software production</code></pre></li>
<li><strong>“Engineering discipline”</strong>
   : 문제를 해결하기 위한 적절한 이론과 방법을 사용</li>
</ul>
<hr>
<h2 id="🌀-fundamentals-of-se">🌀 Fundamentals of SE</h2>
<p>&#39; <strong>NO SILVER BULLET</strong> &#39;
: 소프트웨어공학을 관통하는 문장으로 SE는 워낙 <strong>다양 (diversity)</strong> 하여 정확하고 명확한 해답이 존재하지 않는다.</p>
<p>따라서 근본적인 원칙들을 기반으로 적용해야한다</p>
<ol>
<li>잘 관리되어야하고 이해할 수 있는 개발 프로세스를 활용해야 함<ul>
<li>ex) SDLC(생명주기 모델), development Process, Agile 등.. </li>
</ul>
</li>
<li>품질이 좋아야함 즉, <strong>dependability</strong> and <strong>성능 (performance)</strong>가 중요함<ul>
<li>ex) Software Quality</li>
</ul>
<ol start="3">
<li>요구사항을 이해하고 만족해야함</li>
</ol>
<ul>
<li>ex) Requirement Engineering</li>
</ul>
<ol start="4">
<li>적절한 곳에 이미 개발되어 있는 <strong>소프트웨어를 재사용 (software</strong> <strong>reuse)</strong> 하는 것이 가능해야 함</li>
</ol>
-&gt; 즉 새로 만드는 것보다 재활용하는 것이 더 나음<ul>
<li>ex) Software Reuse, Open-Source Software → API와 같이 Web-based software에 대한 정보를 가져다가 씀</li>
</ul>
</li>
</ol>
<hr>
<h2 id="🌀-types-of-software-products">🌀 Types of Software Products</h2>
<p>소프트웨어 상품은 크게 &#39;generic software&#39;와 &#39;customized software&#39;로 구분된다</p>
<ul>
<li><strong>Generic software</strong>
  : 일반적인 소프트웨어로 SRS가 <strong>개발자</strong>에 의해 결정되는 것으로 개발자가 원하는 대로 만들어 놓은 후, 그것을 원하는 사람이 있다면 사서 쓰는 방식<ul>
<li>reuse의 관점 → <strong>evolution</strong></li>
<li>ex) word, excel …</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Customized Software</strong>
 : 사용할 사람 즉, 고객 (customer)이 개발자에게 그들의 요구 사항을 말한 후 그것을 기반으로 개발하는 방식<ul>
<li>SRS는 소프트웨어의 <strong>user</strong>에 의해 결정됨</li>
<li>reuse의 관점 → <strong>maintenance</strong></li>
<li>ex) teller가 은행에서 사용하는 시스템, 대학교 포탈</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🌀-esstential-attributes-of-good-software-products">🌀 Esstential Attributes of Good Software Products</h2>
<p>좋은 소프트웨어라고 하는 것에는 필수적인 특징들이 존재한다</p>
<ul>
<li><strong>Maintainability</strong> <strong>(=유지보수)</strong><ul>
<li>계속하여 수정되고 변경되는 코드에 유연한 대응이 가능해야함</li>
<li>고객 (customer)의 요구를 충족할 수 있도록해야함</li>
</ul>
</li>
<li><strong>Dependability (= 의존 가능, 신뢰 가능)</strong><ul>
<li>의존성 (reliability), 보안 (security), 안전성 (safety) 포함해야함</li>
<li>시스템이 안정적으로 동작할 수 있어야함</li>
</ul>
</li>
<li><strong>Efficiency</strong><ul>
<li>time, resource같은 제한된 자원을 효율적으로 사용하는 것</li>
<li>자원을 낭비해서는 안됨</li>
</ul>
</li>
<li><strong>Acceptability (=수용성)</strong><ul>
<li>user의 타입에 accceptable 해야함</li>
<li>이해가능하고 (understandable), 사용하기 쉽고 (usable), 호환이 편리한 (compatible) 시스템이여야 함</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🌀-typical-activities-in-se">🌀 Typical Activities in S.E</h2>
<ul>
<li><p><strong>Software Specification</strong></p>
<ul>
<li>요구사항 분석</li>
<li>&#39;요구공학 (Requirements Engineering)&#39;의 최종산출물은 SRS이다. </li>
</ul>
<blockquote>
<p><strong>SRS(Software Requirement Specification Document)</strong>란?
: 특정 프로젝트의 요구 사항과 요구 사항에 대한 자세한 설명을 제공하는 소프트웨어 개발에 필수적인 문서</p>
</blockquote>
</li>
<li><p><strong>Software Development</strong></p>
<ul>
<li>프로그램 화 작업 → designed &amp; programmed</li>
<li>ex) Architecture Design(high level), Detailed Design(low level), Implementation</li>
<li><strong>CTIP (continuous test and integration platform)</strong>: 지속적 테스트와 통합으로 빠른 피드백과 높은 품질의 소프트웨어 배포를 지원함</li>
</ul>
</li>
<li><p><strong>Software Validation</strong></p>
<ul>
<li>customer가 요구한대로 만들어졌는지 <strong>확인&amp;검증</strong></li>
<li>ex) Software V&amp;V(Verification&amp;Validation), Testing</li>
<li>) verfication과 validation은 엄연히 다른 과정이니 구분해야한다. 나중에 자세히 다룰 것이다.</li>
</ul>
</li>
<li><p><strong>Software Evolution</strong></p>
<ul>
<li><strong>유지보수 (Maintenance)</strong>의 일종으로 계속해서 요구 사항에 맞게 발전하는 것</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🌀-software-application-types">🌀 Software Application Types</h2>
<p>소프트웨어 어플리케이션 종류에는 여러 종류가 존재하는데 몇 개만 알아보도록 하겠다. 그냥 이런 종류가 있구나 하고 넘어가도 된다. </p>
<ul>
<li><p><strong>stand-alone applications</strong></p>
<ul>
<li>네트워크 연결 없이 잘 돌아가는 독립적인 application
(= do not need to be connected to network)</li>
</ul>
</li>
<li><p><strong>Interactive transcation-based applications (=~ Web-based Systems)</strong></p>
<ul>
<li>사용자의 입력에 반응하여 application을 수행하는 것으로 네트워크를 필요로함
ex) online-game 종류, 예약 시스템</li>
</ul>
</li>
<li><p><strong>Embedded control systems</strong></p>
<ul>
<li>하드웨어 장치를 관리하고 제어하는 software control system
ex) 전국 T-money, 전투기 등..</li>
</ul>
</li>
<li><p><strong>Batch processing systems</strong></p>
<ul>
<li>중간에 개입이나 인터럽트 없이 한 번에 쭉 진행되어 결과를 내놓는 시스템
ex) 월급이나 성과금 계산</li>
</ul>
</li>
<li><p><strong>Systems for modelling and simulation (M&amp;S)</strong></p>
<ul>
<li>하드웨어와 소프트웨어 동시에 보는 것</li>
<li>복잡한 현실 세계의 시스템이나 프로세스를 표현하고 실험하는데 사용
 ex) matlab</li>
</ul>
</li>
<li><p><strong>Data collection systems</strong></p>
<ul>
<li>센서를 통해 데이터를 수집하고 처리를 위해 다른 시스템 송신
  ex) IOT</li>
</ul>
</li>
<li><p><strong>Systems of systems (<del>=</del> CPS: cyber physical system)</strong></p>
<ul>
<li>여러 다른 소프트웨어 시스템으로 구성되어있음 (서로 communication허는 software로 구성)</li>
<li>회사 등에서 하위 시스템들을 관리하는 총괄 시스템</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/7c131280-5a20-480e-971c-8568ff36d976/image.png" alt=""></p>
<hr>
<h2 id="🌀-10-faqs-about-software-engineering">🌀 10 FAQs about Software Engineering</h2>
<p> 소프트웨어 공학에 대해 자주 묻는 10가지의 질문에 대해 정리해보았다</p>
<p><strong>1. 소프트웨어란 무엇인가?</strong>
: 컴퓨터 프로그램과 관련 문서로 소프트웨어 제품은 특정 고객을 위해 개발되거나 일반 시장을 위해 개발될 수 있다</p>
<p><strong>2. 좋은 소프트웨어의 속성은?</strong>
: 좋은 소프트웨어는 요구된 기능과 성능을 사용자에게 제공하고 유지보수 가능하고 신뢰성 있으며 사용하기 쉬워야 한다</p>
<p><strong>3. 소프트웨어 공학이란?</strong>
: 소프트웨어 공학은 소프트웨어 생산의 모든 측면을 다루는 엔지니어링 학문이다</p>
<p><strong>4. 기본적인 소프트웨어 공학 활동은?</strong>
: 소프트웨어 구체화 (software specification), 소프트웨어 개발 (software development), 소프트웨어 검증 (software validation) 및 소프트웨어 진화 (software evolution) 이다</p>
<p><strong>5. 소프트웨어 공학과 컴퓨터 과학의 차이는?</strong>
: 컴퓨터 과학은 <strong>이론</strong>과 <strong>기초</strong>에 중점을 두는 반면 소프트웨어 공학은 유용한 소프트웨어를 개발하고 제공하는 <strong>실용성</strong>에 중점을 둔다</p>
<p><strong>6. 소프트웨어 공학과 시스템 공학의 차이는?</strong>
: 시스템 공학은 하드웨어, 소프트웨어 및 프로세스 엔지니어링을 포함한 컴퓨터 기반 시스템 개발의 모든 측면을 다루는 반면 소프트웨어 공학은 이 더 일반적인 과정의 일부이다</p>
<p><strong>7. 소프트웨어 공학이 직면한 주요 과제는?</strong>
: 증가하는 개발의 다양성, 짧은 시간 내의 요구 및 신뢰할 수 있는 소프트웨어 개발에 대처하는 것이다</p>
<p><strong>8. 소프트웨어 공학의 비용은 얼마나 되나요?</strong>
: 일반적으로 소프트웨어 비용의 약 60%는 개발 비용이고 40%는 테스트 비용입니다. </p>
<p><strong>9. 가장 좋은 소프트웨어 공학 기법과 방법은 무엇인가요?</strong>
: 모든 소프트웨어 프로젝트는 전문적으로 관리되고 개발되어야 하고 특정 유형의 시스템에는 특정 기법이 적합하기 때문에 한 가지 방법이 다른 방법보다 낫다고 할 수 없다. 앞서 말했듯이 소프트웨어공학은 &#39;NO SILVER BULLET&#39;이다.</p>
<p><strong>10. 웹이 소프트웨어 공학에 미친 영향은?</strong>
 : 웹 기반 시스템 개발은 프로그래밍 언어와 소프트웨어 재사용의 발전을 가져왔다</p>
<hr>
<p>&lt;참고: 소프트웨어공학 수업 자료, Software Engineering 10th edition&gt;</p>
<p>소프트웨어 공학 수업을 들으면서 정리했던 자료를 복습하면서 올려보고자한다. 다시 공부하면서 업로드해야지 !! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[파이썬] 여러 값 입력 받는 법]]></title>
            <link>https://velog.io/@chae_yu/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%97%AC%EB%9F%AC-%EA%B0%92-%EC%9E%85%EB%A0%A5-%EB%B0%9B%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@chae_yu/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%97%AC%EB%9F%AC-%EA%B0%92-%EC%9E%85%EB%A0%A5-%EB%B0%9B%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Wed, 17 Jul 2024 05:21:28 GMT</pubDate>
            <description><![CDATA[<p>알고리즘을 풀다보면 여러 값을 입력 받는데 그 방식이 매우 다양해서 확실하게 한 번 정리해보려고 한다. </p>
<hr>
<h3 id="💻-기본-입력-방식">💻 기본 입력 방식</h3>
<p>파이썬에서 어떤 값을 입력 받을 때에는 <code>input()</code>을 사용하는데 기본이 문자열로 저장되기 때문에 숫자나 실수 등 다른 값으로 입력받고자 할 때는 <code>int(input())</code> 등 형변환을 해줘야한다. </p>
<h3 id="💻-여러-값-입력-받기">💻 여러 값 입력 받기</h3>
<p>파이썬에서 여러 개의 값을 한 번에 입력 받고 싶을 때는 <code>split()</code>  함수를 사용한다. </p>
<h4 id="🌀-문자열-여러-값-입력-받기">🌀 문자열 여러 값 입력 받기</h4>
<p><code>a, b = intput().split()</code> </p>
<h4 id="🌀-문자열-여러-줄로-입력-받기">🌀 문자열 여러 줄로 입력 받기</h4>
<p><code>s_list = [input() for _ in range(n)]</code></p>
<h4 id="🌀-정수-여러-값-입력-받기">🌀 정수 여러 값 입력 받기</h4>
<p>: 문자열이 아닌 다른 값을 입력 받을 때에는 <code>map</code>을 사용해서 형변환해준다
<code>a, b = map(int, intput().split())</code> </p>
<blockquote>
<p> <strong>map 함수란?</strong>
: 리스트의 요소를 지정된 함수로 처리하는 것으로 일반적으로 여러 데이터를 일괄적으로 다른 형태를 바꾸고자할 때 사용한다. 
-&gt; map (function, iterable) 의 형식을 가진다. </p>
</blockquote>
<p>+) 알고리즘을 풀 때 첫 줄에 테스트 케이스의 개수를 입력하고 그 다음줄부터 다른 값들을 입력하는 경우가 많은데 이 때는 일반적으로 아래와 같이 입력문을 작성하면 된다. </p>
<pre><code class="language-python">n = int(input()) # 입력받는 테스트케이스의 개수
for _ in range(n):
    a, b = map(int,sys.stdin.readline().split())</code></pre>
<h4 id="🌀-배열-입력-받기">🌀 배열 입력 받기</h4>
<p>: 1차원 배열
<code>a = list(map(int, intput().split()))</code> </p>
<p>: 2차원 배열  -&gt; 엔터를 기준으로 한 줄씩 배열로 저장
<code>b = list(map(int, input())) for _ in range(n)</code> </p>
<hr>
<p>추가로 알고리즘을 풀다가 헷갈리는 입력 방식이 있다면 작성하도록 하겠다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1244 : 스위치 켜고 끄기 / 파이썬 / 정렬]]></title>
            <link>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-1244-%EC%8A%A4%EC%9C%84%EC%B9%98-%EC%BC%9C%EA%B3%A0-%EB%81%84%EA%B8%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-1244-%EC%8A%A4%EC%9C%84%EC%B9%98-%EC%BC%9C%EA%B3%A0-%EB%81%84%EA%B8%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Sun, 14 Jul 2024 13:06:57 GMT</pubDate>
            <description><![CDATA[<h3 id="💻-문제">💻 문제</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/16865551-a0eb-44cd-a1c4-c5a814367ecb/image.png" alt=""></p>
<p>정렬에 해당하는 백준 : 1244 스위치 켜고 끄기 문제</p>
<hr>
<h3 id="💡-코드">💡 코드</h3>
<h4 id="내-코드">내 코드</h4>
<p>우선 남자와 여자 케이스를 나눠서 문제를 풀었다. 
남자의 경우 스위치 번호 내에서 각 학생이 가진 스위치 번호의 배수만 찾아내면 되었다.
여자의 경우 학생이 가진 번호를 기준으로 양쪽으로 스위치 상태의 여부를 판단해야 해서 스위치 번호 범위를 벗어나거나 대칭이 깨질 때까지 찾아내서 문제를 풀었다. </p>
<pre><code class="language-python">import sys

n = int(input()) # number of switches
switch = list(map(int, sys.stdin.readline().split())) # state of switch -&gt; on:1 / off:0
std = int(input()) # number of student
for _ in range(std):
    sex, number = map(int,sys.stdin.readline().split())

    if sex == 1: # man
        for i in range(1,n+1):
            if i % number == 0:
                if switch[i-1] == 1:
                    switch[i-1] = 0
                else:
                    switch[i-1] = 1

    else: #female
        number -=1
        left, right = number, number

        while (left &gt;= 0 and right &lt; n and switch[left]==switch[right]):
            left-=1
            right+=1

        left+=1
        right-=1

        for i in range(left,right+1):
            if switch[i] == 1:
                switch[i] = 0
            else:
                switch[i] = 1

for i in range(n):
    print(switch[i],end=&#39; &#39;)
    if((i+1)%20==0):
        print()</code></pre>
<p>코드를 위와 같은 방법으로 작성했는데 개선할 부분이 많아 보여 다른 사람들의 코드를 참고하여 어떤 부분을 고쳐야할지 찾아보았다. </p>
<p><strong>1. 반복된 부분 함수로 작성</strong> 
: 남자와 여자 모두 스위치를 변환하는 부분이 공통적으로 있다.</p>
<p>** 2. 남자 - for문 개선**
: if 문을 통해 배수 여부를 판단해도 되지만 for문을 일일이 다 돌지 않고 range를 이용해 간격을 두어 배수만 찾도록 한다.</p>
<pre><code class="language-python">for i in range(number, n+1, number):</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11652:카드 / 파이썬 / 정렬 / 딕셔너리]]></title>
            <link>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-11652%EC%B9%B4%EB%93%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%95%EB%A0%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC</link>
            <guid>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-11652%EC%B9%B4%EB%93%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%95%EB%A0%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC</guid>
            <pubDate>Sun, 14 Jul 2024 12:34:11 GMT</pubDate>
            <description><![CDATA[<h3 id="💻-문제">💻 문제</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/fe0da06e-8e61-4a7e-9c27-bd93436109ad/image.png" alt=""></p>
<p>정렬에 해당하는 백준 11652:카드 문제 </p>
<hr>
<h3 id="💡-코드">💡 코드</h3>
<pre><code class="language-python">cardDic={}
N=int(input())

for _ in range(N):
    card = int(input())
    if card in cardDic:
        cardDic[card] += 1
    else:
        cardDic[card] = 1

ans = sorted(cardDic.items(), key=lambda x: (-x[1],x[0]))
print(ans[0][0])</code></pre>
<hr>
<h3 id="📚-해결-과정">📚 해결 과정</h3>
<p>문제를 보고 같은 번호를 가진 카드의 개수대로 정렬 후, 카드 번호의 숫자대로 정렬해야한다고 생각이 들었다. </p>
<p>파이썬에서 Dictionary 자료형을 제공해주기 때문에 이를 사용하면 되었다. </p>
<h4 id="-딕셔너리-자료형이란">+ 딕셔너리 자료형이란?</h4>
<p>key와 value의 쌍으로 이루어진 자료형으로 일반적으로 key에는 고유한 값이 들어가 value를 식별할 수 있다. 
(기회가 된다면 딕셔너리 자료형에 대해서 정리해보는 시간을 가지겠다.)</p>
<p>아무튼 딕셔너리 자료형을 사용해서 key를 카드 번호로, value를 카드 개수로 지정하여 이미 존재하는 카드라면 value의 값을 1 증가 시키고 없다면 1로 지정하였다.</p>
<p>추가로, 파이썬에서 정렬할 때 sort나 sorted 를 사용하면 된다고 정리한 적이 있는데 특이하게 딕셔너리에서는 <code>sorted</code>만 사용이 가능하다 !!
또, sorted 함수를 사용할 때  어떤 값을 우선순위로 둘지 제대로 정하자
여기서는 카드 개수가 많은 순서대로 먼저 정렬해야하므로 value 값을 내림차순으로 정렬하고 같은 값이 존재할 시 카드 번호를 오름차순으로 정렬해야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[파이썬] sort / sorted]]></title>
            <link>https://velog.io/@chae_yu/%ED%8C%8C%EC%9D%B4%EC%8D%AC-sort-sorted</link>
            <guid>https://velog.io/@chae_yu/%ED%8C%8C%EC%9D%B4%EC%8D%AC-sort-sorted</guid>
            <pubDate>Sun, 14 Jul 2024 05:00:41 GMT</pubDate>
            <description><![CDATA[<h3 id="💻-sort">💻 sort</h3>
<p>: 리스트명.sort( ) 형식으로 리스트형의 메소드</p>
<ul>
<li>리스트 원본 자체를 수정</li>
<li><blockquote>
<p>원본 리스트 자체를 바꾸는 것이기 때문에 정렬된 값을 반환하지 않음</p>
</blockquote>
</li>
<li>기본적으로 오름차순 정렬 </li>
</ul>
<pre><code class="language-python">a1 = [3,2,1]
print(&#39;정렬 전: &#39;, a1)

a1.sort()
print(&#39;정렬 후: &#39;, a1)

a2 = a1.sort()
print(&#39;정렬된 값이 반환되지 않음: &#39;, a2)
</code></pre>
<pre><code class="language-python">정렬 전: [3,2,1]
정렬 후: [1,2,3]
정렬된 값이 반환되지 않음: None</code></pre>
<h3 id="💻-sorted">💻 sorted</h3>
<p>: sorted(리스트명)으로 파이썬의 내장함수</p>
<ul>
<li>기존 리스트는 유지하고 새로운 리스트를 반환</li>
<li>기본적으로 오름차순 정렬</li>
</ul>
<pre><code class="language-python">a1 = [3,2,1]
print(&#39;정렬 전: &#39;, a1)

a2 = sorted(a1)
print(&#39;정렬 후: &#39;, a2)

print(&#39;원본 리스트의 값은 유지 - a1 : &#39;, a1)
print(&#39;정렬된 값이 반환 - a2 : &#39;, a2)
</code></pre>
<pre><code class="language-python">정렬 전: [3,2,1]
정렬 후: [1,2,3]
원본 리스트의 값은 유지 - a1 : [3,2,1]
정렬된 값이 반환 - a2 : [1,2,3]</code></pre>
<hr>
<h3 id="💻-파이썬에서의-정렬">💻 파이썬에서의 정렬</h3>
<p>앞서 살펴본 sort와 sorted는 기본적으로 오름차순으로 정렬된다. sort와 sorted 모두 <strong>key</strong>와 <strong>reverse</strong>를 통해 원하는 순서로 정렬할 수 있다. 
(sort와 sorted에서 key와 reverse를 통해 정렬하는 방법은 같으니 예시를 들 때에는 sort를 사용하도록 하겠다.)</p>
<h4 id="1-기본-정렬-오름차순--내림차순">1. 기본 정렬 (오름차순 / 내림차순)</h4>
<p>내림차순으로 정렬하고 싶을 때에는 &#39;reverse&#39; 값을 &#39;True&#39; 로 설정해주면 된다. 기본 값이 오름차순 정렬이기 때문에 오름차순 정렬 시에는 생략 가능하다. </p>
<pre><code class="language-python">a1 = [3,1,2]

a1.sort()
print(&#39;오름차순 정렬: &#39;, a1)

a1.sort(reverse=True)
print(&#39;내림차순 정렬: &#39;, a1)
</code></pre>
<pre><code class="language-python">오름차순 정렬: [1,2,3]
내림차순 정렬: [3,2,1]</code></pre>
<h4 id="2-2차원-정렬">2. 2차원 정렬</h4>
<p>key의 값을 지정해줌으로써 배열 내에서 원하는 값을 통해 정렬할 수 있다
+) key 값을 따로 지정하지 않으면 기본적으로 오름차순 정렬을 해준다. </p>
<p><code>a1 = [[1, 1], [3, 4], [2, 5], [1, 3]]</code> 라는 배열로 예시를 들어 살펴보도록 하겠다. </p>
<h5 id="--첫번째-인덱스를-기준으로-정렬">- 첫번째 인덱스를 기준으로 정렬</h5>
<p>: 첫번째 인덱스가 동일한 경우, 두 번째 인덱스를 기준으로 오름차순 정렬을 해준다.</p>
<pre><code class="language-python">a1 = [[1, 1], [3, 4], [2, 5], [1, 3]]

a1.sort(key=lambda x:x[0])
print(&#39;리스트의 첫번째 인덱스를 기준으로 오름차순 정렬: &#39;, a1)
</code></pre>
<pre><code class="language-python">리스트의 첫번째 인덱스를 기준으로 오름차순 정렬: [[1,1],[1,3],[2,5],[3,4]]</code></pre>
<h5 id="--두번째-인덱스를-기준으로-정렬">- 두번째 인덱스를 기준으로 정렬</h5>
<p>: 두번째 인덱스가 동일한 경우, 첫번째 인덱스를 기준으로 오름차순 정렬을 해준다.</p>
<pre><code class="language-python">a1 = [[1, 1], [3, 4], [2, 5], [1, 3]]

a1.sort(key=lambda x:x[1])
print(&#39;리스트의 두 번째 인덱스를 기준으로 오름차순 정렬: &#39;, a1)</code></pre>
<pre><code class="language-python">리스트의 두 번째 인덱스를 기준으로 오름차순 정렬: [[1,1],[1,3],[3,4],[2,5]]</code></pre>
<p>만약, 두 번째 인덱스가 동일할 때 첫번째 인덱스를 정렬하는 기준을 정하고 싶다면 추가로 값을 적어주면 된다. </p>
<pre><code class="language-python">a1 = [[1, 1], [3, 4], [2, 5], [1, 3]]

a1.sort(key=lambda x: (x[1], -x[0]))
print(&#39;두 번째 인덱스를 기준으로 오름차순 정렬, 동일한 경우 첫번째는 내림차순 정렬: &#39;, a1)</code></pre>
<pre><code class="language-python">두 번째 인덱스를 기준으로 오름차순 정렬, 동일한 경우 첫번째는 내림차순 정렬
정렬: [[1,3],[1,1],[3,4],[2,5]]

- - - - - -</code></pre>
<pre><code class="language-python">a1 = [[1, 1], [3, 4], [2, 5], [1, 3]]

a1.sort(key=lambda x: (x[1], x[0]))
print(&#39;두 번째 인덱스를 기준으로 오름차순 정렬, 동일한 경우 첫번째도 오름차순 정렬:&#39;, a1)</code></pre>
<p>```python
두 번째 인덱스를 기준으로 오름차순 정렬, 동일한 경우 첫번째도 오름차순 정렬
:[[1,1],[1,3],[3,4],[2,5]]</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11650: 좌표 정렬하기 / 파이썬 / 정렬 / Sorting]]></title>
            <link>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-11650-%EC%A2%8C%ED%91%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%95%EB%A0%AC-Sorting</link>
            <guid>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-11650-%EC%A2%8C%ED%91%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%95%EB%A0%AC-Sorting</guid>
            <pubDate>Fri, 12 Jul 2024 06:29:01 GMT</pubDate>
            <description><![CDATA[<h3 id="💻-문제">💻 문제</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/bd0d2ca6-0a16-4b6f-966a-9af2bef27fac/image.png" alt=""></p>
<p>정렬에 해당하는 백준 11650: 좌표 정렬하기 문제이다. </p>
<hr>
<h3 id="💡-코드">💡 코드</h3>
<h4 id="초기-코드">초기 코드</h4>
<pre><code class="language-python">import sys

N = int(input())
const = [list(map(int, sys.stdin.readline().split())) for _ in range(N)]

for i in range(N):
    for j in range(N-i-1):
        if const[j][0] &gt; const[j+1][0] or (const[j][0]==const[j+1][0] and const[j][1]&gt;const[j+1][1]):
            const[j], const[j+1] = const [j+1], const [j]

for point in const:
    print(point[0], point[1])</code></pre>
<p>문제를 딱 보자마자 생각나는대로 bubble sort를 사용하여 풀었더니 당연한 결과이겠지만 시간 초과가 떴다. 
❗️ bubble sort의 시간복잡도는 O(n^2) 로 최악이니 절대 사용하지 말자..</p>
<p>그래서 해결책으로 파이썬 내장함수인 sort 함수를 사용하였다. 이 함수는 O(nlogn)의 시간복잡도를 가진다. </p>
<h4 id="정답-코드">정답 코드</h4>
<pre><code class="language-python">import sys
N = int(input())
const = [list(map(int, sys.stdin.readline().split())) for _ in range(N)]
const_s = sorted(const)

for point in const_s:
    print(point[0], point[1])</code></pre>
<p>sorted 함수를 사용하면 오름차순으로 저절로 해주니 얼마나 편한지..!!</p>
<hr>
<h3 id="💪🏻-til">💪🏻 TIL</h3>
<ul>
<li><p><code>map()</code> : 입력된 값을 특정 값으로 반환해줌</p>
</li>
<li><p><code>split()</code> : 분할 값을 기준으로 입력된 값을 나눔</p>
</li>
</ul>
<p>+) <a href="https://velog.io/@chae_yu/%ED%8C%8C%EC%9D%B4%EC%8D%AC-sort-sorted#-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C%EC%9D%98-%EC%A0%95%EB%A0%AC">파이썬 sort/ sorted 정리글</a></p>
<p>시간 복잡도를 생각하려해도 잘 생각나지 않는걸 보니 자료구조 복습이 절실히 필요하다고 느낀다 😭
또 파이썬 내장 함수를 그냥 사용하는게 아니라 각 함수가 어떤 시간 복잡도를 가지는지 공부하는 시간을 가져야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2164 : 카드2 / 파이썬 / 자료구조 /  큐]]></title>
            <link>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-2164-%EC%B9%B4%EB%93%9C2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%81%90</link>
            <guid>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-2164-%EC%B9%B4%EB%93%9C2-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%81%90</guid>
            <pubDate>Fri, 12 Jul 2024 05:16:12 GMT</pubDate>
            <description><![CDATA[<h3 id="💻-문제">💻 문제</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/18838c2d-b16b-4b21-bc08-a4ed3102d183/image.png" alt=""></p>
<p>자료구조 / 큐 문제에 해당하는 &#39;2164 : 카드2&#39; 문제</p>
<hr>
<h3 id="💡-코드">💡 코드</h3>
<h4 id="초기-코드">초기 코드</h4>
<pre><code class="language-python">numList=[]

N=int(input())
for i in range(N,0,-1):
    numList.append(i)


while len(numList) != 1:  
    numList.pop()
    next=numList.pop()
    numList.insert(0,next)

print(numList)</code></pre>
<p>처음에는 list를 사용해 코드를 구현하였으나, 시간초과가 발생했다. 그래서 list보다 연산 속도가 빠른 deque를 사용해서 문제를 해결하였다. </p>
<h4 id="정답-코드">정답 코드</h4>
<pre><code class="language-python">from collections import deque

N=int(input())
myqueue = deque()

for i in range(N,0,-1):
    myqueue.append(i)

while len(myqueue) != 1:
    myqueue.pop()
    next = myqueue.pop()
    myqueue.appendleft(next)
print(myqueue[0])</code></pre>
<hr>
<h3 id="📚-해결-과정">📚 해결 과정</h3>
<p><strong>list</strong>를 사용하여 구현하였을 때에는 맨 위에 있는 카를 <code>pop()</code>을 사용해서 버리고 그 다음 위에 있는 카드를 <code>insert()</code> 함수를 사용하여 list의 앞쪽에 추가하는 방식을 택하였다. </p>
<p>list는 고정된 배열이기 때문에 의 앞쪽에 데이터를 삽입하는 과정에서_ O(n)_ 만큼의 시간 복잡도가 발생하여 시간 초과가 발생하는 문제가 있었다. </p>
<p>데이터를 앞이나 중간에 삽입할 때에는 <strong>dequeue</strong>와 같은 구조의 자료구조를 사용해줌으로써 시간 복잡도를 줄일 수 있다. </p>
<p>내부적으로 dequeue는 double linked list 구조로 구현되어있어서 앞이나 뒤에 데이터를 삽입, 삭제할 때에도 _O(1)_의 시간복잡도가 걸리게된다. </p>
<p>list와 dequeue의 시간복잡도 및 내부 구현에 관련해서는 아래 링크를 통해 정리해놨다.<br>(링크 추가 예정)</p>
<hr>
<h3 id="💪🏻-til">💪🏻 TIL</h3>
<p>시간복잡도를 고려하여 어떤 자료구조를 사용하는 것이 문제 해결에 적절할지 생각하는 습관을 가져야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] LV1 / 소수만들기/ 파이썬]]></title>
            <link>https://velog.io/@chae_yu/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-LV1-%EC%86%8C%EC%88%98%EB%A7%8C%EB%93%A4%EA%B8%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@chae_yu/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-LV1-%EC%86%8C%EC%88%98%EB%A7%8C%EB%93%A4%EA%B8%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Fri, 12 Jul 2024 04:15:03 GMT</pubDate>
            <description><![CDATA[<h3 id="💻-문제">💻 문제</h3>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/6dd07599-710c-44d9-a230-c3c78829178e/image.png" alt=""></p>
<p>프로그래머스 Lv1. 소수만들기 문제이다. </p>
<hr>
<h3 id="💡-내-코드">💡 내 코드</h3>
<pre><code class="language-python">def isPrime(sum):
    for i in range(2,sum):
        if sum % i == 0:
            return False
    return True 

def solution(nums):
    answer,sum = 0,0

    for i in range(0,len(nums)-2):
        for k in range (i+1, len(nums)-1):
            for j in range (k+1, len(nums)):
                sum = nums[i]+nums[k]+nums[j]
                if isPrime(sum) : answer+=1

    return answer</code></pre>
<h4 id="📚-해결-과정">📚 해결 과정</h4>
<p>배열에 들어있는 숫자들 중 세 개를 선택하여 소수가 되는 경우의 수를 판별해야했기에 3중 for문으로 숫자를 선택하는 코드를 작성하고 소수인지 아닌지 판별하는 함수를 통해 문제를 풀었다. </p>
<hr>
<h3 id="💡-개선-코드">💡 개선 코드</h3>
<p>삼중 for문을 사용하기도 했고 코드에 개선할 부분이 많아 보여 다른 분들의 코드를 참고하여 보완점을 찾았다. </p>
<p><strong>1. 소수 판별 코드</strong></p>
<pre><code class="language-python">def isPrime(sum):
    for i in range(2,sum):
        if sum % i == 0:
            return False
    return True</code></pre>
<p>기존 코드에서는 for문을 소수인지 판별하고자 하는 수 전체를 돌도록 작성하였는데 이렇게 하게 되면 시간 복잡도가 증가하게된다.</p>
<p>이때 가운데 약수를 기준으로 수들은 대칭적인 구조를 보인다. 
다시 말해, 16의 경우 약수로 1,2,4,6,16 을 가지게 되는데</p>
<ul>
<li>1 X 16 = 16</li>
<li>2 X 8 = 16</li>
<li>4 X 4 = 16</li>
<li>8 X 2 = 16</li>
<li>16 X 1 = 16</li>
</ul>
<p>위와 같이 4x4 즉 제곱근을 기준으로 대칭적인 구조를 가지는 것을 확인할 수 있다. 그렇기 때문에 for문이 숫자 범위를 전부 도는 것이 아닌 제곱근까지 돌게 해도 소수를 판별할 수 있다. </p>
<p>아래와 같이 개선된 코드를 작성할 수 있다. </p>
<pre><code class="language-python">def isPrime(result):
    for i in range(2,(result//2)+1):
        if result % i == 0:
            return False
    return True</code></pre>
<ul>
<li><code>(sum//2)+1</code> 대신 <code>math.sqrt(sum)) + 1</code> 을 사용할 수도 있다. </li>
</ul>
<p><strong>2. 3중 for문 대신 파이썬 내장함수 활용</strong></p>
<p>3중 for문을 사용하게 되면 시간 복잡도 측면에서 안 좋기도 하고 파이썬은 내장 함수가 아주 잘 되어있는 편이다. 물론 나는 아직 익숙치 않아 써먹지 못하고 있지만 .. </p>
<p>그 중 <code>combination()</code>이라는 함수를 활용하면 중복없이 조합을 반환해준다.</p>
<p>특정 배열 내에서 일정 수 만큼의 조합을 생성하고 싶다면 <code>combination(list,num)</code> 에서 list 에는 배열 변수를, num에는 원하는 조합의 개수를 넣어주면 된다. </p>
<pre><code class="language-python">from itertools import combinations

def isPrime(result):
    for i in range(2,(result//2)+1):
        if result % i == 0:
            return False
    return True 

def solution(nums):
    answer=0

    comb = list(combinations(nums,3))
    for i in comb:
        if isPrime(sum(i)) : answer+=1
    return answer</code></pre>
<hr>
<p>파이썬 내장 함수들에 대해서 공부해야겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 9012번 : 괄호 / 파이썬 / 자료구조 / 스택]]></title>
            <link>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-9012%EB%B2%88-%EA%B4%84%ED%98%B8-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8A%A4%ED%83%9D</link>
            <guid>https://velog.io/@chae_yu/%EB%B0%B1%EC%A4%80-9012%EB%B2%88-%EA%B4%84%ED%98%B8-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8A%A4%ED%83%9D</guid>
            <pubDate>Thu, 11 Jul 2024 14:23:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/chae_yu/post/ecb246e9-e72f-465a-92ef-ba090ef98a2c/image.png" alt="">
자료구조 / 스택에 해당하는 &#39;9012:괄호&#39; 문제이다.</p>
<hr>
<h3 id="💡-정답-코드">💡 정답 코드</h3>
<pre><code class="language-python">stack=[]

t = int(input())
for _ in range (t):
    data = input()
    isVPS=True
    for char in data:
        if char==&#39;(&#39;:
            stack.append(char)
        elif char==&#39;)&#39;:
            if stack:
                stack.pop()
            else:
                isVPS=False
                break

    # 다 처리한 후에 스택에 남아있는게 있으면 vps가 아님
    if stack:
        isVPS=False

    if isVPS:
        print(&quot;YES&quot;)
    else:
        print(&#39;NO&#39;)

    stack.clear()</code></pre>
<hr>
<h3 id="📚-해결-과정">📚 해결 과정</h3>
<p>처음에 단순히 괄호의 짝만 맞으면 되는줄 알고 &#39;(&#39; 괄호의 개수와 &#39;)&#39; 괄호의 개수를 비교하여 같으면 YES를, 다르면 NO를 출력하는 코드를 작성하였는데 해당 문제에서는 VPS여부를 판단해야했다. </p>
<ol>
<li>stack 자료 구조를 이용 </li>
<li>입력되는 값과 다음 값을 비교하여 stack에서 pop을 해줌</li>
<li>만약 스택이 비어있거나 &#39;)&#39; 값이 먼저 들어온다면 VPS 조건이 만족되지 않음을 이용</li>
</ol>
<hr>
<ul>
<li>문제를 푸는 데에 급급해서 제대로 읽지 않고 다급하게 하다보니 애먼 시간을 쓰는 점을 고치기 위해 차분히 문제부터 분석하고 코드를 작성하자</li>
<li><blockquote>
<p>무작정 키보드에 손 올리지 않기!</p>
</blockquote>
</li>
<li>스택 말고 다른 자료구조로 풀리는 알고리즘이 많으니 자료구조 개념에 대해 정리도 다시 해야겠다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] push error 해결 - error: failed to push some refs to]]></title>
            <link>https://velog.io/@chae_yu/Git-push-error-%ED%95%B4%EA%B2%B0-error-failed-to-push-some-refs-to</link>
            <guid>https://velog.io/@chae_yu/Git-push-error-%ED%95%B4%EA%B2%B0-error-failed-to-push-some-refs-to</guid>
            <pubDate>Wed, 10 Jul 2024 05:00:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/chae_yu/post/bc0f6b64-93cd-44a7-825c-87561bdaca3d/image.png" alt=""></p>
<p>공부하고 있던 자바 프로젝트를 github에 올리려고 git push 를 하는 과정에서 계속 
<strong>&#39;error: failed to push some refs to&#39;</strong> 에러가 발생하였다. </p>
<p>리포지토리를 생성하고 하고 있던 프로젝트를 올리는 과정이라 리포지토리에는 아무것도 없어서 branch 문제인가 하고 main, master branch 병합 방법을 찾아보고 했는데 그 문제가 아니라 원격 저장소의 문제라서 원격 저장소를 초기화해주는 방법으로 이 오류를 해결하였다. </p>
<p>중간에 commit 한 것들이 너무 많아 파일들이 지저분한 상태라면 원격 저장소를 삭제하긴 어려우니 마지막 commit 상태로 초기화시키는 이 방법을 사용하면 좋을 듯 하다. </p>
<hr>
<ol>
<li><p>로컬 저장소의 .git directory를 삭제해준다.</p>
<pre><code>rm -rf ./.git </code></pre></li>
<li><p>로컬 저장소에서 git init으로 초기화 시킨다.</p>
<pre><code>git init</code></pre></li>
<li><p>등록될 파일을 커밋해준다.</p>
<pre><code>git add .
git commit -m &#39;message&#39;</code></pre></li>
</ol>
<ul>
<li>&#39;message&#39; 부분에 원하는 커밋 메세지를 입력해주면된다. </li>
</ul>
<ol start="4">
<li>초기화 시킬 원격 저장소를 연결한다.<pre><code>git remote add origin &lt;url&gt;</code></pre></li>
</ol>
<ul>
<li>원격 저장소 url은 리포지토리에서 Code를 누르면 복사할 수 있다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/chae_yu/post/acda849a-767c-4bf0-bd3f-78498660f6b7/image.png" alt=""></p>
<ol start="5">
<li>현재 상태를 원격 저장소에 push.<pre><code>git push --force --set-upstream origin main</code></pre></li>
</ol>
<hr>
<p>git을 사용하는데 익숙치 않아서 단순히 push하고 pull하는 데에도 어려움이 많이 생겨 공부가 필요할 것 같다. 😭💪🏻</p>
<hr>
<p>참고 url : <a href="https://niees.tistory.com/25">https://niees.tistory.com/25</a></p>
]]></description>
        </item>
    </channel>
</rss>