<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>@insukL.log</title>
        <link>https://velog.io/</link>
        <description>데이터를 소중히 여기는 개발자가 되고 싶습니다</description>
        <lastBuildDate>Sat, 17 Jan 2026 11:23:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. @insukL.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cloud_365" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Storage (File, Block, Object)]]></title>
            <link>https://velog.io/@cloud_365/Storage-File-Block-Object</link>
            <guid>https://velog.io/@cloud_365/Storage-File-Block-Object</guid>
            <pubDate>Sat, 17 Jan 2026 11:23:43 GMT</pubDate>
            <description><![CDATA[<p> 아키텍처 구성 중에 목표 프로젝트가 데이터를 다루는 만큼 데이터를 저장하는 장소에 대한 고려가 필요했다. 다양한 서비스를 활용할 수 있겠지만 결국 클라우드냐 온프레미스냐로 나눠서 고려하게 되었다. 그중에서 특히 시놀리지와 같은 NAS 장비를 홈에서 구축하는 방법에 관심이 생겼다. 비교를 위해서는 기존에 고려한 클라우드(S3) 방식과 어떤 차이가 있는지 공부가 필요하다 느꼈고, 아래와 같이 학습과 정리를 진행했다.</p>
<h1 id="왜-storage">왜 Storage?</h1>
<p> 기본적으로 저장소라 말하는 스토리지는 컴퓨터 사용자에게 익숙한 용어다. 기본적으로 컴퓨터는 메인 프로세서와 저장 장치를 가지고, 우리는 이를 활용해서 데이터를 생산, 가공하고 저장하게 된다. 여기서 조금 더 확장해서 PC가 아닌 여러 명의 사용자가 접근하는 커다란 시스템으로 바꿔보자.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/f0b6cd2c-2ce8-4792-b8d8-540ccba5c0c5/image.png" alt=""></p>
<p> 그림으로 간단하게 표현해봐도 벌써 머리가 아프다. 저장소는 적고 제한적이며, 저장 공간을 늘리겠다고 디바이스 자체를 늘려야 하고, 늘려봤자 기존 디바이스는 추가한 디바이스의 저장소를 참조할 수 없다. 또 동시에 저장소에 접근하는 사람은 여러 명이고 정보가 파편화되고.. 등등 다양한 문제가 야기될 것이 눈에 뻔하다. 그래서 기존에 디바이스에 묶여 있는 구조가 아니라 컴퓨팅과 스토리지를 분리하는 아키텍처가 요구되기 시작한다.</p>
<h1 id="데이터-스토리지-아키텍처-data-storage-architecture">데이터 스토리지 아키텍처 (Data Storage Architecture)</h1>
<p> 시작은 단순하게 호스트가 되는 디바이스와 저장 장치가 되는 스토리지를 분리하자. 그리고 호스트에서 사용할 스토리지에 직접 연결해 스토리지에 저장, 읽기, 수정 등을 진행한다. 이렇게 호스트와 스토리지를 연결해서 데이터를 저장하는 방식을 DAS(Direct Attached Storage)라고 한다.</p>
<p> <img src="https://velog.velcdn.com/images/cloud_365/post/b73dc7a8-0f36-4400-b2fd-096106230883/image.png" alt=""></p>
<p> 어떻게 보면 스토리지 아키텍처에서 가장 단순한 유형의 시스템으로, 사실상 기존 스토리지 아키텍처와 동일한 아키텍처다. 직접 연결되어 있으니 장단점이 크게 변하진 않는다.</p>
<p> 앞선 DAS 아키텍처에서 어떤 문제를 해결해야 더 많은 저장 공간을 확보하고, 호스트의 문제가 스토리지에 영향을 주는 것을 막을 수 있을까? 호스트와 스토리지의 연결을 느슨하게 만들면 된다. 직접 연결을 아닌 방법으로 네트워크를 사용할 수 있는데 이런 방법이 NAS (Network Attached Storage)다.</p>
<p> <img src="https://velog.velcdn.com/images/cloud_365/post/8d3bf76b-b443-45f3-992a-a8b40f7e35cd/image.png" alt=""></p>
<ul>
<li>호스트는 네트워크를 통해 데이터를 저장, 접근, 관리한다 (네트워크 지연만큼 성능 저하, 비용 증가 발생)</li>
<li>파일 서버 혹은 파일 게이트웨이는 파일 제공을 전담하는 운영체제를 통해 LAN(Local Area Network)에 연결된다</li>
<li>NAS 장치(스토리지)는 주로 고유 IP 주소를 가지는 단일 노드로서 동작한다 (네트워크 병목이 발생할 수 있다)</li>
<li>설치 및 유지보수 관리가 쉽다</li>
</ul>
<p> 확실히 DAS에 비해 유연한 연결이 허용된다. 네트워크를 통해 스토리지를 중앙 집중 방식으로 파편화를 막을 수 있게 된다. 파일 서버가 별도로 요구되지만 이를 기반으로 LAN의 연결에 따라 저장 장치를 좀 더 손쉽게 관리가 가능해진다. 다만 네트워크 상에서 하나의 노드로 동작하면서, 호스트의 접속이 많아지면 성능 저하로 직결되는 문제가 있다. 거기에 파일 시스템으로 공유되기 때문에 단숨에 통째로 쉽게 보안 위협에 노출된다.</p>
<p> 여기서 스토리지 장치, 서버 혹은 다른 여러 장치를 LAN이 아닌 전용 네트워크로 묶어서 느슨한 연결을 구성할 수도 있는데, 이런 방식을 SAN (Storage Area Network)라고 한다. SAN의 경우 Fiber Channel Switch를 통해 연결을 진행하고, 이를 통해서 고속 데이터 네트워크를 제공한다.</p>
<p> <img src="https://velog.velcdn.com/images/cloud_365/post/618440e4-a1c1-4041-b461-54a250e86dc5/image.png" alt=""></p>
<ul>
<li>호스트는 SAN 전용 네트워크를 통해 스토리지에 접근한다</li>
<li>스토리 전용 네트워크 구성으로 네트워크 복잡도가 높고 초기 비용이 높다</li>
<li>광 스위치 뿐만 아니라 광 케이블을 통한 빠른 전송 속도 및 안정적인 시스템을 제공한다</li>
</ul>
<h1 id="파일은-어떻게-관리하는가">파일은 어떻게 관리하는가?</h1>
<p>지금까지 스토리지가 어떻게 구성되는지 아키텍처를 살펴봤다. 여기서 우린 저렇게 다수의 저장 장치를 연결하면서 호스트에서 이를 어떻게 접근하고 관리할지에 대한 의문이 생긴다. 대표적으로 DBMS와 같은 어플리케이션 서버를 사용할 텐데, 이 방법만 있진 않을 텐데 말이다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/fb845e2a-5641-457c-a16b-8b4dfc03e954/image.png" alt=""></p>
<p> 우선 각 아키텍처별로 정리하자면 어플리케이션 서버 내지 파일 서버를 통해 저장되어 있는 데이터를 관리하게 된다. 각 서버는 다양한 서비스를 통해 사용할 수 있을 것이니 패스하도록 하겠다. 결과적으로 우리가 알 수 있는 것은 앞선 아키텍처들처럼 각 물리적으로 스토리지를 구성하더라도, 이를 관리하기 위해 별도의 서버 구성이 필요하다는 점이다.</p>
<h1 id="데이터-스토리지-유형">데이터 스토리지 유형</h1>
<p> 별도의 서버 구성이 필요하다면 그 서버들은 어떤 유형으로 나뉠지 한번 알아보자. 데이터를 어떤 형태로 저장하는지에 따라 분류하게 되는데, File Storage / Block Storage / Object Storage로 구분할 수 있다.</p>
<h2 id="파일-스토리지-file-storage">파일 스토리지 (File Storage)</h2>
<p> 파일 스토리지는 우리가 흔히 볼 수 있는 데이터 스토리지 유형으로 폴더가 있고, 폴더 내 파일이 있는 형태로 관리된다. 흔히 우리가 사용하는 PC에서 활용되는 방식으로 가장 익숙한 방식이라고 할 수 있다. 다만 디렉토리의 Depth가 깊을수록 파일의 경로가 길어지고, 디렉토리나 폴더의 계층 구조를 지키는 과정에서 데이터 관리가 어려워질 수 있다.</p>
<ul>
<li>사용자 친화적인 인터페이스를 제공</li>
<li>확장하기 쉬운 아카이브</li>
<li>데이터 보호에 강한 면모 (사용 기간이 길어 그에 따른 표준화 기술 및 프로토콜이 많다)</li>
<li>파일별로 날짜, 시간 등 메타 데이터가 존재함</li>
<li>주로 NAS (Network Attached Storage)에서 활용</li>
</ul>
<h2 id="블록-스토리지-block-storage">블록 스토리지 (Block Storage)</h2>
<p> 블록 스토리지는 데이터를 고정된 크기의 블록으로 구분하여 단위별로 저장하는 방식이다. 블록 크기는 설정에 따라 킬로 바이트 단위에서 메가바이트까지도 이른다(일반적으로 256KB ~ 4MB 정도). 블록에 각각 고유한 주소 내지 번호를 부여하며, 이를 기반으로 테이블에 로깅해서 추적하게 된다. 이를 통해 낭비되는 공간 없이 저장할 수 있고, 테이블을 통해 신속하게 검색이 가능하다.</p>
<ul>
<li>테이블을 통해 빠른 검색을 제공하고, 블럭 기반의 다중 경로를 통해 성능 향상을 꾀할 수 있다</li>
<li>블록 단위를 사용해 현재 데이터를 모두 제거하지 않고, 일부에 대해서만 변경이 가능하다</li>
<li>장애 발생에 따라 빠르게 백업 데이터를 찾고 복구할 수 있다</li>
<li>다른 형태의 스토리지에 비해 복잡한 구조를 가진다</li>
<li>유지 보수 및 관리가 어려워 외부 서비스에서 더 높은 비용이 발생할 수 있다</li>
<li>블럭 식별자를 사용하므로 최대한 적은 양의 메타 데이터를 저장하며, 이를 통해 전송 중 오버헤드를 최소화한다</li>
</ul>
<h2 id="오브젝트-스토리지-object-storage">오브젝트 스토리지 (Object Storage)</h2>
<p> 오브젝트 스토리지는 오브젝트라 하는 단위로 데이터를 각각 나누고 저장소에 저장하는 방식이다. 오브젝트는 키와 문서, 이미지 또는 데이터, 관련된 메타 데이터로 구성된다. 계층 구조가 아닌 Flat한 구조를 가지며, 고유 식별자를 통해 스토리지 내에서 객체의 주소를 찾아낼 수 있다. 구조상 데이터 검색 및 읽기 속도가 빠르나, 메타 데이터로 인해 입출력 오버헤드를 가지게 된다.</p>
<ul>
<li>각 오브젝트는 하나의 모듈처럼 동작하며, 인가 / 보안 등 정보가 담긴 메타 데이터를 통해 독립 저장소 역할을 같이 수행한다</li>
<li>대부분 HTTP와 REST API를 지원한다</li>
<li>비정형 데이터를 관리하기 용이하다</li>
<li>Flat한 구조를 통해 거의 무한한 확장성을 가진다</li>
<li>각 오브젝트가 독립적인 모듈로 동작하는 점과 고유 식별자를 기반으로 여러 위치 분산하여 저장소를 운영할 수 있다</li>
<li>데이터 수정이 불가능하며, 필요시 오브젝트 전체를 수정해야 한다</li>
<li>전통적인 데이터베이스(RDB)와 잘 연동되지 않는다</li>
</ul>
<h1 id="마무리">마무리</h1>
<p> NAS라던가 오브젝트 스토리지라던가 이름은 많이 들어봤지만 정확히 어떤 용도로 사용되는지 몰랐는데, 이번 기회를 통해 공부해 보게 되었다. NAS에서 오브젝트 스토리지가 가능할까? 하는 궁금증이 있었는데, 공부하면서 결국 NAS는 스토리지 아키텍처의 한 방법이고 오브젝트 스토리지는 데이터 스토리지의 한 유형으로 불가능한 방법은 아니었다. 다만 데이터의 유형이나 사용 방식에 따라 좀 더 적합한 방법이 나뉘게 될 테니 좀 더 고민을 하게 될 것 같다. </p>
<h1 id="참고한-사이트">참고한 사이트</h1>
<p><a href="https://www.purestorage.com/kr/knowledge/what-is-data-storage.html">https://www.purestorage.com/kr/knowledge/what-is-data-storage.html</a>
<a href="https://blog.naver.com/suin2_91/223030481792">https://blog.naver.com/suin2_91/223030481792</a>
<a href="https://developers-haven.tistory.com/82">https://developers-haven.tistory.com/82</a>
<a href="https://aws.amazon.com/ko/compare/the-difference-between-block-file-object-storage/">https://aws.amazon.com/ko/compare/the-difference-between-block-file-object-storage/</a></p>
<blockquote>
<p>블로그 이전을 위해 Tistory와 병행하여 작성을 진행하고 있습니다.
<a href="https://is42.tistory.com/">https://is42.tistory.com/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[메달리온 아키텍처(Medallion Architecture)]]></title>
            <link>https://velog.io/@cloud_365/%EB%A9%94%EB%8B%AC%EB%A6%AC%EC%98%A8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98Medallion-Architecture</link>
            <guid>https://velog.io/@cloud_365/%EB%A9%94%EB%8B%AC%EB%A6%AC%EC%98%A8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98Medallion-Architecture</guid>
            <pubDate>Sat, 13 Sep 2025 19:27:40 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>데이터 엔지니어링에서 사이드 프로젝트를 진행하려고 할 때, 레이크 하우스까지 생각했으나 데이터를 분류하는 기준을 이해하지 못했다. Apache Iceberg를 활용해보고 싶은데, 기술 도메인을 공부하면서 계층별로 데이터를 구분하는데 어디서 나온 개념인지 몰라 공부해보았다.</p>
<h1 id="메달리온-아키텍처란">메달리온 아키텍처란?</h1>
<p>레이크하우스에 논리적으로 데이터를 정리하는데 사용되는 데이터 설계 패턴</p>
<p>데이터가 각 레이어를 통과하는 동안 데이터의 구조와 품질을 증분적, 점진적으로 개선하는 것을 목표로 한다. 다른 이름으로 &#39;멀티 홉&#39; 아키텍처라고도 한다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/ee0977a0-ce24-4088-a514-d41247749647/image.png" alt="">
사진 출처 : databricks[메달리온 아키텍처]</p>
<h2 id="메달리온-3계층-구조">메달리온 3계층 구조</h2>
<h3 id="bronze-레이어-원시-데이터-수집">Bronze 레이어 (원시 데이터 수집)</h3>
<p>외부 소스 시스템의 데이터가 위치하는 레이어.</p>
<p>소스 시스템 테이블 구조에 그대로 대응하는 곳이며, 초기 수신 역할을 한다. 그렇기에 소스 시스템 테이블에 로드 날짜/시간, 프로세스 ID 등 캡처에 대한 메타데이터 컬럼이 추가된다.</p>
<p>최소한의 데이터 유효성 검사가 진행될 수도 있고, 삭제된 데이터에 대한 스키마 변경 보호를 위해 대부분의 필드를 문자열 및 이진 파일 저장하는 것이 추천됨</p>
<p>데이터를 빠르게 캡처하는 것이 목적인 레이어로, 소스의 과거 아카이브, 데이터 계보, 필요하다면 재처리 기능까지 제공하는 것이 핵심이 된다.</p>
<h3 id="silver-레이어-데이터-정리-및-유효성-검사">Silver 레이어 (데이터 정리 및 유효성 검사)</h3>
<p>브론즈 레이어 또는 실버 레이어 기반으로 데이터를 읽고 실버 테이블에 데이터를 쓴다.</p>
<p>주요 단체, 개념, 트랜잭션에 대한 엔터프라이즈 뷰를 제공한다. 다른 소스의 데이터를 엔터프라이즈 뷰를 가져오고, 즉석 보고를 위해 서비스 분석, 고급 분석, ML을 지원한다.</p>
<blockquote>
<p>분석가, 엔지니어, 과학자에게 소스 역할과 동시에 골드 레이어에 있는 비즈니스에 문제에 답할 수 있도록 돕는다.</p>
</blockquote>
<p>레이크하우스 데이터 엔지니어링 패러다임에서 ELT를 따르므로, 실버 레이어를 로드하는 동한 최소한 또는 적당한 수준의 변환과 데이터 정리 규칙만 적용한다.</p>
<blockquote>
<p>대부분의 복잡한 변환과 비즈니스 규칙은 골드 레이어를 로드하는 동안 시행한다. 이는 데이터 레이크의 수집 및 전달 속도 향상을 위함</p>
</blockquote>
<p>실버 레이어는 대개 데이터 모델처럼 3차 정규형이 많음</p>
<h3 id="gold-레이어-비즈니스-레벨-테이블">Gold 레이어 (비즈니스 레벨 테이블)</h3>
<p>골드 레이어에 위치한 데이터는 보통 바로 사용할 수 있는 프로젝트별 데이터베이스에 정리된다. 주로 보고용으로 사용하고, JOIN 개수가 적고, 더욱 비정규화된 읽기 최적화 데이터 모델을 사용한다.</p>
<p>데이터 변환과 데이터 품질 규칙의 마지막 레이어가 적용되며, 주로 스타 스키마 기반 데이터 모델이나 데이터 마트가 들어가는 경우가 많다.</p>
<h1 id="해당-개념에-대한-궁금증">해당 개념에 대한 궁금증</h1>
<h2 id="gold-레이어의-데이터는-어디에-위치해야-하는가">Gold 레이어의 데이터는 어디에 위치해야 하는가?</h2>
<p>Gold 레이어의 데이터는 비즈니스에 직접 사용되는 데이터인데, 객체 저장소에 저장해서 읽어서 쓸까? 아니면 WAS 내의 RDB나 NoSQL 등 비즈니스에서 직접 활용할 수 있도록 DB에 직접 저장하는걸까?
결론부터 말하자면 객체 저장소에 위치하는 것이 아키텍처의 방식
Gold 레이어도 결국 메달리온 아키텍처의 포함되어 있는 계층으로, 정제되어 완성된 데이터가 레이어 내에 위치
그래서 비즈니스 상에서 빠른 접근을 위해서 별도의 데이터 저장이 필요한 경우 레이어에서 데이터의 사본을 가져와 저장한다</p>
<h2 id="bronze-레이어의-데이터의-삭제-주기는-어떻게-설정하면-좋을까">Bronze 레이어의 데이터의 삭제 주기는 어떻게 설정하면 좋을까?</h2>
<p>아키텍처에서 해당 계층에서 재처리, 품질 문제로 인한 원본 재참조(데이터 계보) 등 다시 원본 데이터를 참조하는 작업이 발생하기 때문에 원칙적으로 저장하는 것이 맞음
단, 저장 비용의 문제가 있기 때문에 설계 및 구현에 따라 별도의 위치로 이동하거나 삭제 주기를 설정하고 삭제하게 됨
결국은 Bronze 레이어에서 재처리 등 기능 수행과 관련 있기에 복구 능력과 저장 비용 간에 Trade-off가 발생하는 것이 됨
설계 및 구현에 따라 적정한 시기(Snapshot이나 특정 시간 단위)에 더 싼 저장소로 옮기거나 삭제하는 과정이 필요함</p>
<h2 id="참고-및-출처">참고 및 출처</h2>
<p>databricks[메달리온 아키텍처]</p>
<p><a href="https://www.databricks.com/kr/glossary/medallion-architecture">https://www.databricks.com/kr/glossary/medallion-architecture</a></p>
<p>microsoft Azure Databricks</p>
<p><a href="https://learn.microsoft.com/ko-kr/azure/databricks/lakehouse/medallion">https://learn.microsoft.com/ko-kr/azure/databricks/lakehouse/medallion</a></p>
<blockquote>
<p>블로그 이전을 위해 Tistory와 병행하여 작성을 진행하고 있습니다.
<a href="https://is42.tistory.com/">https://is42.tistory.com/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 및 시스템 설계 - (6) 빅데이터 아키텍처]]></title>
            <link>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-6-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</link>
            <guid>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-6-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</guid>
            <pubDate>Sun, 06 Jul 2025 06:27:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 시리즈는 <a href="https://www.udemy.com/course/software-architecture-design-large-scale-systems/?couponCode=KRLETSLEARNNOW">【한글자막】 소프트웨어 아키텍처 및 대규모 시스템 설계 </a> 강의를 보고 정리한 내용입니다.</p>
</blockquote>
<h1 id="빅데이터-아키텍처">빅데이터 아키텍처</h1>
<h2 id="빅데이터">빅데이터?</h2>
<ul>
<li>매우 크거나, 아주 복잡하거나, 매우 빠른 속도로 시스템에 들어와 기존 어플리케이션의 처리 속도를 뛰어 넘는 데이터셋</li>
<li>다음의 특징을 가짐<ol>
<li>Volume<ul>
<li>처리, 저장, 분석해야 하는 데이터의 양</li>
<li>하루에 테라바이트나 페타바이트 이상을 처리함</li>
</ul>
</li>
<li>Variety<ul>
<li>다양한 소스의 비정형 데이터를 수집하여 처리해야 함</li>
<li>모든 데이터를 처리하고 결합하여 새로운 인사이트를 얻는데 활용됨</li>
</ul>
</li>
<li>Velocity<ul>
<li>시스템의 규모가 크거나 이벤트 빈도가 잦아 빠른 속도로 데이터가 들어오게 됨</li>
</ul>
</li>
</ol>
</li>
<li>빅데이터 처리의 목적<ul>
<li>경쟁사보다 강력한 서비스 경쟁력</li>
<li>데이터 시각과, 쿼리 능력, 예측 분석을 통한 인사이트</li>
</ul>
</li>
<li>결과적으로 다양한 비정형 데이터 스트림을 인사이트, 시각화, 예측 등의 결과로 처리하는 것이 빅데이터 처리의 목적이 됨</li>
</ul>
<h2 id="빅데이터-처리-패턴">빅데이터 처리 패턴</h2>
<ol>
<li><p>일괄 처리 방식</p>
<ul>
<li><p>들어오는 데이터를 주로 분산 데이터베이스나 분산 파일 시스템에 저장함</p>
<ul>
<li>이는 절대 수정하지 않고 추가하기만 함</li>
</ul>
</li>
<li><p>일정 기간 혹은 일정량을 모아 특정 시간, 갯수에 처리를 수행함</p>
</li>
<li><p>마지막으로 처리한 이후 데이터들에 대해 처리하고, 이를 최신 View로 저장함</p>
<ul>
<li>View는 구조화된 데이터베이스나 인덱스 데이터베이스를 활용하여 쉽게 결과를 얻을 수 있게 함</li>
<li>전체적인 데이터세트에 대한 지식이 view에 반영되어 있어야 함</li>
</ul>
</li>
<li><p>최신에 들어온 데이터만 처리하거나 전체를 처리하는 방식으로 인사이트를 도출함</p>
</li>
<li><p>대표적으로 추천 시스템, 검색 엔진 등에 활용할 수 있음</p>
</li>
<li><p>장점</p>
<ul>
<li>지연 시간 걱정이 없어 구현이 쉬움</li>
<li>분석한 데이터 세트를 저장해두므로 고가용성을 제공함</li>
<li>일괄 처리를 통해 효율성이 높음</li>
<li>인적 오류에 대한 내결함성이 높음 ⇒ 원본 데이터를 별도로 저장하기 떄문</li>
<li>큰 데이터셋에 대한 깊고 복잡한 분석 가능</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li><p>데이터 유입부터 결과 도출까지 오랜 시간이 걸림</p>
<ul>
<li>실시간으로 빠른 대처가 불가능함</li>
</ul>
</li>
<li><p>시스템에서 이뤄진 피드백이 적용되기까지 오랜 시간이 걸림</p>
<ul>
<li><p>어느 정도 시간이 걸리는지 사용자에게 미리 공지하는 것이 필요함</p>
<p>  ex)  제품 등록 시 1 영업일 이후 반영 등등..</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>실시간 처리 방식</p>
<ul>
<li>메시지 브로커나 대기열에 배치하고, 데이터가 들어오는대로 처리함</li>
<li>처리 완료된 데이터는 데이터베이스에 반영되어 실시간 시각화와 분석 능력을 제공함</li>
<li>장점<ul>
<li>처리까지 오랜 시간을 기다릴 필요가 없음</li>
</ul>
</li>
<li>단점<ul>
<li>복잡한 분석 능력을 가지기 힘듦</li>
<li>각기 다른 시간에 발생한 데이터의 융합이나 데이터 이력 분석이 불가능함</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="람다-아키텍처">람다 아키텍처</h2>
<ul>
<li>일괄 처리 능력과 실시간 처리 능력을 동시에 요구할 수 있음<ul>
<li>승차 관리 시스템, 이상 감지 시스템 등등..</li>
</ul>
</li>
<li>람다 아키텍처는 일괄 처리의 내결함성, 통합적 데이터 분석과 실시간 처리의 짧은 대기 시간 사이에서 균형을 찾은 아키텍처</li>
</ul>
<h3 id="구성요소">구성요소</h3>
<ul>
<li>데이터는 Batch Layer와 Speed Layer에 동일하게 입력됨</li>
</ul>
<ol>
<li>Batch Layer<ul>
<li>데이터 세트를 관리하며 기록 시스템으로 사용됨<ul>
<li>일괄 처리와 동일하게 수정이 불가능하고, 새로운 데이터를 추가할 수 있음</li>
</ul>
</li>
<li>배치 뷰를 사전 계산함<ul>
<li>데이터를 일괄 처리해서 완료되면 읽기 전용 데이터베이스로 따로 저장함</li>
<li>결과는 보통 이전 처리 작업을 덮어씀</li>
</ul>
</li>
<li>완벽한 정확성을 목적으로 하며, 전체 데이터셋에 대해 동작함</li>
</ul>
</li>
<li>Speed Layer<ul>
<li>실시간 처리 전략을 사용함 - 메시지 브로커를 이용한 실시간 분석</li>
<li>짧은 대기 시간을 가지지만 종합적인 뷰나 복잡한 데이터 수정을 진행하지 않음</li>
<li>Batch Layer의 이전 결과와 현재 계산중인 결과 사이의 간극을 메우는 역할을 함</li>
</ul>
</li>
<li>Serving Layer<ul>
<li>쿼리에 반응하여 결과를 도출해내는 레이어</li>
<li>Batch Layer를 통한 과거 데이터와 Speed Layer를 통한 최신 데이터를 모두 내보낼 수 있음</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 및 시스템 설계 - (5) 소프트웨어 아키텍처 패턴]]></title>
            <link>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-5-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-5-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sun, 06 Jul 2025 06:26:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 시리즈는 <a href="https://www.udemy.com/course/software-architecture-design-large-scale-systems/?couponCode=KRLETSLEARNNOW">【한글자막】 소프트웨어 아키텍처 및 대규모 시스템 설계 </a> 강의를 보고 정리한 내용입니다.</p>
</blockquote>
<h1 id="소프트웨어-아키텍처-패턴---멀티-티어-아키텍처">소프트웨어 아키텍처 패턴 - 멀티 티어 아키텍처</h1>
<ul>
<li>시스템 계층을 논리적, 물리적으로 나눈 것<ul>
<li>논리적 분리는 각 계층의 책임의 범위를 결정함</li>
<li>물리적 분리는 각 팀마다 시스템을 배포 또는 업그레이드, 확장하는 영역</li>
</ul>
</li>
<li>멀티 레이어 아키텍처와 다른 개념으로 하나의 어플리케이션에 대한 멀티 레이어 아키텍처와 달리 각 계층마다 별도의 기기에 실행됨</li>
<li>물리적 계층을 분리하면서 배포와 업그레이드가 분리되어 진행되는 이점이 있으나 설계가 단순해지는 제약 사항이 존재함<ol>
<li>인접한 계층과 짝을 이루는 어플리케이션들은 클라이언트 - 서버 형식으로 동작함</li>
<li>계층을 뛰어넘어서 통신을 할 순 없음</li>
</ol>
<ul>
<li>제약 사항을 통해 각 계층 간 결합이 느슨해짐</li>
</ul>
</li>
</ul>
<h2 id="변형-패턴---3-tier-architecture">변형 패턴 - 3 Tier Architecture</h2>
<ul>
<li>클라이언트 - 서버 구조의 웹 기반 서비스에 주로 사용되는 보편된 아키텍처 패턴</li>
<li>서비스를 다음의 3가지 계층으로 구분함<ul>
<li>Presentation Tier<ul>
<li>상위 계층으로 사용자 인터페이스가 포함됨</li>
<li>사용자에게 정보를 나타내고, 그래픽을 통해 입력값을 받는 것을 목표로 함</li>
<li>대표적으로 웹 페이지, 모바일 App이나 데스크탑 어플리케이션이 있음</li>
<li>비즈니스 로직을 포함하지 않으며 이는 브라우저에서 실행되는 코드가 사용자에게 직접적으로 나타나기 때문임</li>
</ul>
</li>
<li>Appliocation Tier<ul>
<li>Business Tier 혹은 Logic Tier로 불림</li>
<li>모든 기능을 담당하는 계층으로 기능 요구 사항을 갖춘 계층</li>
<li>받은 데이터를 처리하고, 관련 있는 비즈니스 로직에 적용하는 역할</li>
</ul>
</li>
<li>Data Tier<ul>
<li>사용자 데이터나 비즈니스 데이터를 저장하고 유지하는 역할</li>
<li>파일 시스템의 파일이나 데이터베이스가 포함됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="많이-사용되는-이유">많이 사용되는 이유</h3>
<ul>
<li>다양한 종류의 사용 사례에 적합함 - 대표적으로 웹 사이트, 웹 서비스</li>
<li>수평적인 확장이 쉬움<ul>
<li>로드 밸런서를 두고 Application Tier의 인스턴스를 확장</li>
<li>데이터베이스를 복제하거나 분산하여 확장</li>
</ul>
</li>
<li>Application Tier에 모든 로직이 집중되므로 별도의 통합 작업이 불필요함</li>
<li>한계점 : 비즈니스 로직 계층이 단일 계층 구조로 단일 런타임 단위의 단일 코드로 집약됨<ul>
<li>과도하게 집약된 메모리를 소모함 ⇒ 속도가 느려짐(가비지 콜렉션  등)</li>
<li>코드가 크고 복잡해짐에 따라 개발 속도가 느려짐<ul>
<li>논리적으로 어플리케이션을 개별 모듈로 나눠 해결할 수 있으나 이는 모듈별로 강하게 결합하게 됨</li>
<li>어플리케이션 전체가 배포되어야 각 세부 기능들이 배포될 수 있음</li>
</ul>
</li>
</ul>
</li>
<li>코드 베이스 규모가 비교적 작고 복잡하지 않은 기업에 적합한 선택지</li>
</ul>
<h2 id="변형-패턴">변형 패턴</h2>
<h3 id="2계층-아키텍처">2계층 아키텍처</h3>
<ul>
<li>Presentation + Business Tier와 Data Tier로 분리하는 방법</li>
<li>중간 단계인 Logic Tier가 제거되므로 오버헤드가 줄고, 보다 빠른 네이티브 앱 경험을 제공할 수 있음</li>
<li>데스크탑, 모바일의 에디터가 대표적으로 해당됨</li>
</ul>
<h3 id="4계층-아키텍처">4계층 아키텍처</h3>
<ul>
<li>3계층에서 Presentation Tier와 Application Tier 사이에 하나의 계층을 추가하는 방법</li>
<li>예를 들어 API Gateway 계층을 만들어 보안, 캐싱, API, 포맷 변경 등의 기능을 수행할 수 있음</li>
</ul>
<h3 id="4n계층-아키텍처">4+n계층 아키텍처</h3>
<ul>
<li>보기 힘든 패턴</li>
<li>계층이 늘수록 성능이 이점이 무조건적으로 늘지 않고, 계층 구분에 따른 오버헤드가 발생함<ul>
<li>제약 사항을 지키지 못하면 각 계층이 강하게 결합되는 문제도 존재함</li>
<li>이런 경우 더 작게 쪼개는 아키텍처를 사용함</li>
</ul>
</li>
</ul>
<h1 id="소프트웨어-아키텍처-패턴---마이크로서비스-아키텍처">소프트웨어 아키텍처 패턴 - 마이크로서비스 아키텍처</h1>
<h2 id="마이크로서비스-아키텍처">마이크로서비스 아키텍처</h2>
<ul>
<li><p>비즈니스 로직이 연결되어 있고, 독립적으로 배포된 서비스로 조직함</p>
</li>
<li><p>3계층 아키텍처는 Logic Tier에 모든 비즈니스 로직이 집중되어 Monolithic 아키텍처라고도 함</p>
<ul>
<li><p>코드 베이스가 커지고 복잡해지면 문제 해결이 어려워지고, 새로운 기능 추가가 힘듦</p>
</li>
<li><p>조직적 확장성에 문제가 생기게 됨</p>
<p>⇒ 마이크로서비스 아키텍처로의 확장</p>
</li>
</ul>
</li>
<li><p>작은 서비스를 각 팀에서 담당하므로 책임 범위가 좁음</p>
</li>
<li><p>장점</p>
<ul>
<li>더 작은 코드베이스<ul>
<li>개발 자체가 쉽고 빠름</li>
<li>코드 빌드와 테스트가 빠름</li>
<li>문제 해결 및 기능 추가가 쉬움</li>
</ul>
</li>
<li>인스턴스를 분리하면서 CPU 부담과 메모리 소모가 덜하기 때문에 하드웨어 요구치가 낮아짐<ul>
<li>성능 낮은 하드웨어를 통한 확장도 가능함</li>
</ul>
</li>
<li>조직적 확장성이 뛰어남<ul>
<li>개별적으로 개발, 유지, 배포가 이뤄지므로 처리량이 높아짐</li>
<li>각 팀은 자율적으로 원하는 언어, 기술, 스케줄을 결정할 수 있음</li>
</ul>
</li>
<li>결함 격리 형태로 더 높은 보안성 제공<ul>
<li>서비스 문제 발생 시 모놀리식에 비해 더 쉽게 분리하여 해결 가능함</li>
</ul>
</li>
</ul>
</li>
<li><p>한계점</p>
<ul>
<li>마이크로서비스 아키텍처에 따른 이점은 쉽게 얻기 힘듦<ul>
<li>단순히 코드를 분할하는 것 이외에도 추가적인 지식이 필요함<ul>
<li>제대로 알지 못할 경우 Big Ball of Mud가 됨</li>
</ul>
</li>
</ul>
</li>
<li>상당한 오버헤드량에 따른 문제 해결이 필요함</li>
<li>개별 개발, 배포를 위한 완전한 조직 분리를 요구함 (모든 서비스를 논리적으로 분리)</li>
</ul>
</li>
<li><p>마이크로서비스 아키텍처의 이점을 얻기 위해 다음의 규칙들을 참고해야 함</p>
<ol>
<li>Single Responsibility Principle (단일 책임 원칙, SRP)<ul>
<li>각 서비스가 하나의 서비스, 도메인, 리소스, 액션을 책임져야 함</li>
</ul>
</li>
<li>Seperate Database Per Service (서비스 단위 데이터베이스 분리)<ul>
<li>서비스가 데이터베이스를 공유할 경우 데이터베이스 변경에 모든 서비스(팀)가 협력이 이뤄져야 함</li>
<li>데이터베이스를 분리하여 업데이트나 변경 사항을 쉽게 파악하는 이점을 가질 수 있음</li>
<li>이는 각 서비스를 완전히 독립된 환경으로 분할함 (데이터 중복 오버헤드는 감수해야 함)</li>
</ul>
</li>
</ol>
</li>
</ul>
<h1 id="소프트웨어-아키텍처-패턴---이벤트-기반-아키텍처">소프트웨어 아키텍처 패턴 - 이벤트 기반 아키텍처</h1>
<h2 id="이벤트-기반-아키텍처">이벤트 기반 아키텍처?</h2>
<ul>
<li>마이크로서비스끼리 통신할 경우 서로의 존재를 알아야 하고, 요청에 대해 응답을 기다려야 함<ul>
<li>이는 각 서비스에 대한 의존성이 발생함</li>
</ul>
</li>
<li>이벤트 기반 아키텍처는 다이렉트 메시지나 데이터를 요구하는 대신 이벤트로만 진행함<ul>
<li>이벤트는 사실이나 변경을 증명하는 역할을 함</li>
</ul>
</li>
<li>이벤트 기반 아키텍처는 다음과 같은 구성 요소를 가짐<ol>
<li>Event Emitter / Producer</li>
<li>Consumer</li>
<li>Message Broker</li>
</ol>
</li>
<li>장점<ul>
<li>의존성이 제거됨<ul>
<li>이벤트를 생산해 통신하므로 서비스는 서로의 존재를 몰라도 됨</li>
<li>모든 메시지는 전적으로 비동기로 교환됨</li>
</ul>
</li>
<li>높은 확장성<ul>
<li>새로운 기능(서비스)는 기존 채널을 구독함으로써 기존 데이터를 얻을 수 있음</li>
<li>구독 과정에서 어떠한 시스템의 변경도 요구하지 않음</li>
</ul>
</li>
<li>데이터 스트림이나 패턴 감지를 실시간으로 수행할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="event-sourcing-패턴">Event Sourcing 패턴</h2>
<ul>
<li>현재 상태를 데이터베이스에 저장하는 대신 다시 돌려볼 수 있는 이벤트를 저장할 수 있음</li>
<li>중간에 트랜잭션에 문제가 발생할 경우 해당 트랜잭션을 보상하는 트랜잭션을 추가하여 해결할 수 있음<ul>
<li>이는 사용자를 동결 시키거나 데이터베이스 레코드를 수정하지 않아도 됨</li>
</ul>
</li>
</ul>
<h2 id="cqrs-패턴">CQRS 패턴</h2>
<ul>
<li>C = Command, Q = Query, R = Responsibility, S = Segregation</li>
<li>해결하는 문제<ul>
<li>읽기와 업데이트 작업이 많은 데이터베이스 최적화 문제<ul>
<li>병행 연산을 적용하는 경우 전체적인 작업 속도가 하락함</li>
<li>동작이 집중된 작업을 최적화할 경우 다른 작업을 희생해야 함</li>
<li>CQRS 패턴은 각 작업을 각각의 데이터베이스로 나눠 별도의 서비스에 둠<ul>
<li>업데이트가 발생할 때마다 이벤트를 발생시키고, 읽기 DB는 이를 읽어 업데이트함</li>
<li>이벤트로 연결되면서 각 작업은 서로의 간섭 없이 진행할 수 있음</li>
</ul>
</li>
</ul>
</li>
<li>다중 테이블 연결 문제<ul>
<li>데이터베이스를 분리하면서 다중 테이블 대상으로 레코드 연산이 힘들어짐</li>
<li>서비스별로 보낼 경우 속도가 느리고, 서비스별로 사용하는 데이터베이스가 다를 수 있음</li>
<li>CQRS 패턴은 각 데이터베이스가 변경될 때마다 채널로 이벤트를 생성함<ul>
<li>다중 테이블 연산하는 서비스는 이를 구독하고, 변경이 발생하면 별도의 읽기 전용 데이터베이스(View)에 저장함</li>
<li>사용자 요청 시 JOIN이 이뤄진 View를 통해 결과를 전송함</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 및 시스템 설계 - (4) 데이터 스토리지]]></title>
            <link>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-4-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</link>
            <guid>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-4-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</guid>
            <pubDate>Sun, 06 Jul 2025 06:25:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 시리즈는 <a href="https://www.udemy.com/course/software-architecture-design-large-scale-systems/?couponCode=KRLETSLEARNNOW">【한글자막】 소프트웨어 아키텍처 및 대규모 시스템 설계 </a> 강의를 보고 정리한 내용입니다.</p>
</blockquote>
<h1 id="데이터-스토리지---관계형-데이터베이스">데이터 스토리지 - 관계형 데이터베이스</h1>
<ul>
<li>대규모 시스템에서 항상 스토리지 비용은 큰 문제임</li>
</ul>
<h2 id="장단점">장단점</h2>
<ul>
<li>장점<ul>
<li>복잡하고 유연한 쿼리를 사용할 수 있음</li>
<li>가격 절감 가능 (중복 레코드 관리 측면에서 적은 공간 사용)</li>
<li>추론하기 쉬움 (직관성)</li>
<li>ACID 트랜잭션</li>
</ul>
</li>
<li>단점<ul>
<li>스키마 테이블이 강요됨 (테이블 구조 변경의 어려움)</li>
<li>유지 관리 및 크기 조정에 대해 높은 비용</li>
<li>ACID를 지키기 위한 비교적 느린 성능</li>
</ul>
</li>
</ul>
<h1 id="데이터-스토리지---비관계형-데이터베이스">데이터 스토리지 - 비관계형 데이터베이스</h1>
<ul>
<li>관계형 데이터베이스의 문제를 해결하기 위해 도입<ul>
<li>관계형 데이터베이스는 일부 레코드에 대한 정보를 넣더라도 모든 레코드에 대한 관리가 필요함<ul>
<li>비관계형의 경우 기존 레코드에 영향을 주지 않고, 하나 혹은 여러 레코드에 수정이 가능함</li>
</ul>
</li>
<li>테이블 구조만을 지원함<ul>
<li>실제 프로그래밍 언어 대부분은 테이블 구조가 아닌 다양한 자료구조를 지원함</li>
<li>비관계형은 여러 자료구조를 지원하여 ORM의 필요성을 낮춤</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="trade-off">Trade-off</h2>
<ul>
<li>관계형 데이터베이스에 비해 빠른 성능에 초점을 맞춰 설계됨</li>
<li>레코드 분석 능력<ul>
<li>데이터 구조가 레코드별로 다양하기 때문에 일괄 분석 불가</li>
</ul>
</li>
<li>기존 방식으로 테이블 연결 불가<ul>
<li>데이터 구조가 바뀌면서 기존의 방식이 제한됨</li>
</ul>
</li>
<li>ACID 지원 불가</li>
</ul>
<h2 id="비관계형-데이터베이스의-유형">비관계형 데이터베이스의 유형</h2>
<ol>
<li>Key / Value<ul>
<li>대규모 해시 테이블이나 각 키가 가진 유형에 대한 Dictionary와 같은 구조</li>
<li>캐싱 페이지, 카운터 등 지연 없이 데이터를 읽는 경우 효과적인 구조</li>
</ul>
</li>
<li>Document<ul>
<li>문서 자체의 작은 규모의 구조를 갖춘 문서 집합을 저장</li>
<li>좀 더 프로그래밍 언어에 쉽게 매핑됨</li>
</ul>
</li>
<li>Graph<ul>
<li>링크를 걸고 다중으로 분석함</li>
<li>레코드를 읽고 분석하는데 뛰어남</li>
<li>이상 거래 분석을 위한 동일 주소의 다중 트랜잭션 분석이나 추천 엔진에 주로 사용됨</li>
</ul>
</li>
</ol>
<h1 id="데이터베이스의-가용성-확장성-성능을-개선하는-기술">데이터베이스의 가용성, 확장성, 성능을 개선하는 기술</h1>
<h2 id="인덱싱">인덱싱</h2>
<ul>
<li>테이블 크기가 클수록 조건에 맞는 탐색에 걸리는 시간이 큼</li>
<li>스캔과 정렬에 전체 테이블을 최소 1회는 탐색하게 됨 ⇒ 이런 작업은 병목 현상이 됨</li>
<li>인덱스는 헬퍼 테이블로 하나 혹은 여러 열에 대해 생성할 수 있음<ul>
<li>해시 맵이나 B Tree를 활용함</li>
</ul>
</li>
<li>읽기 쿼리를 증가 시키지만 추가 공간과 쓰기 쿼리의 시간을 증가 시킴</li>
<li>쿼리 속도를 높이기 위해 Document 유형의 비관계형 데이터베이스에도 사용됨</li>
</ul>
<h2 id="파티셔닝샤딩">파티셔닝/샤딩</h2>
<ul>
<li>데이터를 각각 서로 다른 데이터베이스에 분산하는 방법</li>
<li>보통은 데이터베이스의 높은 성능을 위해 별도의 컴퓨터에 데이터베이스를 분산함</li>
<li>다중 인스턴스를 통해 높은 처리량과 각기 다른 데이터에 대한 쿼리를 병렬로 정확히 처리 가능함</li>
<li>여러 데이터 베이스를 사용하면서 확장성과 성능을 만족시킬 수 있음</li>
<li>접근하려는 데이터가 있는 데이터베이스에 정확히 전달하는 것이 어렵고 오버헤드가 발생함</li>
<li>비관계형 데이터베이스는 의도적으로 레코드를 분리하므로 데이터를 분산하기 관계형보다 훨씬 쉬움</li>
</ul>
<h2 id="복제">복제</h2>
<ul>
<li>모든 데이터를 한 곳에 저장하면 데이터베이스는 단일 장애점이 되기 쉬움<ul>
<li>데이터를 다중으로 복사하여 내결함성과 높은 가용성을 보장하여 이를 해결하는 방법</li>
</ul>
</li>
<li>여러 사용자의 쿼리를 여러 데이터베이스로 분산함으로써 높은 처리량도 만족시킬 수 있음</li>
<li>레코드 생성, 수정, 삭제에 뛰어난 면을 보이나 충돌을 방지하고, 레코드의 일관성과 정확성을 보장해야 하는 면에서 구현이 어려움</li>
<li>비관계형 데이터베이스는 구현에 따라 다양하게 지원함</li>
</ul>
<h1 id="cap-이론">CAP 이론</h1>
<h2 id="cap">CAP?</h2>
<ul>
<li>C = Consistency, A = Availability, P = Partition Tolerance</li>
<li>네트워크 분할이 일어난 경우, 분산 데이터베이스는 일관성과 가용성을 모두 만족시키지 못한다는 이론<ul>
<li>네트워크 분할로 특정 데이터베이스가 고립됐을 때, 해당 데이터베이스에 요청이 발생한 경우<ul>
<li>가용성을 우선할 경우 다른 데이터베이스와 다를 수 있음을 파악하고 요청에 응답</li>
<li>일관성을 우선할 경우 해당 요청을 나중에 처리하도록 미룸</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="실제로-어떤-의미">실제로 어떤 의미?</h2>
<ul>
<li>특정 환경에서 데이터베이스를 선택하거나 정리할 경우 3가지 속성 중 하나를 포기해야 함</li>
<li>포기 유형<ul>
<li>CA - 일관성과 가용성을 가지나 분할 포용성이 없음<ul>
<li>중앙 집중식으로 배치된 하나의 데이터베이스</li>
<li>네트워크 통신을 제거하여 네트워크 분할 가능성을 없앰</li>
</ul>
</li>
<li>CP -일관성과 분할 포용성이 있으나 가용성이 없음<ul>
<li>상품 판매 사이트, 티켓팅 등</li>
</ul>
</li>
<li>AP - 가용성과 분할 포용성이 있으나 일관성이 없음<ul>
<li>좋아요와 같이 꼭 최신 데이터가 필요하진 않은 경우</li>
</ul>
</li>
</ul>
</li>
<li>포기한다고 해서 0이거나 100인 구조는 아님<ul>
<li>일관성과 가용성을 필요에 따라 비율을 조정하는 식으로 선택하게 됨</li>
<li>아키텍처 설계 관점에서 이러한 부분의 절충은 매우 중요함</li>
</ul>
</li>
</ul>
<h1 id="확장-가능한-비정형-데이터-스토리지">확장 가능한 비정형 데이터 스토리지</h1>
<h2 id="비정형-데이터란">비정형 데이터란?</h2>
<ul>
<li>Blob과 같이 특정 데이터 형태가 없는 데이터</li>
<li>주로 사용자가 업로드하는 영상, 이미지 파일 등에 해당함</li>
<li>시스템에 사용되는 데이터베이스의 Snapshot과 같은 백업을 비정형 데이터로 저장할 수 있음<ul>
<li>재해 복구, 아카이빙 등에 활용할 수 있음</li>
</ul>
</li>
<li>분석이나 AI에 활용하기 위한 데이터 포인트도 비정형 데이터임<ul>
<li>크기가 크거나 이진 데이터를 포함할 수 있음</li>
<li>테라바이트나 페타바이트 단위가 될 수 있음</li>
</ul>
</li>
</ul>
<h2 id="분산-파일-시스템">분산 파일 시스템</h2>
<ul>
<li>네트워크 상에 상호 연결된 스토리지 장치를 활용하는 방식<ul>
<li>별도의 분산 파일 시스템을 통해 복사본에 대한 일관성이나 자동 복구를 보장함</li>
<li>이진 파일을 폴더 안의 파일이 있는 트리 구조로 저장할 수 있음</li>
</ul>
</li>
<li>장점<ul>
<li>파일에 접근하는데 있어 별도의 API를 요구하지 않음</li>
<li>필요에 의해 쉽게 파일을 수정할 수 있음</li>
<li>성능을 요구하는 작업에 효율적임</li>
</ul>
</li>
<li>한계점<ul>
<li>생성 가능한 파일 수의 제한이 있음</li>
<li>웹 API로 쉽게 접근할 수 없음 - 필요하면 별도의 추상화가 요구됨</li>
</ul>
</li>
</ul>
<h2 id="객체-저장소">객체 저장소</h2>
<ul>
<li>확장 가능한 스토리지 솔루션<ul>
<li>여러 개의 컨테이너(버킷)가 있는 수평 구조</li>
<li>저장하는 객체의 갯수에 제한이 따로 없음 (예산적 제한은 존재)</li>
</ul>
</li>
<li>객체에 대한 메타데이터에 대한 저장 공간이 추가적으로 요구됨</li>
<li>제공하는 업체에 따라 스케일이 상이함<ul>
<li>예산상, 법률상, 성능상으로 제약이 따라올 수 있음</li>
<li>오픈 소스 및 타사 관리 솔루션을 통해 온프레미스 환경을 객체 저장소로 전환할 수 있음</li>
<li>클라우드와 데이터 센터를 모두 하나의 API로 통합하여 동일하게 접근 가능하게 하는 방법</li>
</ul>
</li>
<li>백그라운드에서 데이터 복제를 진행하므로 물리적 스토리지 손상에 따른 데이터 손상이 적음</li>
<li>장점<ul>
<li>비정형 데이터를 인터넷 규모로 저장할 수 있음<ul>
<li>선형으로 확장할 경우 분산 파일 시스템처럼 파일 시스템을 추가하는 방식을 사용함</li>
</ul>
</li>
<li>저장 가능한 이진 객체 개수에 제한이 없음</li>
<li>단일 크기 제한이 높음<ul>
<li>데이터베이스 백업이나 아카이빙 용도의 솔루션이 됨</li>
</ul>
</li>
<li>별도의 HTTP + REST API를 제공하여 이미지, 영상이나 웹 컨텐츠 저장에 적합함</li>
<li>저장하는 객체에 대해 버저닝을 지원함</li>
</ul>
</li>
<li>단점<ul>
<li>객체를 접근하여 수정할 수 없고, 새 버전으로 교체만 가능<ul>
<li>성능에 부정적인 영향을 끼치고, 협업 목적의 경우 데이터 추가가 불가능함</li>
</ul>
</li>
<li>객체를 읽을 때 파일 시스템과 달리 별도의 HTTP나 REST API로 접근해야 함</li>
<li>분산 파일 시스템에 비해 높은 처리량 작업에 불리함</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 및 시스템 설계 - (3) 빌딩 블록(DNS, 로드밸런스, 메시지 브로커)]]></title>
            <link>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-3-%EB%B9%8C%EB%94%A9-%EB%B8%94%EB%A1%9DDNS-%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9F%B0%EC%8A%A4-%EB%A9%94%EC%8B%9C%EC%A7%80-%EB%B8%8C%EB%A1%9C%EC%BB%A4</link>
            <guid>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-3-%EB%B9%8C%EB%94%A9-%EB%B8%94%EB%A1%9DDNS-%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9F%B0%EC%8A%A4-%EB%A9%94%EC%8B%9C%EC%A7%80-%EB%B8%8C%EB%A1%9C%EC%BB%A4</guid>
            <pubDate>Sun, 06 Jul 2025 06:24:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 시리즈는 <a href="https://www.udemy.com/course/software-architecture-design-large-scale-systems/?couponCode=KRLETSLEARNNOW">【한글자막】 소프트웨어 아키텍처 및 대규모 시스템 설계 </a> 강의를 보고 정리한 내용입니다.</p>
</blockquote>
<h1 id="빌딩-블록---dns-로드밸런싱-gslb">빌딩 블록 - DNS, 로드밸런싱, GSLB</h1>
<h2 id="로드-밸런싱">로드 밸런싱</h2>
<ul>
<li>시스템의 여러 서버에 트래픽 부하를 나누는 것</li>
<li>기본적으로 클라이언트는 여러 서버에 부하를 분산하기 위해서 각 서버의 주소를 알고 있어야 함<ul>
<li>각 요청은 이 서버의 주소와 강하게 결합됨</li>
<li>이는 클라이언트 서비스 구현 내부적으로 강한 결합이 발생하고, 이를 수정하는 건 어려운 일이 됨</li>
</ul>
</li>
<li>로드 밸런서는 여러 서버에 부하를 나누어 단일 서버에 과부하를 피하게 함<ul>
<li>대개 추가적으로 서버에 대한 추상화 기능을 제공함</li>
<li>이는 하나의 큰 서버를 이용하는 것과 같이 보임</li>
</ul>
</li>
</ul>
<h2 id="로드-밸런스로-얻을-수-있는-품질-속성">로드 밸런스로 얻을 수 있는 품질 속성</h2>
<ol>
<li>확장성<ul>
<li>부하가 증가함에 따라 인스턴스를 수평적으로 확장 가능(Scale Out)</li>
<li>부하가 감소하면 불필요한 인스턴스를 줄일 수 있음</li>
<li>부하의 기준으로 초당 응답 요청 수나 네트워크 대역폭 등이 있음</li>
</ul>
</li>
<li>고가용성<ul>
<li>로드 밸런서의 모니터링 기능을 사용하면 문제가 있는 서버에 부하를 멈출 수 있음</li>
</ul>
</li>
<li>기능(Performance), 처리량(Throughput)<ul>
<li>로드 밸런서 자체는 추가 지연 시간과 응답 소요 시간을 늘림</li>
<li>하지만 처리량에 있어서 유의미한 성과를 가지기 때문에 사용함</li>
</ul>
</li>
<li>유지보수성<ul>
<li>서버를 번갈아 가며 구동하기 때문에 특정 서버에 대해 가동을 멈추고 유지보수가 가능<ul>
<li>서비스 중단 없이 어플리케이션 버전을 조정할 수 있음</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="로드-밸런스의-종류">로드 밸런스의 종류</h2>
<ol>
<li>DNS 로드 밸런싱<ul>
<li>사용자가 DNS 서버로 쿼리를 보낼 때,단일 DNS 레코드와 단일 IP 주소를 매핑하지 않고 주소의 목록을 반환함</li>
<li>사용자는 주로 목록의 첫 번째 서버에 요청하는데, DNS에서 이 순서를 라운드 로빈으로 조정해서 보냄</li>
<li>장점<ul>
<li>싸다 (도메인 주소 하나만 있으면 됨)</li>
<li>간단하다</li>
</ul>
</li>
<li>단점<ul>
<li>서버의 헬스 체크가 되지 않음<ul>
<li>TTL이 긴 상태에서 사용자 컴퓨터에 캐싱되면 더 오랜 시간 요청이 보내지지 않을 수 있음</li>
</ul>
</li>
<li>전략이 단순한 라운드 로빈 방식임<ul>
<li>더 리소스가 많은 인스턴스를 파악할 수 없고, 부하가 발생한 서버를 발견할 수 없음</li>
</ul>
</li>
<li>클라이언트 어플리케이션에게 서버 주소 목록이 전달됨<ul>
<li>시스템 구현의 세부사항이 전달됨</li>
<li>주소가 악의적으로 활용될 수 있음(특정 서버 공격)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>하드웨어 로드 밸런싱 &amp; 소프트웨어 로드 밸런싱<ul>
<li>전용 장치에서 로드 밸런싱 실행 / 로드 밸런싱 기능을 하는 프로그램에 의한 로드 밸런싱</li>
<li>하드웨어 타입, 각 서버의 부하량, 활성화된 연결 수 등을 고려할 수 있음</li>
<li>기기 간의 추상화에도 활용 가능</li>
<li>보안, 복구, 모니터링, 로드 밸런싱 측면에서 DNS에 비해 뛰어남</li>
<li>지형적 위치가 다른 여러 개의 데이터 센터의 경우 무조건 로드 밸런서를 거치면서 한쪽의 성능을 포기해야 함</li>
</ul>
</li>
<li>GSLB (글로벌 서버 로드 밸런서)<ul>
<li>DNS 방식과 하드웨어 / 소프트웨어 방식이 결합된 방식</li>
<li>요청의 원본 IP로 사용자의 위치를 파악하고, 한편으로 각 인스턴스들을 모니터링함<ul>
<li>각기 다른 데이터 센터에 로드 밸런스를 두고 요청에 따라 가장 가까운 로드 밸런스 위치를 반환함</li>
</ul>
</li>
<li>재해 복구 상황에 강점을 보임<ul>
<li>한쪽 데이터 센터/로드 밸런서가 다운된 경우 다른 데이터 센터의 로드 밸런서 주소를 반환함</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="빌딩-블록---메시지-브로커">빌딩 블록 - 메시지 브로커</h1>
<ul>
<li>앞서 설명됐던 모든 어플리케이션 간의 통신은 양측 어플리케이션이 모두 정상이고 동시에 실행중이라는 전제가 필요했음<ul>
<li>이런 형태를 동기 의사소통이라고 함</li>
</ul>
</li>
<li>문제점 ⇒ 해결하기 위해 메시지 브로커 사용<ul>
<li>동기 의사소통은 양측이 정상인 채로 트랜잭션의 완료까지 연결을 유지해야 함<ul>
<li>만약 메시지가 커지면 시간이 오래 걸리므로 해당 조건을 만족하기 힘듦</li>
</ul>
</li>
<li>급증한 트래픽 또는 부하를 해결할 시스템 패딩이 없음<ul>
<li>동시에 많은 요청이 왔을 때, 인스턴스를 늘려도 이미 도착한 많은 요청을 처리하기 위해 오랜 시간이 걸려 이를 해결할 수 없음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="메시지-브로커">메시지 브로커</h2>
<ul>
<li>대기열 구조를 통해 발신자와 수신자 사이의 메시지를 저장함</li>
<li>외부 트래픽 감당을 위한 로드 밸런서와 달리 시스템 내부에 사용되어 밖에 노출되지 않음</li>
<li>기본적인 메시지 버퍼링 외에도 다음의 기능을 제공함<ul>
<li>메시지 라우팅</li>
<li>유효성 검사</li>
<li>로드 밸런싱</li>
</ul>
</li>
<li>자체 의사소통 API를 통해 수신자와 발신자를 분리함</li>
<li>아키텍처 빌딩 블록으로 모든 비동기 소프트웨어 아키텍처에 사용됨</li>
<li>이점<ul>
<li>메시지 버퍼링을 통해 하나의 서비스를 단계별 서비스로 나눌 수 있음</li>
<li>급등하는 트래픽에 대해 방어가 가능함<ul>
<li>프론트 엔드에서 요청된 트래픽을 임시로 저장하면서 카운팅하여 재고를 처리하고, 이를 차후에 버퍼링된 메시지를 처리하여 서비스를 제공함</li>
</ul>
</li>
<li>대개 publish/subscribe 패턴을 제공<ul>
<li>시스템을 변형하지 않고 추가적으로 서비스를 만들 수 있음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="품질-속성">품질 속성</h2>
<ul>
<li>시스템 간의 의사소통을 가능케 하여 시스템에 많은 내결함성을 제공함</li>
<li>내결함성을 바탕으로 고가용성 제공 가능</li>
<li>메시지 버퍼링을 통해 고가용성/확장성 제공 가능</li>
</ul>
<h1 id="빌딩-블록---api-게이트웨이">빌딩 블록 - API 게이트웨이</h1>
<ul>
<li>문제점<ul>
<li>단일 서비스를 여러 서비스로 확장할 경우 최초로 구성한 API에 대해 프론트 엔드는 코드를 수정하여 각각의 서비스에 대해 따로 호출해야 함</li>
<li>각 서비스에 대해 보안성, 인증, 권한 부여를 다시 구현해서 사용해야 함</li>
<li>결과적으로 중복된 과정과 오버헤드가 발생함</li>
</ul>
</li>
</ul>
<h2 id="api-게이트웨이">API 게이트웨이?</h2>
<ul>
<li>외부 API를 단순화하고 서비스 형태를 추상화함</li>
<li>프론트엔드와 백엔드 컬렉션 사이에 놓인 API 관리 서비스</li>
<li>API composition이라는 아키텍처 패턴을 따름</li>
<li>이점<ul>
<li>시스템에 적용되는 내부 변화를 완벽하고 투명하게 이용자에게 제공</li>
<li>모든 보안성, 인증 권한 부여를 단일 공간에 통합할 수 있음<ul>
<li>권한과 역할에 따라 사용자가 다른 작업을 진행할 수 있게 함</li>
<li>DoS 공격 방지를 위해 속도 제한일 실행할 수 있음</li>
</ul>
</li>
<li>시스템 성능 개선<ul>
<li>사용자가 여러 서비스에 다중 요청을 보내는 것을 방지할 수 있음 ⇒ 요청 라우팅</li>
</ul>
</li>
<li>특정 요청에 대한 응답에 대해 캐싱할 수 있음</li>
<li>각 서비스에 대해 모니터링과 경고가 가능함</li>
<li>단일 공간에서 프로토콜 변환을 진행할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="품질-속성--고려사항">품질 속성 / 고려사항</h2>
<ul>
<li>보안성, 성능, 모니터링을 통한 높은 가용성 제공</li>
<li>고려사항<ol>
<li>API Gateway에는 어떠한 비즈니스 로직도 없어야 한다<ul>
<li>API Gateway에 기능이 많아지면 초기에 단일 서비스를 분리하려 했던 문제로 회귀하게 됨</li>
</ul>
</li>
<li>API Gateway는 Single Point of Failure이 될 수 있다<ul>
<li>API 다중 게이트웨이를 배포하고 로드 밸런스 뒤에 배치함으로써 성능 측면의 문제는 쉽게 해결할 수 있음</li>
</ul>
</li>
<li>외부에서 API Gateway를 우회하는 것을 피해라<ul>
<li>우회하는 코드가 많아질수록 관리해야 하는 구간이 늘어나 초기 문제로 회귀함</li>
</ul>
</li>
</ol>
</li>
</ul>
<h1 id="cdn">CDN</h1>
<ul>
<li>문제점<ul>
<li>최종 사용자와 호스팅 서버의 물리적 거리 차이로 인해 심각한 지연이 발생함</li>
<li>오가는 모든 요청이 서로 다른 네트워크 라우터의 여러 홉을 지나야 함</li>
<li>서비스를 복사해 여러 데이터 센터에 복사하면 비즈니스 로직의 속도를 개선할 수 있으나 사용자에게 더 빠른 로딩을 제공하기 위해서는 정적 콘텐츠의 개선이 필요함</li>
</ul>
</li>
</ul>
<h2 id="cdn-1">CDN?</h2>
<ul>
<li>최종 사용자에게 컨텐츠 전송 속도 상승을 목적으로 전략적인 장소에 위치 시킨 세계적으로 분산된 서버 네트워크</li>
<li>종종 엣지 서버로 언급되는 다른 상호 접속 위치에 자리한 서버에 웹 사이트 컨텐츠를 캐싱하는 방식</li>
<li>이미지, 텍스트, CSS, JS 파일, 비디오 스트림과 같은 정보를 전송하는데 사용함</li>
<li>이점<ul>
<li>빠른 페이지 로딩으로 성능 개선</li>
<li>CDN의 분산적 성격으로 고가용성 제공</li>
<li>시스템 보안성을 개선하고 DoS 공격 방어를 도움</li>
</ul>
</li>
</ul>
<h2 id="컨텐츠-캐싱-관련-전략">컨텐츠 캐싱 관련 전략</h2>
<ol>
<li>Pull 전략<ul>
<li>캐싱하려는 웹사이트와 캐시를 무효화 하는 횟수를 전달하는 방법</li>
<li>만료된 에셋에 대한 요청의 경우 서버에게 버전을 확인하고 버전이 바뀌지 않았다면 만료 기간만 다시 설정하여 사용자에게 제공함</li>
<li>유지 관련을 많이 설정하지 않아도 됨</li>
<li>에셋의 최초 사용자는 지연이 길다는 단점이 있음</li>
</ul>
</li>
<li>Push 전략<ul>
<li>서버에서 컨텐츠를 CDN으로 전달하고, 업데이트가 발생할 경우 수동으로 서버에 해당 버전을 전달하는 방법</li>
<li>콘텐츠가 자주 변경되지 않는 경우 한 번의 전송으로 고가용성을 유지할 수 있음</li>
<li>CDN에서 지속적으로 캐싱되어 있기 때문에 고가용성에 좋음</li>
<li>사용자가 예전 버전에 고착화된다는 단점이 있음</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 및 시스템 설계 - (2) 인터페이스]]></title>
            <link>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-2-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-2-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sun, 06 Jul 2025 06:23:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 시리즈는 <a href="https://www.udemy.com/course/software-architecture-design-large-scale-systems/?couponCode=KRLETSLEARNNOW">【한글자막】 소프트웨어 아키텍처 및 대규모 시스템 설계 </a> 강의를 보고 정리한 내용입니다.</p>
</blockquote>
<h1 id="api-설계-개요">API 설계 개요</h1>
<h2 id="api란">API란?</h2>
<ul>
<li>시스템을 블랙 박스라고 했을 때, 동작 이외에도 인터페이스를 만들게 됨<ul>
<li>클라이언트나 다른 엔지니어와 협의를 통해 만든 인터페이스를 통해 호출됨</li>
<li>이 인터페이스를 Application Programming Interface라고 함</li>
</ul>
</li>
</ul>
<h3 id="api-구별">API 구별</h3>
<ul>
<li>개방형 API<ul>
<li>일반 대중에게 공개된 API</li>
<li>회원 가입을 통해 API 접근 권한이나 사용자 파악을 하게 됨</li>
</ul>
</li>
<li>Private API<ul>
<li>사내에서 접근하고 활용하는 API</li>
<li>계약된 대상에게만 공개되는 차이가 있음</li>
</ul>
</li>
<li>파트너 API</li>
</ul>
<h2 id="잘-정의된-api">잘 정의된 API</h2>
<ul>
<li>잘 정의된 API는 클라이언트가 우리 어플리케이션을 잘 몰라도 쉽게 활용할 수 있게 함</li>
<li>API를 미리 잘 정의해두면 클라이언트는 모든 기능 구현을 기다리지 않아도 됨</li>
<li>잘 정의하면 시스템의 내부와 아키텍처를 쉽게 설계할 수 있음<ul>
<li>API라는 엔드포인트를 미리 파악해두고 설계하기 때문</li>
</ul>
</li>
</ul>
<h2 id="best-practices-and-patterns">Best Practices and Patterns</h2>
<ol>
<li>Complete Encapsulation<ul>
<li>내부 설계와 구현으로부터 완전히 구별된 채 설계하고 구현한다</li>
<li>시스템을 사용하려는 개발자와 API가 관계 없도록 설계한다<ul>
<li>사용자가 지나치게 비즈니스 로직이나 구현 방식에 대해 알게 될 경우 API이 상실됨</li>
<li>사용자의 계약과 별개로 내부적으로 수정할 수 있도록 해야함</li>
</ul>
</li>
</ul>
</li>
<li>Easy to Use<ul>
<li>사용하기 쉽고, 이해하기 쉽고, 우연히 또는 의도적으로 오용할 수 없어야 한다<ul>
<li>데이터를 얻거나 작업하는 방법을 하나만 만든다</li>
<li>작업 및 자원에 대해 구체적인 이름 부여한다</li>
<li>사용자에게 필요한 정보만 공개한다</li>
<li>모든 API에 있어서 모든 항목을 일정하게 유지한다</li>
</ul>
</li>
</ul>
</li>
<li>Keeping the Operations Idempotent<ul>
<li>작업이 최대한 멱등성을 지니도록 노력해야 한다</li>
</ul>
</li>
<li>API Pagination<ul>
<li>페이징 기능이 없다면 큰 결과값을 가져다 주기엔 시스템 다운이 발생할 수 있음</li>
<li>오프셋을 통해 적당한 세그먼트 내에서 값을 얻을 수 있도록 해야 함</li>
</ul>
</li>
<li>Asynchronous Operations<ul>
<li>작업을 오래 기다려야 하는 큰 작업은 비동기 동작을 통해 작동할 수 있도록 해야 함</li>
</ul>
</li>
<li>Versioning<ul>
<li>API의 변경이 없으면 제일이지만 모든 미래를 예측할 수 없으므로 변경에 대해 기록해야 함</li>
<li>버저닝을 통해 클라이언트와 소통하는 과정을 이뤄내야 한다</li>
</ul>
</li>
</ol>
<h1 id="rpc">RPC</h1>
<h2 id="rpc란">RPC란</h2>
<ul>
<li>원격 서버의 서브 루틴을 실행하는 클라이언트의 기능<ul>
<li>Interface Description Language, 서버 스텁, 클라이언트 스텁으로 구성</li>
</ul>
</li>
<li>일반 메서드 호출과 같이 사용되는데 이를 위치 투명성이라고 함</li>
<li>RPC 프레임 워크는 다양한 프로그래밍 언어를 사용해도 해당 RPC를 구현한 언어를 통해 데이터를 주고 받을 수 있음<ul>
<li>데이터를 담는 각 클래스는 서버와 클라이언트에 구현되는데 이를 DTO라고 함</li>
<li>통신 시 클라이언트와 서버는 이를 직렬화 또는 마샬링을 통해 데이터를 사용할 수 있게 가공함</li>
</ul>
</li>
</ul>
<h2 id="rpc-장점">RPC 장점</h2>
<ul>
<li>객체 메소드를 통해 손쉽게 통신 가능</li>
<li>클라이언트와 서버 간에 통신하는 과정은 개발자에 의해 추상화됨</li>
<li>서버와의 통신 실패가 단순하게 오류 또는 에러로 설명됨</li>
</ul>
<h2 id="rpc-단점">RPC 단점</h2>
<ul>
<li>원격 메소드에 대해 느리고, 신뢰성이 낮음<ul>
<li>병목 현상이 발생할 수 있음 ⇒ 막히지 않도록 개발자가 고려해야 함</li>
<li>시스템 서버로 네트워크와 통신하기 때문에 신뢰성이 낮음</li>
</ul>
</li>
<li>실패했는지, 수신 자체를 하지 못했는지 전송하는 측에서 알 수 있는 방법이 없음</li>
</ul>
<h2 id="rpc가-필요할-때">RPC가 필요할 때</h2>
<ul>
<li>2개의 백엔드 시스템이 통신하는 경우</li>
<li>다른 회사에 제공하는 API나 웹 서비스</li>
<li>네트워크 통신의 추상화를 원하고, 서버 시스템의 동작에만 집중하려면 좋음</li>
<li>HTTP 쿠키와 같은 이점을 바라기 힘듦</li>
<li>데이터나 리소스의 비중이 적음</li>
</ul>
<h1 id="rest-api">REST API</h1>
<ul>
<li>표준이나 프로토콜이 아닌 아키텍처 스타일</li>
<li>확장성, 고가용성, 기능의 품질 속성을 가진 설계를 하기 좋음</li>
<li>사용자에게 추상화 되는 것이 리소스라는 면에서 RPC와 다름</li>
<li>사용자가 몇 가지의 메소드만을 통해 자원을 이용하는 것을 허락함</li>
</ul>
<h2 id="rpc-vs-rest-api">RPC vs REST API</h2>
<ul>
<li><p>REST API는 동적인 성질을 가짐</p>
<ul>
<li><p>RPC는 사용자가 할 수 있는 행동이 인터페이스 단계에서 정의됨</p>
</li>
<li><p>REST API는 HATEOAS를 통해 더 동적으로 행동할 수 있음</p>
<p>  ⇒ 사용자에게 응답 시 하이퍼링크등을 포함하는 방법 등</p>
</li>
</ul>
</li>
</ul>
<h2 id="rest-api가-고가용성을-가지는-이유">REST API가 고가용성을 가지는 이유</h2>
<ul>
<li>stateless한 상태를 유지함<ul>
<li>여러 인스턴스를 써도 사용자는 신경쓰지 않고 서비스 이용 가능</li>
</ul>
</li>
<li>캐시 가능성<ul>
<li>멱등성을 가지는 인터페이스에 대해 캐싱 가능</li>
</ul>
</li>
</ul>
<h2 id="리소스-이름-짓기">리소스 이름 짓기</h2>
<ul>
<li>리소스의 이름과 주소는 URI를 활용함</li>
<li>각 계층 구조는 /로 표현하고, 하위 개념을 가짐</li>
<li>비슷한 리소스 목록을 가질 수 있음</li>
<li>다양한 리소스 상태 표현 가능</li>
</ul>
<h2 id="올바른-이름-짓기">올바른 이름 짓기</h2>
<ul>
<li>명사로만 리소스 이름 짓기</li>
<li>컬렉션 리소스와 단일 리소스 분리하기</li>
<li>명확하고 의미 있는 이름 짓기</li>
<li>리소스 식별자 사용하기</li>
</ul>
<h2 id="메소드와-작업">메소드와 작업</h2>
<ul>
<li>REST API는 메소드의 개수를 제한함<ul>
<li>Create, Update, Delete, Getting</li>
</ul>
</li>
<li>표준 메소드가 매핑되어 있음<ul>
<li>POST, PUT, DELETE, GET</li>
</ul>
</li>
<li>표준 메소드는 몇몇 특징을 가짐<ul>
<li>GET - 적용해도 리소스가 변하지 않음</li>
<li>GET, PUT, DELETE - 멱등성을 가짐</li>
<li>GET - 디폴트로 캐싱이 가능</li>
<li>POST - 클라이언트에게 보내는 일부 응답을 다른 메소드 형태로 캐싱 가능</li>
</ul>
</li>
<li>POST나 PUT 메소드는 JSON이나 XML 형태로 데이터를 보낼 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 및 시스템 설계 - (1) 시스템 설계]]></title>
            <link>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-1-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@cloud_365/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%B0%8F-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-1-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 06 Jul 2025 06:21:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 시리즈는 <a href="https://www.udemy.com/course/software-architecture-design-large-scale-systems/?couponCode=KRLETSLEARNNOW">【한글자막】 소프트웨어 아키텍처 및 대규모 시스템 설계 </a> 강의를 보고 정리한 내용입니다.</p>
</blockquote>
<h1 id="시스템-설계">시스템 설계</h1>
<ul>
<li>메소드, 알고리즘, 클래스에 대한 설계가 다름<ul>
<li>추상화의 단계가 다르기 때문</li>
<li>추상화가 커질수록 문제가 다양해지고, 제약이 적어짐</li>
</ul>
</li>
<li>클라이언트가 항상 본인의 요구사항을 다 이해하고 있지 않음<ul>
<li>이에 필요한 정보는 질문 등을 통해 확인해야 함</li>
<li>면접에서조차 이런 질문이 평가의 일부가 되기도 함</li>
</ul>
</li>
<li>대규모 프로젝트로 넘어갈수록 엔지니어가 많이 필요하고, 인력 비용이 많이 듦<ul>
<li>매번 요구 및 클라이언트의 피드백에 따라 수정할 수 없음</li>
</ul>
</li>
</ul>
<h2 id="아키텍처-드라이버">아키텍처 드라이버</h2>
<ol>
<li>시스템 기능<ul>
<li>시스템의 행위를 나타내는 요구사항</li>
<li>시스템의 목적과 연결되며, 기능적 요구사항이라고도 함</li>
<li>블랙박스 함수로 표현됨</li>
<li>아키텍처면에서 중요하지 않음 ⇒ 어느 아키텍처라도 기능을 수행할 순 있음</li>
</ul>
</li>
<li>품질 속성<ul>
<li>시스템이 꼭 가져야 하는 요구사항으로 비기능적 요구사항이라고도 함</li>
<li>확장성, 가용성, 신뢰성 등이 포함됨</li>
<li>아키텍처와 직결됨</li>
</ul>
</li>
<li>시스템 제약 사항<ul>
<li>마감 기한, 인력 등등이 전부 포함됨</li>
<li>일부 설계가 희생되거나 방법을 바꾸는 등으로 절충하게 됨</li>
</ul>
</li>
</ol>
<h2 id="시스템-기능-요구사항-분석">시스템 기능 요구사항 분석</h2>
<ol>
<li>시스템 기능<ul>
<li>시퀀스 다이어그램을 활용하여 시각화 가능</li>
</ul>
</li>
<li>품질 속성<ul>
<li>기능 사항과 관련 없지만 시스템 전체에 영향을 줄 수 있는 부분들</li>
<li>측정 가능하고 검증 가능해야 함<ul>
<li>요구사항에 맞게 동작하는지 파악하기 위함</li>
<li>빠르게와 같은 정성적인 요소를 사용하지 않음</li>
</ul>
</li>
<li>품질 속성 전부를 제공할 순 없음<ul>
<li>품질 속성은 서로 상충하기 때문에 모두 만족 시킬 수 없음</li>
<li>결합하기 어려우므로 적절한 절충이 필요함</li>
</ul>
</li>
<li>품질 속성의 실현 가능성을 고려해야 함<ul>
<li>고객은 항상 전문가가 아니기 때문에 이에 대한 판단은 우리의 몫임</li>
</ul>
</li>
</ul>
</li>
<li>시스템 제약 사항<ul>
<li>품질 속성을 적용하기 위해 절충하게 만드는 것들<ul>
<li>시스템을 설계할 때 권한을 제한하도록 시스템 전체나 일부에 적용된 결정 사항</li>
<li>좀 더 설계하는 방향성에 가까움</li>
</ul>
</li>
<li>기술 제약사항<ul>
<li>하드웨어 벤더나 클라우드 벤더</li>
<li>프로그래밍 언어, 프레임워크</li>
<li>데이터베이스 기술</li>
<li>플랫폼, 브라우저, OS 지원</li>
</ul>
</li>
<li>비즈니스 제약 사항<ul>
<li>예산, 마감일 등등</li>
</ul>
</li>
<li>규칙/재정에 의한 제약 사항</li>
<li>꼭 알아야 할 사항<ul>
<li>어떤 제약 사항도 가볍게 넘겨서 안됨</li>
<li>해당 제약 사항을 삭제해도 동작하도록 설계해야 함</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="대규모-시스템에서-중요한-품질-속성일부">대규모 시스템에서 중요한 품질 속성(일부)</h1>
<h2 id="기능performance">기능(performance)</h2>
<ol>
<li>응답 시간<ul>
<li>사용자는 자연스레 응답 시간이 빠르면 좋은 시스템의 성능이 좋다고 생각함</li>
<li>Processing Time(처리 시간)과 Waiting Time(대기 시간, 지연 시간)으로 구성됨</li>
</ul>
</li>
<li>처리량(Throughput)<ul>
<li>시스템이 일정 시간 내에 처리한 양 (1초 단위 측정이 자주 사용됨)</li>
</ul>
</li>
<li>고려사항<ul>
<li>고객은 응답 시간에 대해 파악하고 있어야 함</li>
<li>응답 시간 분산에 대해 파악해야 함<ul>
<li>실무에서는 응답 시간이 일정하지 않기 때문에 어떤 값을 샘플로 사용할지 결정해야 함</li>
<li>히스토그램 등을 활용하여 시각화할 수 있음</li>
<li>꼬리 지연 시간(Tail Latency)<ul>
<li>가장 긴 지연 시간을 가지지만 적은 수의 케이스를 가지는 지연 시간</li>
<li>응답 시간 지연이 발생한 대부분의 케이스는 마지막에 몰려 있는 경우가 큼<ul>
<li>이 경우 중앙값과 평균값으로 일부에 대해 파악할 수 없음</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="확장성scalability">확장성(scalability)</h2>
<ul>
<li>일반적으로 시스템의 부하는 일정하지 않고 특정한 패턴을 띔<ul>
<li>사건이 발생했을 때, 검색 사이트 사용량이 커짐</li>
<li>주말 / 연휴의 쇼핑 사이트 사용량</li>
</ul>
</li>
<li>성능 저하 지점<ul>
<li>사용량이 증가하다 시스템의 성능이 떨어지는 지점</li>
<li>동일한 조건에서 성능 저하 지점이 늦을수록 확장성이 좋다고 함</li>
</ul>
</li>
<li>3차원 형태로 이해하기<ul>
<li>수직 확장(Scale up)<ul>
<li>시스템이 부하에 다다랐을 때, 업그레이드 하는 방식</li>
<li>장점<ul>
<li>어떤 어플리케이션이든 적용 가능하고, 코드 수정이 필요 없음</li>
<li>마이그레이션이 간편함</li>
</ul>
</li>
<li>단점<ul>
<li>업그레이드에 한계가 있음</li>
<li>중앙 집중화된 시스템으로 고가용성과 내결함성을 갖기 힘듦</li>
</ul>
</li>
</ul>
</li>
<li>수평 확장(scale out)<ul>
<li>리소스의 단위를 늘리는 방법(기기를 여러 대 사용)</li>
<li>기기마다 요구되는 부담이 완화됨</li>
<li>장점<ul>
<li>제한 없이 확장 가능</li>
<li>기기를 쉽게 더하거나 제거 가능</li>
<li>고가용성과 내결함성을 가짐</li>
</ul>
</li>
<li>단점<ul>
<li>어플리케이션을 분산 시키기 어려울 경우 코드의 수정이 필요함</li>
<li>복잡해지고 조정을 위한 오버헤드가 발생함</li>
</ul>
</li>
</ul>
</li>
<li>조직적 확장성<ul>
<li>일정 이상 엔지니어를 투입해도 어느 순간부터 효율이 증가하지 않음</li>
<li>많은 회의, 잦은 머지 컨플릭트, 진입 장벽, 테스트 어려움 등등이 포함됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="가용성">가용성</h2>
<ul>
<li>시스템 가용할 수 있는 시간 = 업타임</li>
<li>시스템 가용이 불가능한 식나 = 다운 다임</li>
<li>가용성 ⇒ Uptime / 전체 시스템 가동 시간</li>
<li>MTTR<ul>
<li>문제를 발견하고 복구하는 평균 시간</li>
<li>평균 중단 시간이라고 보면 됨</li>
<li>MTBF / (MTBF + MTTR)로도 가용성을 계산할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="내결함성과-고가용성">내결함성과 고가용성</h2>
<ul>
<li>시스템 다운이 발생하는 이유<ul>
<li>휴먼 에러</li>
<li>소프트웨어 에러</li>
<li>하드웨어 에러</li>
</ul>
</li>
<li>오류를 피할 수 없으므로 고가용성을 위해 내결함성이 필요함<ul>
<li>Single Point of Failure를 제거하기<ul>
<li>공간적 중복성<ul>
<li>대표적으로 데이터베이스 replication이 있음</li>
</ul>
</li>
<li>시간적 중복성<ul>
<li>동일한 수행이나 동작을 성공하거나 실패할 때까지 여러 번 반복</li>
</ul>
</li>
</ul>
</li>
<li>오류 감지와 고립시키기<ul>
<li>여러 인스턴스 가용 중 결함이 발생하면 해당 인스턴스에 대한 연결을 끊음</li>
<li>이를 위해 모니터링 서비스가 별도로 요구됨</li>
</ul>
</li>
<li>복구<ul>
<li>복구 시간이 빠르다면 가용성이 높아질 수 있음</li>
<li>대표적으로 Rollbacks과 같은 방법으로 문제를 제거할 수 있음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="품질-관련-용어">품질 관련 용어</h2>
<ol>
<li>SLA - Service Level Agreement<ul>
<li>서비스 제공자와 클라이언트 간의 협약</li>
<li>품질 등에 대한 협약을 의미함</li>
</ul>
</li>
<li>SLIs - Service Level Indicators<ul>
<li>서비스 수준 척도</li>
</ul>
</li>
<li>SLOs - Service Level Objectives<ul>
<li>시스템과 관련해서 설정하는 시스템 목표, 목표값, 목표 범위 등</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] SSAFY 10기를 마치며]]></title>
            <link>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-SSAFY-10%EA%B8%B0%EB%A5%BC-%EB%A7%88%EC%B9%98%EB%A9%B0</link>
            <guid>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-SSAFY-10%EA%B8%B0%EB%A5%BC-%EB%A7%88%EC%B9%98%EB%A9%B0</guid>
            <pubDate>Fri, 14 Jun 2024 13:03:14 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>현장 실습을 끝낸지 1년 반, SSAFY를 시작한지 1년이라는 시간이 흘렀다. 어떤 모습이든 SSAFY라는 교육 과정이 끝나면서 생각해봤다. SSAFY는 나한테 무엇을 남겼을까.</p>
<h1 id="입과하는-날">입과하는 날</h1>
<p>현장 실습을 마치고 6개월 간 취업 활동을 했다. 지금 생각해보면 그렇게 간절하지도 않고, 거의 빈둥거리면서 6개월을 보낸 것 같다. 자기소개서를 적은 나, 포트폴리오를 만드는 나에 만족했다. 정확히 자기소개서가 무엇인지, 포트폴리오는 어떻게 만들어야 하는지 아는 바가 없지만 그렇게 쓰고 나면 뭐라도 한 것 같아 좋았다.</p>
<p>생각해보면 현장 실습할 때부터 취업 한파에 대한 우려가 들려왔다. 사실 4학년에서 졸업 준비하면서 코로나가 끝나오고, 충분히 일자리가 줄어들고 있었다. 하지만 나는 괜찮겠지했고,  현장 실습 전환이 아니라 취업 준비를 선택했다.</p>
<h2 id="ssafy-지원">SSAFY 지원</h2>
<p>취업 준비를 하다보면 <code>내가 부족한가</code>라는 생각이 가득 찬다. 그래서 불안함이 해소하고자 의무감 반, 좀 더 배우고 싶다는 마음 반으로 SSAFY를 지원했다.</p>
<p>나는 전공자였고, 간단한 코딩 테스트부터 치뤘다. 실수가 있어서 1문제를 풀고, 1문제를 제대로 다 못풀었지만 당시 코딩 테스트는 전공에서 배운 지식을 활용할 수 있으면 풀 수 있는 만큼 나왔다.</p>
<p>어려운 건 면접이었다. 따로 면접 연습도 제대로 안해봤고, 취업 준비 과정에서 면접을 가보지도 못해서 사실상 첫 면접이었다. PT 면접과 관련해서 많이 검색해보고 갔는데, 얘기해보니 다른 사람들도 다 똑같은 유투브 영상을 보고 갔던 것 같다.</p>
<h2 id="추가-합격">추가 합격</h2>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/43331710-0f96-4f91-a57f-8d4e04af044d/image.png" alt="불편"></p>
<p>근데 그러고 불합격했다. 코딩 테스트가 기업의 코딩 테스트에 비해 쉬웠기 때문에 <code>이정도는 그냥 합격하지</code>라고 생각했었는데, 진짜 충격 많이 받았다. 내가 이정도 밖에 안되는 건가? 싶어서 많이 서글펐던 기억이 난다. 물론 서울 캠퍼스가 다른 캠퍼스에 비해 경쟁률이 높다고 이야기가 나왔지만, 그런건 중요하지 않았다. 떨어졌으니까.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/4a526a04-e159-4c49-bb8b-30f2c81717f2/image.png" alt="편안"></p>
<p>그러고 7월 좀 지나서 추가 합격 연락을 받았다. SSAFY는 지원금이 나오니 막막한 취준 생활에 단비가 내리는 느낌이었다.</p>
<h1 id="1학기---교육">1학기 - 교육</h1>
<p>나는 Java 전공반으로 입과했고, 웹 개발 관련 교육을 수료했다. SSAFY의 1학기는 교육으로 이뤄졌다. 자료구조나 알고리즘 같은 수업도 들었고, Java나 JavaScript와 같은 언어, 문법 그리고 Spring이나 Vue.js와 같은 웹 개발에 필요한 내용도 들었다.</p>
<p>전공반이라고 되어 있긴 하지만 개인 역량 및 의지에 따라 비전공반이 아니라 전공반을 들을 수도 있고, 이에 전체적인 프로세스를 교육하는 느낌이었다. 그렇기 때문에 대학 생활이나 개인적으로 웹 개발을 공부한 사람이라면 이미 아는 내용이거나 금세 파악하고 지루해질 수는 있었다.</p>
<h2 id="스터디">스터디</h2>
<p>하지만 SSAFY가 오프라인 교육이라는 점이 컸다. 비슷하게 IT 계열 취업 준비을 목표로 하는 사람이 모이니 필요에 따라 금세 스터디가 구성됐다. 반이 배정된지 몇 주만에 금세 첫 스터디가 만들어졌고, 여기서 다들 동기부여가 어느 정도 됐는지 스터디를 구성했다.</p>
<p>어쩌다보니 나도 스터디장을 맡아서 스터디를 진행했다. 처음엔 알고리즘 스터디를 만들고, 이후엔 CS스터디도 하나 참가해서 병행했다. 특히 매주 알고리즘 문제를 전주에 배운 개념이랑 다른 문제집들을 보면서 열심히 냈는데, 다들 낸 문제들을 호평해줬다.</p>
<h2 id="조기-퇴소">조기 퇴소</h2>
<p>SSAFY는 중도 취업하는 경우 병행이 불가능하기 때문에 조기 퇴소를 진행한다. 이게 SSAFY 교육생 사이에서 &#39;싸탈&#39;이라고 부르는데, 뭐랄까 군대 동기가 먼저 전역하는 느낌? 그거보다 조금 더 헛헛하고 막막해진다.</p>
<p>SSAFY는 점심을 준다. 심지어 정말 잘 준다. 그리고 월마다 지원금도 주고, 교육도 하니까 뭔가 잘하고 있는 것 같다. 그렇게 점점 안심이 되는데, 그 때 누가 조기 퇴소를 한다. 점심마다 얼굴 마주보고 밥 먹던 사람이 사라지니 내가 늦은 것 같고 막막하다.</p>
<h2 id="다시-1학기로-돌아간다면">다시 1학기로 돌아간다면</h2>
<p>지금 돌이켜봤을 때 1학기에 가장 아쉬웠던 부분은 회사 지원을 별로 안한 점이다. 이전에 6개월을 그렇게 지낸 관성도 있고, 1학기가 지나도 잡페어와 2학기가 있다고 덮어두고 안심한 점이 컸다. 그런데 취준에 있어 제일 문제는 해본 사람이 잘한다는 거다. 아무리 재료가 있다 한들 그걸 잘 요리하지 않으면 생식과 다름이 없으니까. 조금이라도 빨리 연습해보지 않는 점이 아쉽다.</p>
<p>내가 만약에 웹 개발 쪽을 공부해보지 않았다면 모르겠지만 동아리 활동이나 장기 현장 실습을 거쳤다. 그렇게 시간을 많이 할애하지 않아도 괜찮았는데, 좀 더 부족한 코딩 테스트나 입사 지원과 같은 부분에 계획적으로 시간을 썼다면 좋았을 것 같다.</p>
<h1 id="2학기---프로젝트">2학기 - 프로젝트</h1>
<p>생각보다 1학기가 빨리 흐른다. 수업 듣고, 스터디하고, 공채 뜬거 구경 좀 하면 1학기가 끝난다. 반마다 다르겠지만 약간 대학교 1~2학년 느낌도 많이 난다.</p>
<p>그렇게 2학기가 되면 갑자기 덩그러니 프로젝트 환경에 떨어진다. 총 3번의 프로젝트를 진행하는데, 취업 관련해서 진행하는 한달 가량을 빼고 4<del>5개월 동안 프로젝트를 3번한다. 곰곰히 생각해보면 프로젝트당 6</del>7주밖에 시간이 안주어지고, 이게 아이디어 기획, 디자인, 개발, 배포 모두 포함한 시간이 된다. 그리고 방향을 헷갈릴 때 잡아주시는 분들만 계시지 사실상 팀끼리 잘 해내야 한다. 맞다. 힘들었다.</p>
<h2 id="첫-프로젝트">첫 프로젝트</h2>
<p>첫 프로젝트에 자세한 회고는 <a href="https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-SSAFY-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0">[회고] 2024 SSAFY 공통 프로젝트 - &quot;Speechless&quot; 회고</a>에 써뒀다. 당장 프로젝트 자체가 처음인 사람도 있고 아닌 사람도 있는데, 내부적으로 협업툴이나 이전까지 주먹구구식으로 프로젝트를 진행한 사람도 있다.</p>
<p>거의 초면인 사람끼리 프로젝트를 하면서 서로 능력이 제각각이니 조율하기 진짜 어렵다. 프로젝트를 여러 번 진행하면서 <code>Validation을 프론트에서 하는데, 백에서도 해요?</code>, <code>기능 명세서 같은 문서 작업이 왜 필요한지 모르겠어요</code> 같은 별의 별 이야기를 들어봤는데, 내 입장에서도 답답한 질문을 자연스레 할테니 서로 감정 상하지 말고 잘 알려주는게 중요했다.</p>
<p>솔직히 좀 의견 교환에서 투닥거리긴 했는데, 결과가 잘나왔다. 그리고 나중에 얘길 들어보니 우린 진짜 의견 교환하면서 투닥거리기만 해서 다행이다. 무엇보다 프로젝트 중에 기술 문서를 적으면서 기술 블로그 작성을 추천 받았는데, 이렇게 작성하는 걸 시작하는데 큰 도움이 되었다.</p>
<h2 id="두-번째-프로젝트">두 번째 프로젝트</h2>
<p>두 번째 프로젝트에 대한 자세한 회고는 <a href="https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-2024-SSAFY-%ED%8A%B9%ED%99%94-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BD%94%EC%BD%94%EB%94%94-%ED%9A%8C%EA%B3%A0">[회고] 2024 SSAFY 특화 프로젝트 - &quot;코코디&quot; 회고</a>에 써뒀다. 프로젝트 회고에 따로 적혀있긴 한데, 기술을 위해 아이디어를 내는건 생각보다 어렵다. 그렇다보니 자연스레 아이디어를 내고 기술이 따라가게 되는데, 그러면 원하는 기술을 쓰기 힘들 확률이 크다. 그래서 나는 데이터 분산 기술을 다뤄보고 싶었는데, AI에 데이터 추천을 섞어서 썼다.</p>
<p>이게 물론 라이브러리나 이미 있는 모델을 가져와서 사용하겠지만, 7주 동안 전부 다하는 게 진짜 힘들다. 실제로 마지막 주까지 추천 알고리즘이랑 씨름했고, 기능 개발이 엄청 밀려서 다른 파트 팀원이 새벽 개발을 했다. 좀 더 잘 알았더라면 빨리 할 수 있었을 텐데. 미리 관심을 가지고 찾아두거나 잘 아는 팀원을 구하는 것도 좋을텐데. 아쉬움이 좀 남는다.</p>
<h2 id="세-번째-프로젝트">세 번째 프로젝트</h2>
<p>솔직히 세 번째 프로젝트는 망했다. 내가 회고를 적으면서 망한 프로젝트에 대해서도 회고를 해야한다고 했는데, 세 번째 프로젝트 회고는 적지 않았다. 프로젝트가 미완이거나 제대로 완성되지 않았다면 모르겠지만, 어디에 내가 열중해서 개발했다고 내놓기 힘들었다. </p>
<p>프로젝트를 진행하면서 언제나 팀원 모두가 같은 방향을 바라보고 갈 수는 없다. 그렇지만 노를 젓지 않는 사람까지 데려갈 정도로 내가 여유롭진 못했다. 아마 내심 세 번째 프로젝트니까 별말 없어도 잘 흘러가겠지라는 마음도 있었다. 하지만 특히 두 번째 프로젝트 중간에 공채 시즌이 시작하는 만큼 여기에 집중하지 못하는 인원이 많았다. <code>리뷰 꼭 해야해요?</code>, <code>컨벤션을 이렇게까지 정할 일인지 모르겠어요</code> 같은 말을 듣고 있으니 마음이 꺾였다.</p>
<p>실제로 프로젝트를 완성은 했으나, 그렇게 기능 대부분이 우격다짐으로 진행됐다. 아마 취준 기간이 길어지면서 마음이 약해졌으리라. 큰 소리 내고 부딪혔으면 좋았을까?</p>
<h2 id="다시-2학기를-한다면">다시 2학기를 한다면</h2>
<p>프로젝트가 아닌 장기적으로 볼 팀을 만들었을 것 같다. 나는 새로운 사람들과 계속 프로젝트를 만들어보는 것도 큰 경험이라고 생각했는데, 프로젝트 기간이 짧다보니 매번 새로운 사람과 커넥션을 만드는 시간이 크게 소요됐다. 여기에 방향이라도 같으면 좋을텐데, 방향마저 다르면 정신적으로, 육체적으로 피곤하다. 물론 좋은 경험은 맞지만, 결국 만든 프로젝트는 포트폴리오로 가져가기 위해 최대한 노력해야 한다.</p>
<h1 id="ssafy를-돌아보며">SSAFY를 돌아보며</h1>
<h2 id="너무-추운-나날들">너무 추운 나날들</h2>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/0b116721-0f24-4b51-a100-c8c87a45b86e/image.png" alt="눈보라"></p>
<p>나는 채용 한파라는 단어가 나랑은 멀 줄 알았는데, 이렇게 와닿을 줄 몰랐다. 또 주변에 IT 업계가 아닌 친구들이나 현장 실습 전환 등을 통해 취업한 친구들이 많아서 그냥 내가 느린줄 알았다.</p>
<p>그런데 SSAFY에서 같이 취업을 준비하는 입장을 만나면서 정말 춥다고 생각했다. 아니 다른 사람들과 진짜 춥다고 이야기했다. 얘기해보면 생각보다 좋은 학교, 경험, 이력 등 나보다 훨씬 뛰어난 사람이 많은데, 다같이 취업에 한탄하는 것을 보면서 너무 추웠다.</p>
<p>그런 상황에서 SSAFY에서 주는 지원금은 많은 도움이 됐다. 적어도 밥은 안굶고, 방세는 내 힘으로 낼 수 있으니까.</p>
<h2 id="ssafy에서-얻을-수-있는-점">SSAFY에서 얻을 수 있는 점</h2>
<p>아예 프로그래밍이나 웹 개발을 모른다는 전제라면 SSAFY는 좋은 시작점이 될 것이라 확신한다. 다소 빠르다 느끼고 주변에 비해 뒤쳐진다고 생각이 들지만, 솔직히 4년 동안 배우고 올라온 사람을 6개월에서 1년만에 따라잡는다고 본인 입으로 말했으니 감수할 수 있다고 생각한다. 따라가면 그만큼 많은 내용을 알 수 있고, 프로젝트 경험을 쌓을 수 있다.</p>
<p>그에 비해 기본적인 웹 개발을 해본 사람이라면? 사실 아는 내용이 대부분이다. 프로그래머니까 당연하게 느끼지만 개인 공부를 지속할 수 밖에 없다.</p>
<h3 id="취업도-공부해야-한다">취업도 공부해야 한다</h3>
<p>그럼 전공자는 SSAFY를 할 필요가 없을까? 그렇진 않았다. 취업에도 공부가 필요했다. 하지만 나는 그런 점을 몰랐다. 6개월을 되지도 않는 자기소개서와 포트폴리오를 만들고 만족하면서 보냈다.</p>
<p>그런 의미에서 SSAFY에서 취업 컨설턴트님과 상담은 큰 도움이 됐다. 사실 학교에서도 컨설팅을 받을 수 있다. 하지만 어떻게 받을지, 어떻게 잘 받을지 나는 잘 몰랐다. 그런데 취업 준비를 어떻게 해야할지, 어떤 내용으로 컨설팅을 받으면 좋을지 계속 이야기하고 직접 받을 수 있는 점은 많이 도움이 됐다.</p>
<h2 id="ssafy는-무슨-의미가-되었는지">SSAFY는 무슨 의미가 되었는지</h2>
<p>SSAFY는 그나마 답답하고 조급했던 마음을 놓을 수 있는 기회였다. 채용 한파라는 단어를 뼈저리게 느끼면서도 버틸 수 있었다. 그러면서 취업에 필요한 공부도 하고, 기술적으로 하고 싶은 공부도 해볼 수 있었다. 그러면서 데이터베이스나 데이터에 관심이 간다는 사실도 알게 됐고, 더 노력하자는 마음도 든다.</p>
<p>시간을 돌려서 SSAFY를 갈 것이냐고 물어보면 갈 것 같다. 이왕이면 이번에 12기부터는 데이터 트랙도 나온다고 하니 12기 SSAFY도 갈 수 있으면 좋을 것 같다. 지난 1년 동안 다닌 보람이 있다고 느낀다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좌충우돌 병렬 프로그래밍]]></title>
            <link>https://velog.io/@cloud_365/%EC%A2%8C%EC%B6%A9%EC%9A%B0%EB%8F%8C%EB%B3%91%EB%A0%AC%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@cloud_365/%EC%A2%8C%EC%B6%A9%EC%9A%B0%EB%8F%8C%EB%B3%91%EB%A0%AC%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Tue, 28 May 2024 05:00:12 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>새로운 프로젝트를 진행하면서 Python을 사용해서 서비스를 구성했다. 서비스 내에 AI 모델을 호출하는 과정이 있었고, 이를 HTTP 기반으로 통신을 진행했다. </p>
<p>동기적인 코드로 구성된 서비스는 AI 모델 성능에 따라 큰 차이가 발생했고, 이를 비동기로 바꾸었으나 동일한 속도가 측정됐다. 내가 Python 코드를 못 짰나? 찾아본 내용을 정리해보고자 글을 작성한다. </p>
<h1 id="비동기로-짠다는-것">비동기로 짠다는 것</h1>
<h2 id="비동기란">비동기란?</h2>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/c4e49b23-e434-4c08-a5c3-13c600a449ae/image.png" alt="동기와 비동기"></p>
<p>흔히 동기와 비동기에 대해 설명하자면 위의 그림을 볼 수 있다. 간단하게 보자면 <strong>동기는 작업의 실행 순서를 지켜 작업을 진행하는 방식</strong>이고, <strong>비동기는 그와 무관하게 작업을 진행한다</strong>.</p>
<p>순서와 무관하게 진행하면서 비동기 방식은 코드 순서 때문에 대기하는 경우가 없기 때문에 <strong>CPU의 유휴 상태를 효과적으로 관리하는 이점</strong>이 있다.</p>
<p>보기엔 비동기 작업만 성공하면 어플리케이션의 속도가 눈에 띄게 향상할 것 같다. 왜? CPU 유휴 상태를 효과적으로 처리하니까. </p>
<h2 id="동기-블로킹">동기? 블로킹?</h2>
<p>어떻게 보면 비동기 통신은 앞선 작업을 대기하지 않는 것으로 보인다. 편의상 비동기 + 논 블로킹 작업을 지칭하지만 이론적으론 엄연한 차이가 있다고 한다. 해당 내용은 구글링하면서 더 잘 정리해둔 블로그가 있으니 첨부한다.</p>
<p><a href="https://jh-7.tistory.com/25">Blocking, Non-blocking, Sync, Async의 차이</a>
<a href="https://inpa.tistory.com/entry/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B8%94%EB%A1%9C%ED%82%B9%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC">완벽히 이해하는 동기/비동기 &amp; 블로킹/논블로킹</a></p>
<h1 id="나는-왜-비동기를-하려고-했는가">나는 왜 비동기를 하려고 했는가</h1>
<p>내가 실수한 부분을 짚기에 앞서 어떤 상황을 통해 비동기 작업을 진행하려고 했는지, 어떻게 해결해 나갔는지 이야기해 보겠다.</p>
<h2 id="1-느린-문장-분리-라이브러리">1. 느린 문장 분리 라이브러리</h2>
<p>프로젝트는 블로그 글을 가져와서 하나의 문단으로 만들고, 이를 패턴을 기반으로 한국어 문장으로 분리하는 라이브러리를 사용했다. 패턴 기반으로 동작하면서 다소 긴 시간 동안 문장 분리가 실행됐다.</p>
<h2 id="2-ai-모델-동작">2. AI 모델 동작</h2>
<p>내부에서 <code>문장에 대한 광고 판단</code>, <code>이미지에 대한 광고 판단</code>, <code>긍정/부정 평가</code> 등 3가지 모델이 동작하면서 모델 동작에 비교적 긴 시간이 걸렸기 때문에 이로 인한 시간이 오래 걸리는 문제가 있었다.</p>
<p>척 봐도 2가지 요소에 의해 시간이 오래 걸린다. 실제로 <strong>11개의 URL</strong>을 요청한 결과 <strong>응답까지 10분 이상</strong> 걸렸다.</p>
<h1 id="비동기로-바꿔보기">비동기로 바꿔보기</h1>
<p>나는 각각에서 제일 병목 현상이 발생하는 부분에 대해 생각했다. 문장 분리와 AI 동작 과정에서 발생하는 CPU Burst가 가장 큰 문제라고 생각했다.</p>
<blockquote>
<p>CPU Burst?</p>
<p>어떤 프로세스가 동작함에 있어 I/O Waiting, 다시 말해서 입출력에 의한 대기 시간이 CPU가 작업하는 시간보다 긴 작업을 I/O Bound라고 말한다.</p>
</blockquote>
<p>반대로 CPU가 작업하는 시간이 입출력에 대해 대기하는 시간보다 긴 경우 CPU Bound라고 한다.</p>
<blockquote>
</blockquote>
<p>어떤 작업에 대해 CPU Bound가 더 큰 작업을 CPU Burst, I/O Bound가 더 큰 작업을 I/O Burst라고 한다.</p>
<h2 id="오히려-느려진-코드">오히려 느려진 코드</h2>
<p>그래서 <code>CPU를 더 잘 사용할 수 있도록 비동기 처리를 해주면 되지 않을까?</code>라고 생각했다. 한 URL에 대해 1분가량 걸리니까 비동기로 작업하면 1분 정도에 모든 URL에 대해 작업이 완료되지 않을까 싶었다. 비동기 처리를 통해 이것이 가능하므로 FastAPI 내의 async/await를 활용하면 될 것이라 생각했다.</p>
<p>결과만 말하자면 <strong>오히려 느려졌다</strong>. 동기로 동작하는 경우 650초 전후가 걸렸는데, 이후 750초까지 늘었다. 내가 무엇을 잘못했을까? 그 전에 Python의 비동기 동작에 대해 알아야 한다.</p>
<h3 id="coroutine-코루틴">Coroutine, 코루틴</h3>
<p>멀티 스레드는 프로세스 내에 스레드 별로 다르게 업무를 할당한다. Python은 기본적으로 싱글 스레드로 작업하고 이러한 멀티 스레드 작업을 위해 코루틴이라는 기능을 지원한다.</p>
<p>Python은 언제든지 함수를 잠깐 멈추고, 다른 작업을 진행할 수 있는 코루틴이라는 개념을 지원한다. 이는 async/await를 통해 사용할 수 있다.</p>
<p>여기서 나는 async/await를 사용했으니 코루틴을 사용했다. 여기서 문제가 생기는데, 코루틴은 동시성은 지원하지만 병렬성은 지원하지 않는다.</p>
<h4 id="동시성-병렬성">동시성? 병렬성?</h4>
<p>이전에 봤던 비동기 사진을 보면 각각의 작업이 동시에 이뤄지는 것으로 보인다. 하지만 엄밀히 말해서 동시성과 병렬성으로 특징이 구분된다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/4973e538-bc56-4eb7-9431-4b6c6476709e/image.png" alt="동시성과 병렬성"></p>
<p><a href="https://www.baeldung.com/cs/concurrency-vs-parallelism">출처: baeldung Concurrency vs Parallelism
</a></p>
<p>그림을 보면 금방 알 수 있다. 코루틴은 단일 스레드에서 동작한다. 다시 말해서, 하단 Parallel 부분에 해당하는 병렬성도 없고 동시성의 경우엔 <strong>일의 총량이 변하지 않고 잘라서 일한다</strong>는 뜻이다.</p>
<h4 id="그럼-왜-더-느려지는데">그럼 왜 더 느려지는데?</h4>
<p>동시성과 병렬성 비교 그림을 보면 알지만 동시성의 경우엔 일을 잘게 쪼개서 진행한다. 그렇다고 각 프로세스가 같은 프로세스는 아니다. 다시 말해서 Context Switching이 발생한다.</p>
<blockquote>
<p>Context Switching?</p>
</blockquote>
<p>CPU가 작업을 처리하고 있을 때, 인터럽트 등에 의해 해당 작업을 잠시 중단하고 다른 프로세스나 스레드를 CPU에서 작업하게 된다.</p>
<blockquote>
</blockquote>
<p>이 과정에서 기존 프로세스나 스레드의 진행도를 저장해두고, 다른 프로세스나 스레드의 작업이 종료되면 기존 작업을 원복한다. 이 과정을 Context Switching이라고 한다.</p>
<p>Context Switching은 단순히 CPU에 올라가 있는 작업이 변경되고, <strong>CPU는 아무 일도 할 수 없는 순수한 오버헤드</strong>다. 그런데 기존에 동기 방식은 작업 하나만 완수하기 때문에 Context Switching이 발생하지 않는다. 하지만 비동기 방식은 수시로 작업이 변경되면서 잦은 Context Switching이 발생한다.</p>
<p>이 과정에서 오버헤드가 쌓여 비동기 방식이 오히려 느린 상태가 발생했다.</p>
<h3 id="gil">GIL</h3>
<p>문제점을 찾았으니 멀티 스레드로 바꾸면 될 것 같았다. 하지만 멀티 스레드도 정답은 아니었는데, 이는 Python의 GIL(Golbal Interpreter Lock) 정책 때문이었다.</p>
<p>Python은 참조 횟수를 기반으로 GC를 진행하는데, 이는 Thread-Safe하지 않다. 이로 인한 문제를 방지하기 위해 <strong>전체 중 하나의 스레드에서만 인터프리터가 동작할 수 있도록 제한</strong>한다. 이것을 GIL이라고 부른다.</p>
<p>인터프리터의 동작이 제한되기 때문에 결과적으로 병렬성을 활용한 행동에 제한받는다. CPU는 결국 하나의 인터프리터를 통해 진행되므로 스레드 기반의 병렬 프로그래밍은 불가능했다.</p>
<h1 id="해결-과정">해결 과정</h1>
<h2 id="ai-모델-서버-분리하기">AI 모델 서버 분리하기</h2>
<p>현재 프로젝트는 AI 모델에 대해 따로 API 서버를 구축했다. 시간과 러닝 커브를 고려하여 메시지 큐를 사용한 아키텍처는 구성하지 않았고, HTTP를 통해 요청과 응답을 진행했다.</p>
<p>내부적으로 Docker Compose로 Docker Network를 구성해서 네트워크를 통한 통신을 피하고, 컨테이너 간의 통신을 진행하도록 했다.</p>
<h3 id="결과는-변함-없음">결과는 변함 없음</h3>
<p>I/O Burst에 따른 속도 이슈가 해소되면서 다소 속도가 올랐으나, 다이나믹한 변화를 보이진 않았다. cProfile을 통해 확인해본 결과 요청 후 대기하지 않고, 로직이 진행됨이 확인되었다. 하지만 AI 모델을 사용하면서 모델 구동에 따른 시간이 단축되지 않았다는 부분이 컸다.</p>
<h2 id="멀티-프로세스">멀티 프로세스</h2>
<p>AI 모델을 구동하는 코드 내에 멀티 프로세스를 적용하기로 했다. GIL를 피하기 위한 방법은 멀티 프로세스를 통한 처리라고 생각했다. 하지만 AI 모델 분리 이전에 멀티 프로세스를 적용해보려 했다가 메모리 부족이 발생했기 때문에 적은 수의 프로세스를 가지고 작업을 진행했다.</p>
<h3 id="응답은-왜-안해주는데">응답은 왜 안해주는데</h3>
<p>팀원과 멀티 프로세스로 변환하고, 서버에 배포하면 계속 API 담당 컨테이너에서 Timeout 에러를 뱉었다. 이게 Timeout 시간을 3분에서 5분 가량으로 늘려서 설정해뒀는데, 이전에도 1분 내에 실행됐으므로 말이 안됐다.</p>
<p>docker stats을 통해서 CPU 사용량을 확인해보았는데, AI 모델 서버가 잠깐 CPU를 점유하는 듯하더니 그대로 0~1퍼를 유지하면서 FastAPI 어플리케이션만 돌아감을 확인했다.</p>
<h3 id="로깅">로깅</h3>
<p>다양한 의견이 나왔으나 결국은 모두 근거가 없었다. 그런 와중 멀티 프로세스는 별도의 프로세스에서 실행되기 때문에 로그 자체가 남지 않았을 거란 생각이 들었다. 때문에 로깅을 하자고 의견을 냈다.</p>
<p>부모 프로세스에게 로그 데이터를 넘겨 로깅을 통해 확인한 결과 AI 모델을 사용하는 과정에서 해당 프로세스가 가만히 멈춰있음을 확인했다.</p>
<h3 id="데드락">데드락</h3>
<p>프로세스가 동작하는데 그대로 멈춰서 대기한다? 머릿속에 데드락이 스쳤는데, 문제는 로컬 환경에서는 제대로 동작한다는 점이었다.</p>
<p>이에 로컬 환경과 서버 환경의 차이에 주목했다. 아무래도 가장 큰 차이는 OS의 차이였다. 로컬 환경은 윈도우를 통해 진행하고, 서버는 Ubuntu로 리눅스 기반의 환경에서 진행했다.</p>
<p>여기서 멀티 프로세스 과정에서 문제가 생겼기 때문에 OS별로 다른 프로세스 생성 방법에 대해 조사했다.</p>
<blockquote>
</blockquote>
<p>Window</p>
<blockquote>
</blockquote>
<p>spawn 방식을 사용하는데, 이 방식은 별도의 프로세스 생성한다.
그리고 자식 프로세스 생성 시 부모 프로세스의 메모리를 그대로 복제하지 않는다</p>
<blockquote>
</blockquote>
<p>Linux</p>
<blockquote>
</blockquote>
<p>fork 방식을 사용하는데, 이 방식은 프로세스 생성 시 부모 프로세스를 복제한다
자식 프로세스는 부모 프로세스의 상태와 메모리를 복제하므로 메모리가 공유된다</p>
<p>여기서 이전에 학습한 개념을 정리하고 가정을 세웠다.</p>
<ol>
<li><p>AI 모델은 모듈을 통해 객체로 import한다.</p>
</li>
<li><p>FastAPI는 싱글 프로세스, 비동기 동작으로 여러 요청을 처리한다.</p>
</li>
<li><p>리눅스 환경에선 fork 방식을 사용하면서 자식 프로세스는 부모 프로세스의 메모리를 그대로 사용한다.</p>
</li>
<li><p>여러 요청이 들어오면 fork를 통해 생성된 프로세스가 동작하면서 각각 AI 모델을 참조한다.</p>
</li>
<li><p>각각의 프로세스가 로드된 AI 모델을 참조하는 과정에서 데드락이 발생했다.</p>
</li>
</ol>
<p>해당 가정을 바탕으로 자식 프로세스 생성 과정을 spawn으로 명시적으로 설정했고, 멀티 프로세스에 성공해 1분 내외 가량 걸리던 응답 시간을 5초에서 10초 내외로 줄일 수 있었다.</p>
<h1 id="마무리">마무리</h1>
<p>사건의 흐름에 따라 문제와 해결 방안을 적어 중구난방이기 때문에 느낀 점을 다시 정리해보고, 정리하는 김에 느낀 점을 추가해본다.</p>
<h2 id="비동기는-빠르다">비동기는 빠르다?</h2>
<p>대체로 그렇다. 하지만 GIL과 같은 언어의 정책에서 병렬성을 지원하는지 고려하고, 본인이 비동기 처리하려는 작업이 CPU Bound인지, I/O Bound인지 고려할 필요가 있다. CPU Bound 작업이라면 비동기 처리를 하더라도 결국 CPU에서 작업하는 시간이 필요하기 때문에 단축되지 않을 수 있다.</p>
<p>그리고 해당 작업이 처리되는 속도에 따라 Context Switching으로 인한 오버헤드가 더 클 수 있다. 고로 본인이 처리하려는 내용을 면밀히 살피고, 설계하는 것이 필요하다.</p>
<h2 id="무조건-비동기로-구성하면-안되나요">무조건 비동기로 구성하면 안되나요?</h2>
<p>안된다. 비동기로 구성하면서 코드의 가독성이 낮아지고, 관리가 어려울 수 있다. 그리고 멀티 프로세스를 사용하는 경우 별도의 프로세스를 생성해서 진행한다. 그렇기에 로깅하고 이를 병합하는 처리를 해두지 않았다면 디버깅이 매우 힘들다.</p>
<h2 id="cs-지식이-도움이-됐어요">CS 지식이 도움이 됐어요</h2>
<p>해결 과정을 보면 Context Switching, CPU Bound, I/O Bound라는 단어나 멀티 스레드, 멀티 프로세스와 같은 내용은 운영체제 과목에서 들어볼 수 있다. 그리고 데드락이 발생했음을 파악하지 못했다면 영영 왜 그런지 알 수 없었을 것이다. 문제 해결은 CS 지식과 기술 스택의 이해에서 출발한다는 것을 몸소 체감했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2024 SSAFY 특화 프로젝트 - "코코디" 회고]]></title>
            <link>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-2024-SSAFY-%ED%8A%B9%ED%99%94-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BD%94%EC%BD%94%EB%94%94-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-2024-SSAFY-%ED%8A%B9%ED%99%94-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BD%94%EC%BD%94%EB%94%94-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 08 Apr 2024 04:46:29 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>SSAFY 2학기 동안 진행하는 2번째 프로젝트가 끝났다. SSAFY 내에서 통칭 특화 프로젝트라고 하는데, 2번째라 좀 익숙해질 줄 알았는데 1달이라는 짧은 시간에 만드는 건 적응이 되질 않는다.</p>
<h1 id="프로젝트-시작">프로젝트 시작</h1>
<h2 id="기술-선택">기술 선택</h2>
<p>특화 프로젝트는 독특하게 인공지능이나 데이터, 핀테크와 같이 사용할 기술이나 도메인을 정하고 시작한다. 근래에 데이터를 수집, 저장하는 것에 관심이 많아지면서 분산 처리가 궁금해 데이터 분산 처리를 선택하자고 주장했다.</p>
<h2 id="빗나가는-기획">빗나가는 기획</h2>
<p>그래서 정말 분산 처리를 선택하고, 기획하는 동안 분산 처리에 대해 찾아봤다. 하둡과 MongoDB를 선택하고 책도 사서 주말에 읽으며 어떻게 작업하면 좋을지 고민했다. 항상 새로운 기술을 익히면서 어떻게 사용해볼지 고민할 때가 가장 재밌다고 생각한다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/01228782-491e-4666-9656-55536d4c92e7/image.png" alt="하둡 가이드"></p>
<p>하지만 기획하면서 제일 문제는 분산 처리라는 기술을 어떻게 살릴지였는데, 기술을 정하고 기획하려니 너무 막막한 상황이 많았다. 그래서 기술을 후에 고려하고 만들고 싶은 기획을 해보기로 했다.</p>
<p>이후 기획한 내용은 &#39;디지털 옷장 관리 및 코디 추천 서비스&#39;였다. 사용자가 가진 옷을 사진을 찍어 등록하면, 이들의 조합에 대해 추천해주는 서비스를 만들기로 했다.</p>
<p>그 다음 서비스를 어떻게 만들 수 있을지 회의하니 데이터보단 AI에 가까워졌다. 사진을 찍어 인공지능 모델을 통해 옷을 인식하고, 이를 바탕으로 옷을 추천해주기로 했다. 그래서 내가 하고 싶던 분산 처리는 사라지고, 생각에도 없던 인공지능/데이터 업무가 넘어왔다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/073a911b-28e3-4806-af6a-b759cc10f0c8/image.png" alt="얘야 마음대로"></p>
<p>정말 내 뜻대로 되는게 없었다. 프론트엔드와 백엔드 팀원이 미리 정해져 있기 때문에 백엔드로 다시 업무를 바꿀 순 없었다. 공부하지 않았던 일을 덜컥 맡게 되고, 당황스러움과 함께 프로젝트를 시작했다.</p>
<h1 id="프로젝트-진행">프로젝트 진행</h1>
<p>이전까지 공부했던 내용이 필요 없어졌기 때문에 시작하자마자 사전 조사와 학습을 다시 진행했다. 1~2주 가량을 코드 없이 조사만 했던 기억이 난다.</p>
<h2 id="될지-안될지-몰라요">될지 안될지 몰라요</h2>
<p>우선 의류 데이터를 쌓는 동안 옆에서 AI 조사와 학습을 진행하던 팀원이 말했다. 의류 인식 및 검색까진 괜찮은데, 가진 옷의 조합을 판단하는 모델을 찾기 힘들다고 했다.</p>
<p>거기에 우리 서비스에 맞게 학습을 진행하기 위해선 <code>어떤 조합이 어떤 사람에게 좋은 평가를 받았는가</code>에 대한 정보가 필요했다. 이 데이터를 구하기 힘들고, 이를 통해 학습해도 우리가 가진 옷에 맞게 나올지 미지수였다. 1달 간의 짧은 프로젝트기 때문에 나올지 안나올지 모르는 결과에 목 매달 순 없었다.</p>
<h2 id="2차-전직">2차 전직</h2>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/712ff1a9-32a0-4659-a423-a7d1437dcedf/image.png" alt="2차 전직 교관"></p>
<p>이야기를 듣고 고심 끝에 선택한 방법은 <strong>내가 데이터 추천 쪽을 공부해서 의류 조합 추천 기능을 만드는 방법</strong>이었다.</p>
<p>다시 구글을 키고, 데이터 추천에는 무엇이 있는지, 어떻게 해야하는지 다시 고민을 시작했다.</p>
<h1 id="데이터-추천-개발">데이터 추천 개발</h1>
<h2 id="기획-정리">기획 정리</h2>
<p>사용자의 옷 목록이 주어지면, 이에 따른 옷 조합을 만들어야 했다. 예를 들어, 상의, 하의, 아우터, 신발과 같이 아이템을 모아 하나의 세트를 만들어야 했다.</p>
<h2 id="협업-기반-필터링">협업 기반 필터링</h2>
<p>처음 추천 방법에 대해 고민하면서 찾아보기 시작한 방법은 협업 기반 필터링이었다.</p>
<p>특히, 아이템 기반 협업 필터링을 진행해서 <code>사용자의 옷장을 보고 비슷한 옷장을 가지고 있는 사용자에게 코디와 옷을 추천해줄 수 있지 않을까?</code>라는 생각에서 시작했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/dcb7d5d9-da4e-40c4-ad5b-73a8a729c8a3/image.png" alt="콘텐츠 기반 필터링"></p>
<p><a href="https://news.samsungsemiconductor.com/kr/%EB%B0%B1%EB%B0%9C%EB%B0%B1%EC%A4%91-%EC%B7%A8%ED%96%A5%EC%A0%80%EA%B2%A9%EC%88%98-%EC%B6%94%EC%B2%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%98-%EB%B9%84%EB%B0%80/">이미지 출처 : 삼성 반도체 이야기</a></p>
<p>추천 모델의 경우엔 별도의 학습 없이 탐색을 진행하고, 라이브러리가 방대하다보니 아래의 글을 참고했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/40cf6d5d-8315-4cee-a698-cbab51ab8770/image.png" alt="필터링 모델 비교"></p>
<p><a href="https://velog.io/@tobigs-recsys/RecommenderSystemLibraries">[Survey] 추천시스템 라이브러리 비교</a></p>
<p>해당 글을 보면서 다음의 생각을 가지고 모델 비교를 확인했다.</p>
<ol>
<li>옷 자체의 특징을 반영해서 필터링을 해야 한다</li>
<li>사용자의 리뷰 데이터는 상대적으로 모으기 힘들다
 2-1. 설문으로 확보할 수 있지만, 시간이 부족하다
 2-2. 차후 사용자의 평가가 추가되면서 필터링에 영향을 주어야 한다</li>
</ol>
<p>위 기준을 바탕으로 <strong>옷과 코디에 대한 직간접적인 평가를 사용</strong>할 수 있고, <strong>하이브리드 방식으로 필터링이 가능</strong>한 <code>LightFM</code>을 사용해서 추천 모델을 사용해보려 했다.</p>
<p>그렇게 조사를 마치고 현재 시스템에 어떻게 적용할 수 있을지 고민하는 과정에서 몇 가지 문제점이 생각났다.</p>
<h3 id="문제점-1-아이템-간의-조합-추천">문제점 1. 아이템 간의 조합 추천</h3>
<p>기본적으로 우리는 <code>코디</code>를 추천하기로 했다. 다시 말해서 <code>옷의 조합</code>을 추천해야 한다. 여기서 조금 LightFM을 적용하기 어려운 문제가 생겼다.</p>
<p>LightFM은 행렬 분해를 사용한다. 설명하자면 기존의 <strong>평가 행렬을 2개의 작은 행렬로 분해하고, 이를 다시 합하여 빈 칸에 예측하는 방법</strong>을 사용한다. 연산이 정확하지 않으나, 그림으로 흐름을 표현하자면 아래의 그림과 같이 설명할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/2f2da3e1-3f2b-4d61-ae78-c8f7bc092f21/image.png" alt="행렬 분해"></p>
<p>이를 우리의 서비스에 적용하려고 하니, 카테고리 갯수에서 문제가 생겼다. 우리는 옷을 &#39;상의&#39;, &#39;하의&#39;, &#39;아우터&#39;, &#39;신발&#39;이라는 4가지 카테고리로 분류하기로 했다. 단순히 상의와 하의가 어울리는지 비교가 가능하다. 하지만 카테고리가 4개가 되면서 연산이 복잡해졌다.</p>
<ol>
<li>상의와 하의에 대해 행렬 분해를 실행해 좋은 조합을 구한다</li>
<li>상하의 조합과 아우터에 대해 행렬 분해를 실행해 좋은 조합을 구한다</li>
<li>상하의, 아우터 조합과 신발을 기준으로...</li>
</ol>
<p>위와 같은 과정이 필요했고, <strong>옷의 카테고리가 추가되거나 카테고리 내의 옷이 추가될 때마다 연산이 복잡해졌다</strong>. 거기에 <strong>부분 집합에 대한 평가가 필요하고, 카테고리 간의 선후관계가 생긴다는 문제</strong>가 있었다.</p>
<h3 id="문제점-2-피드백-적용">문제점 2. 피드백 적용</h3>
<p>2번째 문제는 사용자의 피드백을 적용하기 힘들다는 점이다. 사용자가 우리에게 추천 받는 것은 코디다. 다시 말해서 사용자 입장에서는 옷 하나 하나에 대한 평가가 아니라, <strong>조합된 코디가 어떤지 평가한다</strong>.</p>
<p>이를 적용하려는 협업 필터링에 적용하려면 사용자가 코디를 선택함에 따라 각각의 아이템에 대해 어떻게 평가를 적용할지 추가적인 고려가 필요했다.</p>
<p>특히 사용자마다 취향이 다른데, 똑같은 코디를 줬을 때 <strong>어떤 부분이 마음에 들어서 해당 코디를 선택하는지</strong> 우리가 파악해서 이를 평가에 반응할 방법이 없었다.</p>
<p>결과적으로 우리는 4가지 카테고리의 조합을 평가하기 때문에 LightFM을 적용해서 협업 기반 필터링을 적용하기 힘들다는 결론을 내렸다.</p>
<h2 id="연관-분석">연관 분석</h2>
<p>결국 다시 조사하고, 또 조사했다. 조사하면서 연관 분석에 대해 읽었는데, <code>룰 기반으로 아이템과 아이템 간의 어떤 관계가 있는지 파악한다</code>는 점에서 우리 시스템에 적용할 수 이을 것 같아 더 조사했다.</p>
<h3 id="apriori-알고리즘">Apriori 알고리즘</h3>
<p>연관 분석 알고리즘에서 Apriori 알고리즘을 찾았다. Apriori 알고리즘은 단순하게 말하면 장바구니 추천처럼 <strong>현재 선택한 아이템들에 대해 어떤 아이템의 선택이 많았는지 알려줄 수 있었다</strong>.</p>
<p>정확하게는 아이템이 같이 선택되는 정도를 지지도라고 할때, 아이템의 조합을 구성하면서 <strong>일정 지지도의 아이템 셋에 대해서만 탐색해 연산량을 줄이는 알고리즘</strong>이다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/9871a843-3aeb-4b9a-8b15-69069471037e/image.png" alt="Apriori"></p>
<p>그림처럼 탐색하면 결과로 A, AB, AC, AD, ABC, ABD, ACD, ABCD의 아이템 셋을 구할 수 있다.</p>
<p>이를 바탕으로 아이템 셋을 만들 수 있으니, 사용자 옷장에서 포함되는 아이템 셋을 찾아 탐색을 하면 다른 사람이 많이 고르는 조합을 만들 수 있게 됐다.</p>
<h3 id="데이터-수집">데이터 수집</h3>
<p>알고리즘 구현에 앞서 데이터를 수집하기로 했다. 의류 데이터는 많이 쌓여있는 무신사를 선택했다.</p>
<p>많은 데이터도 선정한 기준이지만 무신사에서 제공하는 가이드를 보고 데이터로 정했다. 실제로 우리가 의류 회사와 협업하면서 가이드를 정할 수 있으면 좋지만, 현재는 시간과 데이터가 없어 가능한 데이터를 사용해보기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/88739bfa-c359-4661-8803-e9faf235651c/image.png" alt="무신사"></p>
<p>특히 너무 많은 데이터 수집은 서비스에 악영향을 줄 수 있다고 판단했기 때문에, 1분에 의류 하나씩 아주 천천히 수집했다. 1분에 하나씩 확인하니 그렇게 많은 데이터를 쌓을 순 없어 2~3일 간 기능 구현이 가능한 정도만 모았다.</p>
<h3 id="사전-조사">사전 조사</h3>
<p>Apriori 알고리즘도 결국 사용자가 선택한 이력을 바탕으로 빈도를 조사하기 때문에 조합에 대한 평가 내역이 필요했다. 그래서 단순히 버튼을 누르면 평가할 수 있는 페이지를 하나 만들어 팀 내에서 평가하기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/2937c689-9553-495b-9c4e-d40317c2bf05/image.png" alt="사전 조사"></p>
<p>빠르게 개발해서 조사하기 위해서 간단한 페이지 하나를 개발했다. 물론 DB에 있는 옷 데이터를 가져와서 사진을 보여줘야 해서 API를 만들면서 페이지 자체를 서버에서 렌더링해서 보내줄 필요가 있었다.</p>
<p>빠른 개발과 간단한 수동 배포가 필요하니 Node를 선택해 사전 조사 페이지를 개발했다. 이전에 현장 실습하면서 Express를 사용해본 경험이 있으니 하루 정도 간단히 개발해 팀 내에 배포했다.</p>
<p>재밌었던 점은 간단하다곤 하지만 HTML 페이지 구성하기 싫어서 GPT에게 물어봤는데 만들어줬다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/18b92508-6c34-4118-a66d-bbe044f5b2da/image.png" alt="gpt meme"></p>
<p>처음에 다른 페이지를 만들어 줬는데, 다르게 디자인할 부분을 몇 번 짚어주니 위와 같은 페이지를 뚝딱 만들어줬다. 개발에 GPT를 사용하는 것을 썩 맘에 들어하는 편이 아니었는데, 이정도면 생각보다 꽤 괜찮게 쓸 수 있지 않을까.</p>
<h2 id="성능-개선">성능 개선</h2>
<p>Apriori 알고리즘의 가장 큰 문제는 트리를 구성하는데, 시간이 오래 걸린다는 점이다. 특히 우리 서비스를 생각하면 새로운 옷이 추가되거나 사용자가 옷을 등록하는 경우가 있는데, 이럴 경우 매번 새로운 트리를 생성하는 것이 매우 힘들다는 생각이 들었다.</p>
<h3 id="옷-특징-추출하기">옷 특징 추출하기</h3>
<p>처음 생각한 개선안은 옷의 특징을 기반으로 트리를 구성하는 것이었다. 옷에는 색상, 두께, 계절감, 핏, 카테고리, 성별이라는 6개의 정보가 있다.</p>
<p>이 중 두께와 계절감은 패션보단 기온과 계절에 따른 착용에 따른 불편함에 관련되어 있다고 생각했다. 그래서 우선 <strong>두께와 계절에 따라 옷을 필터링하는 것에 사용</strong>했다. 기온을 0~28도를 기준으로 임의로 7단계로 분리했다. 이후 계절감과 두께의 포함 여부를 검사해서 입을 수 있는 옷을 필터링했다.</p>
<p>성별도 마찬가지로 입을 수 있는지에 대한 여부를 분류하고, 남은 특성은 색상, 핏, 카테고리로 나뉘었다. 색상이 약 40가지, 핏이 5가지, 카테고리는 상의, 하의, 원피스, 신발, 아우터 5가지로 분류되었다. 이를 언더바를 기준으로 합쳐서 옷의 특성을 나타내는 문자열을 만들었다. 이제 이 특성을 기준으로 조합에 대한 평가를 기록하고, 이를 Apriori 알고리즘에 사용했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/a6c41b24-eb9b-4840-97e9-225e49659400/image.png" alt="아이템 셋"></p>
<p>이제 옷의 특징에 대해서 평가가 이뤄지므로 새로운 옷이 추가되어도 매번 트리를 만들 필요가 없고, 트리를 생성하는데 전체 옷에 대한 트리를 생성하는 것보다 적은 시간이 걸린다.</p>
<h3 id="옷장-탐색하기">옷장 탐색하기</h3>
<p>이제 아이템 셋을 매번 구성할 순 없지만, 옷의 특징을 기준으로 이뤄지면서 조합을 찾기 위해 매번 옷장을 탐색해야 하는 문제가 있었다.</p>
<p>순차 탐색을 계속 진행하면 옷의 갯수가 늘어날 때마다 조합을 완성하기 위한 탐색 횟수가 기하급수적으로 증가할테니 다른 방법이 필요했다.</p>
<p>그래서 <strong>map 자료구조, Python에선 dict 자료구조를 사용했다. 특징을 Key로 가지고 옷 리스트를 저장</strong>했는데, Python에서는 dict에 list형이 값이 변하기 때문에 바로 넣을 수가 없었다. 그래서 defaultdict를 사용해서 list형을 넣고 진행했다.</p>
<p>어떻게 보면 옷장을 정리하는 과정이 추가된 것이다. 하지만 조합을 탐색하기 위해 옷들을 여러 번 탐색해야 하고, 조합이 여러 개 요구되는 경우 이를 여러 번 탐색해야 해서 이렇게 구성해 시간을 많이 줄일 수 있었다.</p>
<h3 id="아이템-셋-필터링">아이템 셋 필터링</h3>
<p>알고리즘을 통해 아이템 셋을 추출하는 것에 mlxtend 라이브러리를 사용했는데, 결과가 DataFrame으로 구성되어 조합을 입력하면 키를 통해서 금방 결과를 찾을 수 있었다.</p>
<p>여기서 착안하여 결과(추가되는 아이템)의 길이가 1인 결과만 필터링하여 전체 아이템 셋의 1/3가량으로 범위를 좁힐 수 있었다. 이제 Python의 DataFrame을 사용해서 특징 조합을 Key로 넣으면 다음 특징을 바로 찾을 수 있게 되었다.</p>
<p>여기서 아이템 셋의 결과가 독립적인지 아닌지는 큰 의미가 없고, 입력 아이템 셋과 결과 아이템이 얼마나 같이 있는지(=지지도) 중요하므로 support_only 옵션을 통해서 더 빠른 계산이 가능하게 했다.</p>
<h3 id="pickle-저장">pickle 저장</h3>
<p>이렇게 만든 결과는 Python의 pickle 파일로 저장해 빠르게 저장하고, 빠르게 로드하여 DataFrame 그대로 사용할 수 있도록 했다. DB에 저장하는 것도 방법이었다. 하지만 <strong>추천 정보에 대해 버전을 유지할 필요가 없고, 최신 추천 아이템 셋만 유지하면 되기 때문</strong>에 오히려 DB와의 통신 시간이 더 느리다고 생각했다. 그래서 자체 파일 시스템을 바탕으로 pickle로 저장했다.</p>
<h1 id="밤-새고-밤-새고-밤-새기">밤 새고, 밤 새고, 밤 새기</h1>
<p>구현한 내용을 최대한 기록해두고 싶어서 히스토리를 기록하니 많이 길어졌다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/bf81b5f4-9cdf-43cd-850d-1f93468b5770/image.png" alt="길어"></p>
<p>조사한 내용이 저렇고 여기에 실제로 프로그램 코드 작성이 남았다. 기획을 포함해서 4<del>5주다보니 개발 기간이 2</del>3주 가량 밖에 없어 마지막엔 밤을 좀 샜다.</p>
<h2 id="프로젝트-마무리-하기">프로젝트 마무리 하기</h2>
<p>다행히 팀원이 RabbitMQ를 통해서 메시지를 통해 프로그램 동작이 가능하도록 구성해주어, 이를 연결하고 오류만 잡으면 됐다. 물론 그게 오래 걸려서 밤을 샜지만.</p>
<p>다른 기능도 많지만 이는 GitHub에서 확인할 수 있으니, 내가 개발한 옷장 기반 코디 추천과 쇼핑몰 의류 추천 기능만 넣어봤다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/3bf42a2b-8fec-4f3a-a860-00283f83583d/image.webp" alt="추천"></p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/cc3aabbb-f43b-415f-9dc2-ae93af048715/image.webp" alt="쇼핑몰 추천"></p>
<h1 id="후기">후기</h1>
<h2 id="새로운-도메인의-도전">새로운 도메인의 도전</h2>
<p>이번 프로젝트는 공부하던 서버도 아니고, 내가 하고 싶었던 데이터 분산 처리도 아니지만 Python으로 아예 새로운 기능을 만들어보는 기회가 되었다.</p>
<p>아직도 어엿한 Back-end 개발자라고 하기 힘들겠지만, 그래도 CRUD 위주의 프로젝트가 4~5개는 쌓이면서 새로 프로젝트를 해도 똑같이 진행되지 않을까 생각을 했다. 그런데 이번에 아예 새로운 방식으로 하지 않았던 기능을 개발하면서 <strong>어떻게 새로운 개념을 익혀서 사용하는지 환기하는 경험</strong>이 된 것 같다.</p>
<p>데이터 사이언스로 넘어가는 것은 없던 생각도 쏙 들어갔지만, 환기된 경험으로 기존 서버 개발도 어떻게 잘 개발할지 고민하면서 시도해봐야 겠다고 생각한다.</p>
<h2 id="데이터-부족">데이터 부족</h2>
<p>데이터에 따른 추천을 해보면서 제일 큰 문제는 데이터의 부족이었다. 실제로 프로젝트를 완성했음에도 필터링 과정에서 적절한 데이터가 없어 조합할 데이터가 없어 에러가 나는 경우가 잦았다. 그에 반해 사전 조사에선 기온이나 성별에 대한 필터링은 있으나, 수집한 데이터를 모두 사용하다보니 랜덤으로 배치했음에도 100% 괜찮지는 않아도 조합을 확인하다보면 생각보다 괜찮은 결과도 나왔다.</p>
<p>그래서 꽤 많은 데이터를 모았다면, 더 정확하고 재밌는 결과를 만들 수 있었지 않을까하는 후회가 있다. 물론 서비스에 영향을 덜 끼치기 위해 적은 데이터를 모았다고 하지만 아쉬운건 사실이다.</p>
<p>데이터를 다루는 공부를 해보고 싶다고 말만 했지 실제로 데이터가 많이 수집하는 것은 굉장히 어려웠다. 상담에서 이런 점을 얘기도 해봤는데, 공공 데이터와 적당한 스크래핑이 답변으로 돌아와서 참 애매하다는 생각이 들었다.</p>
<h2 id="개념과-수단의-분리">개념과 수단의 분리</h2>
<p>Python으로 진행하면서 제일 부족했던 부분은 예외 처리였다. 비록 어플리케이션 서버를 구성하는 것이 아니었지만 엄연히 <strong>실시간 서비스를 가정하고 구성했으니, 이에 따른 예외 상황을 처리하는 것이 필요</strong>했다. 대표적으로 사용자가 등록해둔 옷이 없거나, 필터링 시 적합한 옷을 찾을 수 없는 경우가 그랬다. 그래서 예외가 발생할 때마다 <strong>수정할 수 밖에 없었고, 코드 품질이 낮아지는 결과</strong>로 이어졌다.</p>
<p>프로젝트를 마무리하고 다음날 곰곰히 생각해봤는데, 결국은 개념과 수단의 분리가 덜 이뤄졌다고 생각했다. Spring을 사용해서 진행할 땐 잘만 처리하던 예외 상황을 Python으로 진행했다고 못잡는다? 이는 그냥 <strong>예외를 어떻게 처리할지 Spring이라는 수단에 종속</strong>되어서 그렇다고 생각한다. 앞으로는 어떤 개념을 익혀도 수단에 종속적인 특징이 아니라면 이를 다른 수단에 적용할 수 있도록 노력해야겠다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/0c1fbd8e-0729-4495-8a81-bded30fa8ebb/image.png" alt="분리수거"></p>
<h1 id="다음-목표">다음 목표</h1>
<h2 id="다시-서버로">다시 서버로</h2>
<p>아직 SSAFY에서 진행할 수 있는 프로젝트가 1번 더 남았는데, 이번엔 서버 어플리케이션 개발을 맡고 싶다. 특히 정합성이나 캐싱 등 서비스를 고도화할 수 있는 프로젝트를 진행해보고 싶다.</p>
<p>특히 동시성 문제와 같이 CRUD에서도 충분히 발생할 수 있는 상황에 대해 공부해서 이를 해결해보고 싶다. 내가 모르는 것이 너무나 많다.</p>
<h2 id="미뤄둔-공부">미뤄둔 공부</h2>
<p>마지막에 툭하면 밤을 새면서 작업을 진행하다보니 체력이 너무 부족했다. 그래서 블로깅하는 양도 줄어들고, 익히고 싶어서 공부한 내용도 적었다. 다음 프로젝트 초기에 기획/설계 기간에 미뤄둔 공부를 좀 더 하고 싶다.</p>
<h3 id="데이터베이스">데이터베이스</h3>
<p>저번 회고에서도 작성했는데, 데이터베이스 공부를 더 하고 싶다. 원래 이번에 분산 처리를 공부하면서 Hadoop을, 그리고 이걸 저장할 수 있는 DB 구조 설계와 가능하면 NoSQL 공부까지 해보고 싶었다. 하지만 데이터 추천으로 업무가 바뀌면서 목표로 했던 공부를 못했다.</p>
<p>막상 다른 DB로 milvus라는 Vector DB를 사용했는데, AI를 통한 이미지 검색을 위해 DB가 변경되면서 2주도 안남기고 진행하다보니 제대로 공부하지도 못하고 적용해서 이도 저도 되지 못한 것이 아쉽다.</p>
<p><a href="https://product.kyobobook.co.kr/detail/S000001810370">몽고 DB 가이드</a>를 읽다 말아서, 이를 계속 읽어보고 싶다. 그리고 이전에 언급한 책들도 읽어서 정리하고, <a href="https://product.kyobobook.co.kr/detail/S000001810370">Real MySQL8.0</a>도 읽어보고 싶은데 읽을 책이 너무 많다.</p>
<h3 id="jpa">JPA</h3>
<p>JPA 책을 읽어서 블로깅하는 목표가 있는데, 아쉽게 1/4정도만 적고 한 2/3 정도 읽었는데 더는 진행을 못하고 있다. 이를 다 정리해서 블로깅해두는 것이 좋을 것 같다.</p>
<h3 id="아키텍처">아키텍처</h3>
<p>이번에 추천 기능을 다루면서 같이 보진 못했는데, SSE나 Message Queue를 적용한 것을 봤다. 저런 아키텍처를 구성하는 공부를 해보고 싶다. 개념부터 정리해두고 아키텍처 설계에 활용해보고 싶어, 미뤄둔 공부를 빨리 마무리하고 아키텍처 관련으로도 공부를 진행해보고 싶다.</p>
<h2 id="취업">취업</h2>
<p>새로운 기술을 익히고, 새로운 프로젝트를 진행해보는 것은 언제나 즐겁다. 하지만 나 혼자서는 한계가 존재하고, 살아가는덴 직업이 필요하다.</p>
<p>그래도 이왕이면 나랑 잘 맞고, 내가 성장할 수 있는 곳을 찾고 싶다. 너무 동화 속 이야기라고 할 수 있지만, 꼭 찾을 수 있다고 생각한다. 그러니까 다음 프로젝트도 노력하고, 취업 준비도 노력해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 Primary Key가 1, 2, 3이예요?]]></title>
            <link>https://velog.io/@cloud_365/%EC%99%9C-Primary-Key%EA%B0%80-1-2-3%EC%9D%B4%EC%98%88%EC%9A%94</link>
            <guid>https://velog.io/@cloud_365/%EC%99%9C-Primary-Key%EA%B0%80-1-2-3%EC%9D%B4%EC%98%88%EC%9A%94</guid>
            <pubDate>Wed, 13 Mar 2024 23:52:15 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>데이터베이스에 대해 이론을 익히고, 설계를 진행하다보면 이론이랑 다른 점이 꽤 보인다. 그 때, 가장 이해하지 못했던 부분이 기본키가 1, 2, 3과 같은 단순 숫자인 경우였다. 매번 예시로 대학생 학번 같은 예시를 들면서 이게 무슨 경우인지 정말 헷갈렸다.</p>
<p>그 때 기본키에 대해 가진 의문을 정리해본 글이다. 그런 의미에서 <a href="https://velog.io/@cloud_365/FK%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-DB">FK를 사용하지 않는 DB</a>와 시리즈로 봐도 좋을 것 같다.</p>
<h1 id="primary-key">Primary Key</h1>
<p>당연히 설명을 위해 Primary Key의 개념이 필요하다.</p>
<h2 id="키-key">키? Key?</h2>
<p>데이터베이스는 릴레이션 안에서 하나의 레코드(row)에 대해 정합성을 보장한다. 그래서 레코드를 하나의 고유한 값으로 보장하는 것을 지원하는데, 여기에 사용되는 것이 키(Key)다.</p>
<p>데이터베이스는 키를 바탕으로 레코드가 고유한 값임을 보장하고, 레코드를 찾거나 정렬을 수행하는 데 사용한다.</p>
<h2 id="키의-종류">키의 종류</h2>
<p>키는 알겠는데, 기본키(Primary Key)는 무엇일까. 어떤 값을 보고 기본키라고 하는 걸까. 키의 종류를 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/1a443dde-1496-4b34-885e-1fe637c76e44/image.png" alt="키의 종류"></p>
<h3 id="슈퍼키">슈퍼키</h3>
<p>각 레코드를 유일하게 식별할 수 있는 <strong>유일성</strong>을 갖춘 키다. 유일성이란 표현을 사용했지만 앞서 말한 키의 개념을 그대로 옮겨 쓴 것이다.</p>
<h3 id="후보키">후보키</h3>
<p>슈퍼키 중에서 <strong>최소성</strong>을 만족하는 키를 말한다. 키는 하나의 값만 사용하지 않는다. 극단적으로 말하면 레코드 하나가 하나의 키가 될 수 있다. 하지만 그렇게 키에 종속된 값이 많으면 조금만 값이 수정되어도 다른 레코드로 인식될 것이다. 그래서 사용할 수 있는 값이 가장 작은 집합으로 키를 만드는데, 이것이 <strong>최소성</strong>이다.</p>
<p>예를 들어 어제 내가 <code>카레</code>를 먹었다고 하자. 오늘은 <code>라면</code>을 먹었다. 그러면 <code>카레를 먹은 나</code>와 <code>라면을 먹은 나</code>는 다른 사람일까? <code>나</code>라는 값이 고유함을 증명하는데, <code>메뉴</code>가 키에 포함되면서 문제가 생겼다. 우리는 적절하게 레코드의 고유성을 확인하기 위해 최소성을 만족해야 한다.</p>
<h3 id="기본키">기본키</h3>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/effb8873-5d15-4c34-a694-68ae2054d1da/image.png" alt="PK"></p>
<p><strong>유일성</strong>과 <strong>최소성</strong>을 만족한 키 중에서 하나를 <strong>선택하여 사용하는 키</strong>다. 레코드를 식별할 때, 기준이 되는 키로 <strong>개체 무결성</strong>에 의해 레코드는 반드시 하나의 기본키를 가진다.</p>
<blockquote>
<p>MySQL과 같은 InnoDB는 기본키를 클러스터드 인덱스(Clustered Index)로 사용하기 때문에 신중히 선택해야 한다.</p>
<p>인덱스에 대해선 해당 포스팅에선 별도로 다루지 않고 다른 포스팅을 통해 다룰 생각에 있다.</p>
</blockquote>
<h3 id="대체키">대체키</h3>
<p>기본키를 선정하고 남은 후보키를 대체키라고 한다.</p>
<h1 id="자연키와-인조키">자연키와 인조키</h1>
<p>앞서 설명한 기본키를 설정함에 대해 우리는 <strong>자연키</strong>를 사용하는 방법과 <strong>인조키</strong>를 생성해서 사용하는 방법이 있다.</p>
<h2 id="자연키">자연키</h2>
<p>흔히 데이터베이스를 배우면서 설정하는 교과서적인 키다. 데이터 측면에서 볼 때, 고유한 값임을 확인할 수 있는 컬럼을 말한다. 대표적인 예시로 학번, 이메일, 아이디, 품번 같이 한 객체가 가지고 사용하는 데이터를 들 수 있다.</p>
<p>자연키를 사용하는 장점은 <strong>가독성</strong>이다. 학번이나 아이디가 고유한 값임을 우리는 비즈니스 로직에서 쉽게 짐작할 수 있다. 뿐만 아니라 값 자체에 의미를 가지기 때문에 별도로 해석이 필요하지 않다.</p>
<p>자연키의 문제점은 <strong>자연키는 변할 수 있다</strong>는 점이다. 환경은 언제나 변화하기 때문에 지금 보기엔 절대 변화가 없더라도 1년, 2년 지나면서 자연키에 대한 환경이 변할 수 있다. 기본키가 바뀐다는 것은 결국 데이터베이스 전체를 고치는 것에 가까울 것이다.</p>
<p>예를 들어, 주민등록번호는 우리나라에서 1명만 가질 수 있는 절대적인 기본키로 보인다. 실제로 기본키로 많이 사용됐다. 하지만 2014년 8월 개인정보보호가 강화되고 근거 없는 주민등록번호 수집이 금지되면서 더 이상 주민등록번호를 새로 받거나 저장할 수 없으므로 기본키로 유지할 수 없는 환경이 됐다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/14378277-1055-45a3-a7af-924744299fc4/image.png" alt="안행부 포스터"></p>
<h2 id="인조키">인조키</h2>
<p>자연키와 대비되게 오로지 키의 역할을 수행하기 위해 생성한 컬럼, 데이터를 <strong>인조키</strong>라고 하며, 이렇게 키의 역할을 기존의 키가 아닌 다른 컬럼을 만들어서 할당하기 때문에 <strong>대리키, 대체키(Surrogate Key)</strong> 라고도 한다.</p>
<p>인조키를 생성하면 이는 오로지 데이터베이스 내에서 키로만 사용되기 때문에 <strong>변경 가능성을 차단할 수 있다.</strong> 뿐만 아니라 비즈니스 로직과 무관한 값을 사용하므로 외부에 키를 노출할 경우에도 내부적인 정보를 노출하지 않고 키를 사용하게 할 수 있다.</p>
<p>그리고 인조키를 통해 <strong>2개, 3개의 컬럼을 사용하는 기본키를 인조키 1개로 줄일 수 있다.</strong> 이는 FK를 생각해보면 어떤 이점을 가지는지 알 수 있다. 다른 테이블에서 FK로 여러 기본키를 가져갈 필요 없이 하나의 키만 가져가서 해결할 수 있다. 이는 데이터베이스 구조와 쿼리문의 단순화의 이점으로 이어진다.</p>
<p>하지만 별도의 컬럼을 생성해서 키를 생성하므로, <strong>추가적인 저장 공간이 소요</strong>된다는 단점이 있다.</p>
<h1 id="auto-increment와-uuid">auto increment와 UUID</h1>
<p>인조키를 생성하는 방법에도 약간의 차이가 있는데, 두 방법에 대해 알아보자.</p>
<h2 id="auto-increment">auto increment</h2>
<p>MySQL의 auto_increment나 PostgreSQL의 Sequence처럼 시작해서 레코드가 추가됨에 따라 순차적으로 값을 증가시키는 방식이다. </p>
<p>순차적으로 값이 증가되기 때문에 자료형 내에서 <strong>유일한 값임을 보장한다.</strong> 삽입 순서에 가깝기 때문에 <strong>비교적 가독성이 좋고, 속도가 빠르다는 장점이 있다.</strong></p>
<p>하지만 <strong>분산 DB 환경(샤딩)</strong>이라면 각각의 DBMS에서 생성한다면 id 값의 중복이 발생할 수 있기 때문에 <strong>어플리케이션으로 PK 생성을 옮겨 실행해야 한다.</strong></p>
<p>뿐만 아니라 보안적인 문제도 있는데, 순차적으로 값이 증가함을 보장하기 때문에 오히려 외부에서 키를 예측하기 쉬워진다. 예를 들어 이번 포스팅이 1450이라는 PK를 가지면, 우리는 다음 포스팅의 PK가 1451임을 알 수 있다. 이를 통해 <strong>악의적인 공격에 노출되기 쉽고, 매크로나 크롤러와 같은 자동화 도구에 노출되기 쉽다</strong>는 문제가 있다.</p>
<h2 id="uuid">UUID</h2>
<p>UUID는 랜덤한 값을 통해 <strong>실질적으로 유효함</strong>을 추구한다. 보통 128비트 길이를 사용하는데, 생일 문제를 생각하면 랜덤한 값이지만 유효할 확률이 높음을 예상할 수 있다.</p>
<p>개발 환경에 대해 온전히 독립적으로 랜덤하게 생성된 값이기 때문에, 생성만 할 수 있으면 된다. 다시 말하면 <strong>분산 DB 환경에서도 생성만 하면 키로 사용</strong>할 수 있어 추가적인 작업을 해주지 않아도 된다. 뿐만 아니라 순차적으로 값이 증가하지 않으므로 순차적으로 접근한들 모든 데이터를 찾을 수 없다.</p>
<p>하지만 UUID는 길고 랜덤한 값을 사용하기 때문에 <strong>가독성이 떨어지고, 많은 용량을 차지한다.</strong> 그리고 전체가 랜덤한 값이므로 <strong>정렬이 필요한 데이터에 대해 별도의 컬럼을 사용</strong>해야 한다는 단점이 있다.</p>
<h2 id="그래서-어떻게-하라구요">그래서 어떻게 하라구요?</h2>
<p>방법을 보면 전부 일장일단이 있다. auto increment 방식을 통해 외부에서 예측할 수 있는 정보(ex) 페이지 수, 데이터 갯수)가 많아 UUID를 쓰는게 좋아보이지만, B Tree를 쓰는 데이터베이스의 인덱스 구조상 auto increment 방식으로 인접한 데이터를 같이 저장하는 것이 더 효율적으로 보인다.</p>
<p>어쩌면 합치는 방법도 고려해볼 수 있다. 외부에 UUID를 노출하고, 내부적으로 id와 매핑하는 방식은 어떨까? auto increment를 주 인덱스로, UUID를 보조 인덱스로 사용해서 저장 공간을 많이 사용하지만 외부와 내부에서 사용할 검색값을 모두 저장하는 방식도 있다.</p>
<h1 id="결론">결론</h1>
<p>매번 포스팅마다 <code>은총알은 없다</code>는 이야기를 한다. 하지만 이번 만큼은 자연키와 인조키의 비교에서 인조키의 손을 들어주고 싶다. 가독성을 조금 포기하더라도 변경 가능성을 차단하는 점에서 인조키를 포기하기 힘들다. 무엇보다 테이블 간의 관계를 설정할 때, 인조키를 써보면 얼마나 FK를 저장하기 쉬워지는지 느낄 수 있다.</p>
<p>생성 방식의 경우엔 개발 목적에 따라 충분히 다르게 선택해야 한다. 하지만 내부적으로 auto increment를 사용해도 unsigned int를 사용하면 대략 40억 데이터를 테이블에 저장할 수 있어, 충분한 저장 공간을 보장할 수 있다고 생각한다. 그래서 메인 키로 auto increment를 사용하고, 노출을 막고자 하는 데이터에 대해 보조키로 UUID를 사용하는 방법에 손을 들어주고 싶다.</p>
<h1 id="참고">참고</h1>
<p><a href="https://mangkyu.tistory.com/287">대체키를 PK로 설정해야 하는 이유</a>
<a href="https://stir.tistory.com/294">UUID vs Auto increment</a>
<a href="https://warmth424.tistory.com/14">자연키 vs 인조키</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring HTTP Call]]></title>
            <link>https://velog.io/@cloud_365/Spring-HTTP-Call</link>
            <guid>https://velog.io/@cloud_365/Spring-HTTP-Call</guid>
            <pubDate>Tue, 12 Mar 2024 09:01:21 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>이전부터 RestTemplate를 아닌 새로운 방법을 사용해야겠다고 인지하고 있었는데, 새 프로젝트를 진행하면서 RestClient를 사용했다. 그 과정에서 여러 글을 참고했는데, 해당 포스팅은 그 글들을 모아 정리한 내용이다.</p>
<p>그래서 별다른 추가 내용을 적지 않아, 원 글들이 진짜 내용이라 생각해 참고를 먼저 작성했다.</p>
<h1 id="참고">참고</h1>
<h3 id="resttemplate-deprecated">RestTemplate Deprecated</h3>
<p><a href="https://velog.io/@doxxx93/is-resttemplate-deprecated">뭐? RestTemplate가 Deprecated라고?</a>
<a href="https://effortguy.tistory.com/285">RestTemplate Deprecated 루머 해결</a></p>
<h3 id="restclient">RestClient</h3>
<p><a href="https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient/">Spring 6.1 RestClient 공식문서</a>
<a href="https://mangkyu.tistory.com/303">[Spring] Spring Boot3.2에 새롭게 추가될 RestClient</a>
<a href="https://octoping.tistory.com/41">Api 보낼 때 RestTemplate, WebClient.. 그리고 RestClient?</a></p>
<h1 id="not-resttemplate">Not RestTemplate</h1>
<ul>
<li>엄밀히 말하자면, Deprecated 된 것은 아님</li>
<li>Spring 5가 나오면서 WebClient를 통해 RestTemplate를 대체하고자 함<ul>
<li>RestTemplate가 Spring 3에서 출시되었는데, 이에 대해 오랜 시간이 흐르면서 사용되는 클래스가 많아졌다고 함</li>
<li>이에 비동기를 지원하는 WebClient로 대체하고 했음</li>
</ul>
</li>
</ul>
<h1 id="not-webclient-why">Not WebClient, Why?</h1>
<ul>
<li>WebClient가 나왔지만 RestTemplate가 자리 잡아 잘 사용하지 않았음</li>
<li>비동기 요청을 진행하는 경우 block 메소드를 추가로 사용해야 한다<ul>
<li>해당 메소드를 사용하는 것은 안티 패턴으로 추천하지 않는다고 함..</li>
</ul>
</li>
<li>WebClient를 사용하기 위해서는 아래를 오로지 WebClient를 사용하기 위해 추가해야 함</li>
</ul>
<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-webflux&#39;</code></pre>
<ul>
<li>webflux 모듈은 Spring 5 부터 추가되었는데, 나중에 살펴보는 걸로…</li>
</ul>
<h1 id="restclient의-등장">RestClient의 등장</h1>
<ul>
<li><p>WebClient가 webflux를 사용해야 한다는 문제에서 기인하여 만들어진 서블릿 기반의 WebClient로 보면 됨</p>
</li>
<li><p>Spring 6.1, Spring Boot 3.2부터 사용 가능함</p>
</li>
</ul>
<h1 id="restclient-사용하기">RestClient 사용하기</h1>
<h2 id="restclient-객체-생성하기">RestClient 객체 생성하기</h2>
<pre><code class="language-java">RestClient restClient1 = RestClient.create();
RestClient restClient2 = RestClient.create(restTemplate);
RestClient restClient3 = RestClient.builder()
                .baseUrl(&quot;&lt;https://example.com&gt;&quot;)
                .build();</code></pre>
<h2 id="get-요청">GET 요청</h2>
<pre><code class="language-java">String result = restClient.get()
  .uri(&quot;https://example.com&quot;)
  .retrieve()
  .body(String.class);</code></pre>
<ul>
<li>필요에 따라 Entity의 형태로 받을 수 있다</li>
</ul>
<pre><code class="language-java">ResponseEntity result = restClient.get()
  .uri(&quot;&lt;https://example.com&gt;&quot;)
  .retrieve()
  .toEntity(String.class);</code></pre>
<ul>
<li>RestTemplate와 동일하게 Message Converter를 사용함</li>
</ul>
<pre><code class="language-java">Pet pet = restClient.get()
  .uri(&quot;https://example.com/pets/{id}&quot;, id)
  .accept(APPLICATION_JSON)
  .retrieve()
  .body(Pet.class);</code></pre>
<h2 id="post-요청">POST 요청</h2>
<pre><code class="language-java">ResponseEntity&lt;Void&gt; response = restClient.post()
  .uri(&quot;https://example.com/pets/new&quot;)
  .contentType(APPLICATION_JSON)
  .body(pet)
  .retrieve()
  .toBodilessEntity();</code></pre>
<h2 id="error-핸들링">Error 핸들링</h2>
<pre><code class="language-java">String result = restClient.get()
  .uri(&quot;https://example.com/this-url-does-not-exist&quot;)
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -&gt; {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);</code></pre>
<h2 id="exchange">Exchange</h2>
<ul>
<li>좀 더 자세한 설정을 위해 exchange 메소드 사용 가능</li>
</ul>
<pre><code class="language-java">Pet result = restClient.get()
  .uri(&quot;&lt;https://example.com/pets/{id}&gt;&quot;, id)
  .accept(APPLICATION_JSON)
  .exchange((request, response) -&gt; {
    if (response.getStatusCode().is4xxClientError()) {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
    }
    else {
      Pet pet = convertResponse(response);
      return pet;
    }
  });</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인 JWT로 만들었어, JWT가 유행이잖아]]></title>
            <link>https://velog.io/@cloud_365/%EB%A1%9C%EA%B7%B8%EC%9D%B8-JWT%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%97%88%EC%96%B4-JWT%EA%B0%80-%EC%9C%A0%ED%96%89%EC%9D%B4%EC%9E%96%EC%95%84</link>
            <guid>https://velog.io/@cloud_365/%EB%A1%9C%EA%B7%B8%EC%9D%B8-JWT%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%97%88%EC%96%B4-JWT%EA%B0%80-%EC%9C%A0%ED%96%89%EC%9D%B4%EC%9E%96%EC%95%84</guid>
            <pubDate>Fri, 08 Mar 2024 00:43:19 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>토이 프로젝트를 진행하면서 흔히 로그인 인증 과정을 만들게 되면 자연스레 JWT로 만들어야지라는 이야기를 듣는다. 그런데 막상 JWT를 쓰기 시작하면 왜? 라는 생각이 꼬리를 문다.</p>
<p>프로젝트로 회원 부분을 구현하면서 JWT에 대해서 많이 되짚어보게 되었고, 당시에 개념을 확립하기 위해서 많이 노력했다.</p>
<p>나만 그런 줄 알았는데, 주변에서 처음 로그인을 구현한다고 하면 &#39;JWT 잘 앎?&#39;이라는 질문이 날아온다. 그리고 JWT의 기본적인 특징을 몰라서 &#39;어떻게 해야하죠?&#39; 라는 식의 대화가 나오는 경우도 잦았다. 그래서 한 번 정리해두면 좋을 것 같아 글을 작성했다.</p>
<h1 id="http의-무상태성-stateless">HTTP의 무상태성 (Stateless)</h1>
<p>우선 거슬러 올라가 HTTP의 무상태성을 알 필요가 있다.</p>
<h2 id="stateless란">Stateless란?</h2>
<p>HTTP는 서버-클라이언트 구조를 가진다. 그래서 클라이언트에서 서버에 요청을 보내고, 응답을 받는 구조로 진행된다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/cfbf15b7-fa67-4b48-96c8-d284889aaa41/image.png" alt="누구세요"></p>
<p>이 때, 서버는 클라이언트에 대한 정보를 저장하지 않고 완료된 요청을 기억하지 않는다. 다시 말하면 클라이언트가 요청을 보내서 응답을 받고, 다시 요청을 보내면 이전 요청과 독립적인 관계가 된다.</p>
<p>그래서 Stateless 상태로 통신을 진행하면 클라이언트는 매번 이전 요청에 대한 정보를 추가로 덧붙여 보내야 한다. ( ex) 장바구니에 상품을 담아둬도, 결제 시엔 장바구니 리스트를 보내야 함)</p>
<h2 id="왜-stateless인가">왜 Stateless인가</h2>
<p>이전 요청을 기억하지 않아서 클라이언트가 계속 추가적으로 데이터를 붙여야 하는데, 왜 이런 구조를 사용하는 걸까?</p>
<p>Stateless는 서버가 클라이언트에 대한 정보를 저장하지 않기 때문에, 서버 설계가 간단하다. 그리고 어떤 서버에 요청해도 동일하게 요청을 처리한다는 특징을 가진다. 이런 특징은 분산 서버 환경에서 어떤 서버에 요청을 해도 동일한 처리가 진행한다는 이야기이고, 곧 Scale-out에 대한 이점으로 이어진다. 뿐만 아니라 요청에 실패하거나 장애가 발생해도 금방 대응이 가능하다는 이점이 생긴다.</p>
<blockquote>
<p>간단히 정리하면 다음과 같다</p>
</blockquote>
<ol>
<li>서버와 클라이언트가 느슨하게 결합된다</li>
<li>서버의 설계가 간단하다</li>
<li>서버 규모 확장/축소가 쉬움 (Scale-out)</li>
<li>충돌 후에도 쉽게 다시 시작이 가능하다</li>
</ol>
<h1 id="쿠키cookie">쿠키(Cookie)</h1>
<p>그래서 HTTP가 Stateless를 하는 이유를 알겠는데, 서비스를 만들다 보면 상태값을 저장해야 하는 경우가 있다. 대표적으로 회원 기반 서비스를 떠올릴 수 있다. 회원 정보를 바탕으로 서비스를 제공해야 하는 경우, 요청이 어떤 회원이 보냈는지 알 수 있어야 한다.</p>
<p>이렇게 추가적으로 필요한 정보가 필요한 경우 클라이언트 측 브라우저에 정보를 저장해둔다. 그리고 요청에 포함 시켜 보낼 수 있도록 만든 것이 쿠키(Cookie)다. 클라이언트가 데이터에 대한 부하를 지기 때문에 서버 측에선 적은 부하로 많은 데이터를 유지하는 방법으로 사용할 수 있다.</p>
<p>그래서 흔히 여러 사이트를 돌아다니면 쿠키 수집에 동의를 구한다. 이는 쿠키를 통해서 사용자의 추가 정보를 요청에 보낼 수 있게 하기 위함이다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/8b531282-026c-4c69-8a3e-5ccd17d90800/image.jpg" alt="쿠키"></p>
<p>하지만 쿠키는 사용자가 요청에 넣어주는 구조를 가지고 있기 때문에 요청의 길이가 길어지고, 그에 따른 크기에 제약이 있다. 뿐만 아니라 삭제하기 너무 쉽고 제일 큰 문제는 위변조하거나 정보를 탈취 당하기 쉽다는 문제가 있다.</p>
<h1 id="세션session">세션(Session)</h1>
<p>Session은 쿠키의 문제점을 서버측에서 해결해보고자 구현한 기술이다. </p>
<ol>
<li>세션 id를 생성해서 서버는 세션 id에 매핑해서 추가적인 정보를 저장해둔다</li>
<li>서버는 클라이언트에게 세션 id를 전달한다</li>
<li>클라이언트는 서비스 요청 시 세션 id를 쿠키로 같이 전달한다</li>
<li>서버는 세션 저장소에서 세션 id로 검색해서 정보를 사용한다</li>
</ol>
<p>세션은 중요 정보를 서버측에 저장해두기 때문에 탈취나 위변조에 대한 걱정이 없다. 하지만 서버에 다시 데이터에 대한 부하를 가지게 되는 문제가 있다.</p>
<p>뿐만 아니라 분산 환경에서 문제가 발생할 수 있는데, 각 서버는 세션 저장소를 공유하지 않는다는 점이다. 예를 들어 A 서버에서 인증을 진행한 유저가 다음 요청을 했을 때, 로드 밸런싱이 B 서버로 이어지면 동일한 서비스지만 인증 정보가 유지되지 못하기 때문에 새로 인증 요청이 진행된다. 이런 문제는 별도의 Sticky Session으로 해결할 수 있다.</p>
<h2 id="sticky-session">Sticky Session</h2>
<p>Sticky Session은 단어처럼 특정 세션을 특정 서버에 챡 하고 붙이는 개념이다. 앞선 예시에서 A 서버에서 인증한 후 B 서버에 인증 정보가 없어서 문제가 되니, 해당 세션의 요청을 전부 A 서버에만 보내는 방식을 사용한다.</p>
<p>하지만 머릿속에서 한 번 그려보면 Sticky Session의 문제가 보인다. 예를 들어, 특정 서버에서 로그인 작업이 빈번히 진행되었다면? 모든 서비스가 해당 서버에 집중될 것이다. 그러면 결국 로드 밸런싱을 사용하는 의미가 없이 서버가 부하를 감당하지 못할 것이다.</p>
<blockquote>
<p>Sticky Session의 단점</p>
</blockquote>
<ol>
<li>로드 밸런싱이 제대로 되지 않아 특정 서버에 과부하가 올 수 있다</li>
<li>특정 서버가 실패하면 서버에 있는 세션 정보가 모두 소실된다</li>
</ol>
<p>단점을 보완하기 위해 서버를 클러스터화하거나 세션 서버를 따로 두는 방법이 있다.</p>
<h1 id="jwt-json-web-token">JWT (Json Web Token)</h1>
<p>Session의 이야기를 들어보면 알겠지만, Session 자체는 쿠키를 활용한 Stateful한 방식이다. 매 요청마다 세션 id를 저장소에서 검색하는 과정을 거쳐야 하고, 많은 요청에 대해서 부하가 발생한다. 그리고 Stateful한 방식은 HTTP의 장점이었던 Scale-out에 대한 이점을 잘 살릴 수 없는 방법이다.</p>
<p>그래서 JWT가 등장했고, 큰 인기를 끌었다. JWT는 뜻 그대로 JSON 형태의 웹에서 사용하는 토큰이다. 서비스에 로그인 적혀 있으면 JWT가 보이고, 사이드 프로젝트를, 토이 프로젝트를 열어보면 JWT로 가득하다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/61f8213c-91d7-4039-8770-b87b954384ee/image.png" alt="JWT는 어디든 있다"></p>
<p>왜 이렇게 인기를 끌게 되었는지 JWT를 좀 더 자세히 살펴보자.</p>
<h2 id="구조">구조</h2>
<p>우선 JWT의 구조에 대해 살펴보자. JWT는 헤더, 페이로드, 시그니처라는 3부분으로 구분되어 있다. 그리고 각 부분은 Base64로 인코딩해서 <code>.</code>으로 구분한다. 다시 말해서, 전체 구조는 <code>헤더.페이로드.시그니처</code>로 구성되고, 각각 Base64로 인코딩되어 있다.</p>
<h3 id="header">Header</h3>
<p>시그니처 생성을 위해 어떤 알고리즘을 사용할지, 이 토큰은 어떤 토큰인지 기록하는 부분으로 alg와 typ으로 이뤄진다.</p>
<p>typ은 JWT로 사용하기 때문에 <code>JWT</code>로 사용한다.
alg는 어떤 알고리즘을 가지고 시그니처 부분을 암호화할지 정한다.</p>
<pre><code class="language-json">{
  alg : &quot;HS256&quot;,
  typ : &quot;JWT&quot;
}</code></pre>
<h3 id="payload">Payload</h3>
<p>실질적인 토큰의 정보가 저장되어 있는 부분이다. 이렇게 저장되어 있는 정보의 각 부분을 클레임(Claim)이라고 한다.</p>
<p>iss(토큰 발급자), exp(만료 시각)과 같은 표준 클레임도 있고 클라이언트와 서버 간에 임의로 지정한 정보를 넣는 비공개 클레임도 존재한다.</p>
<pre><code class="language-json">{
  id : 1,
  exp : 1480849147370
}</code></pre>
<h3 id="signature">Signature</h3>
<p>이 토큰이 올바른 토큰인지 확인하는 부분으로 시그니처가 없다면 단순히 문자열에 불과하다. 시그니처는 헤더와 페이로드를 Base64로 인코딩한 뒤 <code>.</code>으로 연결한다. 인코딩한 값을 비밀 키를 사용해서 암호화를 진행하고 암호화된 값을 다시 Base64로 인코딩한다.</p>
<h2 id="왜-jwt인가">왜 JWT인가?</h2>
<p>JWT에서 Signature를 보면 비밀키로 암호화한 값임을 알 수 있다. 그리고 시그니처는 헤더와 페이로드를 암호화한 값이다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/56779e93-416e-481c-b929-08cd29e8196d/image.png" alt="시그니처 비교"></p>
<p>그렇다. 우리는 이미 있는 값에 비밀키만 알고 있으면 해싱을 진행해서 시그니처와 비교할 수 있다. 비밀키를 통해 이 토큰이 우리가 발급한 올바른 토큰인지 확인할 수 있다.</p>
<p>이 특징을 통해서 세션과 다른 강점을 가진다.</p>
<ol>
<li>저장소를 거치지 않아도, 비밀키만 있으면 인증을 진행할 수 있다</li>
<li>분산 환경에서도 비밀키만 공유된다면 인증을 진행할 수 있다</li>
</ol>
<p>1번 강점을 통해서 서버는 저장소에 갔다 오는 부하를 줄일 수 있다. 그리고 2번 강점은 특히 기존의 세션 방식에 비해 분산 환경에서 충분히 활용 할 수 있다는 장점이 있다. 그래서 MSA 환경이 급부상하면서 분산 환경에서 유용하게 쓸 수 있는 JWT의 인기가 많아졌다.</p>
<h2 id="jwt를-사용하면서-생각해야-할-부분">JWT를 사용하면서 생각해야 할 부분</h2>
<p>열렬한 인기에도 불구하고, JWT에 대해 명확한 개념이 없는 채로 유행하니까~ 블로그에 레퍼런스가 많으니까~ 라는 이유로 사용하는 경우가 잦다. 그러면 이미 JWT에서 보증하는데 추가적인 공수가 들어가거나 잘못 사용하는 경우가 생긴다.</p>
<h3 id="payload-읽기-문제">Payload 읽기 문제</h3>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/8df01425-85f1-44b7-be6d-da7fdd6de527/image.png" alt="이게 뭔데"></p>
<p>처음 JWT를 접하면서 많이 하는 실수는 Payload에 크리티컬한 정보를 저장하는 것이다. 보통 인증을 위한 토큰이라고 이야기하고, 눈에는 문자의 나열로만 보이니 정확한 개념이 없으면 발생하는 문제다.</p>
<p>하지만 설명에 있듯이 Signature를 제외하면 Header와 Payload는 Base64로 인코딩되어 있기 때문에 단순히 디코딩을 통해서 해당 내용을 읽을 수 있다. 다시 말해서 무슨 정보를 저장하든 토큰을 읽을 수만 있으면 어떤 정보가 담겨 있는지 알 수 있다. <strong>절대 노출되면 안되는 정보는 JWT에 담지 말자</strong></p>
<h3 id="만료-시간-예측-문제">만료 시간 예측 문제</h3>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/86d23f7d-6348-45d6-b48e-fc2e5bbe08b9/image.webp" alt="푸 흐린 눈"></p>
<p>Payload에 노출되면 안되는 정보가 담기는 것과 반대로 JWT를 인증을 위한 신성한 토큰을 생각하면 발생할 수 있는 문제다.</p>
<p>예를 들어, JWT의 내부를 고려하지 않고 JWT의 만료 여부를 판단한다고 생각해보자. 생각할 수 있는 방법은 흔히 2가지 정도가 나올 것이다.</p>
<ol>
<li>JWT를 서버에 한 번 전송해서 에러 메시지가 반환되는지 확인한다</li>
<li>유효 기간 정보를 미리 알고, JWT를 발급 받는 시점에서 만료 시간을 저장해두고 비교한다</li>
</ol>
<p>우선 1번 방법은 불필요한 네트워크 통신을 발생 시킨다. 예를 들어, 사이트 재접속 시 기존 토큰이 만료되었는지 파악이 필요한 경우가 있다. 그래서 아무 API를 호출하는 경우가 있는데, 이러면 불필요한 네트워크 통신이 있을 뿐만 아니라 의미 없는 서버 연산이 동반될 수 있다.</p>
<p>2번 방법은 클라이언트에서 불필요한 부하를 지게 된다. 유효 기간 정보를 미리 알고 있어야 하는 부분이나 각 토큰의 만료 시점을 추가로 저장해야 하는 문제가 있다.</p>
<p>Payload는 Base64로 인코딩 되어 있기 때문에 어떤 클라이언트에서도 Base64로 디코딩하면 Payload의 내용을 읽을 수 있다. 그리고 <strong>Payload에 흔히 exp(만료시간)이 저장되어 있기 때문에 해당 토큰이 만료된 토큰인지 유효한 토큰인지 파악</strong>할 수 있다.</p>
<p>Payload를 읽음으로 클라이언트는 JWT 내부를 확인하고, 남은 시간에 따라 적절히 토큰을 재요청할 수 있다. <strong>개념에 대해 서버와 클라이언트 양측의 적절한 이해가 필요하다.</strong></p>
<h2 id="jwt-탈취">JWT 탈취</h2>
<p>JWT의 가장 큰 문제는 탈취의 위협이다. JWT로 인증을 진행하면 아무 값도 매핑하지 않기 때문에 저장소에서 데이터를 찾지 않는다. 다시 말해, <strong>토큰이 탈취되면 서버는 아무런 값을 매핑하고 있지 않으므로 만료되기 전까지 아무런 대응을 할 수 없다.</strong></p>
<h3 id="refresh-token">Refresh Token</h3>
<p>그럼 탈취에 대해 대비하기 위한 방법을 떠올려 보자. <strong>첫 번째로 토큰에 대해 서버에 저장해두고 매번 확인하는 방법</strong>. 이 방법은 JWT가 가진 Stateless한 장점이 사라지는 문제가 있다. <strong>두 번째 방법은 짧은 유효 기간을 탈취되어도 금방 만료되게 하는 방법</strong>. 이 방법은 사용자는 자주 로그인 요청을 받게 되므로 UX에 악영향을 끼칠 수 밖에 없다.</p>
<p>이에 두 해결책을 적절히 섞어 각각의 단점을 해결하기 위해 Refresh Token이란 방법이 있다.</p>
<p>서버는 클라이언트에게 2가지 토큰(Access Token, Refresh Token)을 발급한다.</p>
<ul>
<li>Access Token : 실제 서비스 내 인증에 사용되는 토큰</li>
<li>Refresh Token : Access Token을 재발급 받기 위한 토큰</li>
</ul>
<p>각 서비스 별로 정책이 다르지만 Access Token은 짧은 유효 기간을 가진다. 탈취 당하더라도 금방 만료되어 악용할 여지를 최대한 줄인다.</p>
<p>이에 반해 Refresh Token은 비교적 긴 시간의 토큰을 생성한다. <strong>여기서 Refresh Token만 서버에 저장한다</strong>. Refresh Token에 대해서 서버는 블랙 리스트, 화이트 리스트를 작성하거나 토큰과 정보의 매핑을 삭제 및 수정하는 것으로 Refresh Token에 대해 최소한의 제어권을 가진다.</p>
<blockquote>
<p>최소한의 제어권이라는 이야기는 주변에 Refresh Token을 DB에 저장하는 이유에 대해 설명하면서 나왔다. 만약 Access Token의 탈취를 보완하기 위해 토큰을 분리한 점만 있다면, 다시 Refresh Token 탈취에 대해 고민이 필요할 것이다.</p>
<p>그러면 Refresh Token 탈취를 대비한 Refresh Refresh Token을 만들어야 할 것이고, 또 그 토큰의 탈취를 대비한 Refresh x 3 Token... 쭉 이어질 것이다.</p>
<p>결국 Refresh Token은 탈취의 위험을 인정하고, 최대한 Stateless의 이점을 가져가면서 탈취 시 후처리를 추가했다는 개념으로 이해했다.</p>
</blockquote>
<h3 id="vs-session">vs Session</h3>
<p>흔히 Refresh Token의 개념을 이야기하면 Session과 다른 점이 뭐냐는 질문이 돌아온다. DB와 같은 저장소에 저장하지 않기 위해 JWT를 썼는데, 다시 저장소에 저장한다니 이상한 결론이다.</p>
<p>여기엔 유효 시간에 재밌는 점이 있다. Session은 인증이 필요한 요청에 대해 저장소를 접근하는데 반해, Refresh Token은 Access Token이 만료될 때까지 저장소에 접근하지 않는다. Access Token의 만료 시간만큼 세션과 저장소 접근 횟수의 차이가 발생한다.</p>
<p>하지만 Refresh Token을 저장하는 시점에서 Refresh Token을 분산 환경에서 어떻게 접근해서 처리할지 고민이 더 필요하다.</p>
<h3 id="rtr-refresh-token-rotation">RTR (Refresh Token Rotation)</h3>
<p>Refresh Token가 탈취되면 후처리로 접근을 막는다지만, 이 방법은 탈취된 사실을 알았을 때 의미가 있다. 그래서 Refresh Token 탈취 자체에 대해서 방지하고 싶을 수 있다. 이런 부분에 대해선 Refresh Token Rotation을 사용할 수 있다.</p>
<p>RTR은 단순하게 말해서, Access Token 재발급 시 Refresh Token을 재발급함으로 Refresh Token을 1회성으로 만드는 방법이다.</p>
<p>1회성으로 만들면서 Refresh Token이 가지고 있는 긴 유효 기간에 대한 문제를 해결한다. 뿐만 아니라 이전에 발급한 Refresh Token을 활용한 Access Token 발급 요청이 다시 들어오면, 해당 Refresh Token의 탈취 여부를 파악할 수 있다.</p>
<p>예를 들어, 사용자가 Refresh Token을 사용하고 해커가 이전 Refresh Token으로 발급 요청을 보내면 공격이 있음을 파악할 수 있다. 반대로 해커가 먼저 Refresh Token을 얻어 재발급 요청을 하면 사용자가 재발급 요청을 보내면서 Refresh Token에 대한 탈취 여부를 파악할 수 있다.</p>
<h1 id="반드시-jwt">반드시 JWT?</h1>
<p>JWT가 유행하다보니 로그인이 들어간 토이 프로젝트에 전부 JWT를 사용하는 모습도 볼 수 있다. 개발자는 충분한 고려와 이유를 가지고 기술을 선택해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/5207e8c8-7220-4b25-8e59-a91401ae0cd7/image.png" alt="JWT vs Session"></p>
<h2 id="jwt는-문제가-없을까">JWT는 문제가 없을까?</h2>
<p>JWT의 구조를 보면 알지만 결국 탈취된 Access Token에 대한 위험을 방지할 수 없다. Refresh Token을 쓰기도 하지만 한 번 크리티컬한 요청을 허용할 수도 있다는 문제가 있다.</p>
<p>그리고 JWT 자체의 길이가 길어 세션에 비해 많은 데이터를 요청에 같이 보내야 하고, 이에 대해 세션에 비해 높은 네트워크 부하가 발생할 수 있다.</p>
<p>Refresh Token에 대한 정보가 저장해서 사용하면서 분산 환경의 세션에서 제시되었던 문제가 동일하게 발생한다. 각 서버는 재발급 요청에 Refresh Token 정보를 가지고 있어야 한다.</p>
<p>이에 인증 서버를 두기도 한다. 물론 세션 서버 방식과 다르게 인증 서버에 접근하는 것은 재발급 요청에만 접근하고 처리되므로 완전히 같은 방식은 아니다. 하지만 해당 서버가 멈추면 인증 정보가 사라지는 등의 문제는 세션 서버와 유사한 문제로 남는다.</p>
<h1 id="결론">결론</h1>
<p>보안과 성능은 어느 정도 줄다리기의 이미지를 가진다고 생각한다. 물론 세션에 꽁꽁 싸매어 저장하는 것이 제일 좋은 방법이지만, 제일 중요한 것은 서비스의 성격이다.</p>
<p>빠른 처리를 요구하는 서비스인지, 높은 보안을 요구하는 서비스인지 개발에 앞서 고려하고 채용하는 것이 중요하다고 생각한다.</p>
<p>물론 토이 프로젝트에서 공부를 위해 JWT를 사용할 수 있다고 생각하지만, 기술의 선택엔 이유가 있어야 한다고 느낀다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 쩝쩝박사 개?발]]></title>
            <link>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-%EC%A9%9D%EC%A9%9D%EB%B0%95%EC%82%AC-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-%EC%A9%9D%EC%A9%9D%EB%B0%95%EC%82%AC-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Sun, 18 Feb 2024 14:18:06 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>SSAFY 공통 프로젝트를 마무리하고 회고를 적으면서 동아리에서 진행했던 쩝쩝박사도 배포가 되었는데, 여기에 대한 회고도 간단하게라도 적어야하지 않을까? 라는 마음으로 작성했다.</p>
<h1 id="개발">개발</h1>
<p>개발 시작에서 배포까지 꽤나 지연된 프로젝트다보니 생각보다 기억나는 부분이 꽤 적다. 그럼에도 배포가 완료되어서 이 과정과 중간에 생긴 이슈를 작성해봤다.</p>
<h2 id="프로젝트-시작">프로젝트 시작</h2>
<p>2022년 7월부터 개발을 시작했는데, 이전부터 기획이 진행되어 와이어 프레임이 나온 상태에서 BE 인원이 먼저 API 개발을 진행할 수 있도록 먼저 인원이 투입되었다. 당시 5명으로 시작했다. 당시 동아리에서 쓰지 않던 JPA를 사용하기로 했고, 그에 따라 엔티티 설계에 시간이 조금 들었다.</p>
<p>엔티티 설계를 담당한 인원이 정말 잘했는데, 엔티티 설계와 기능 하나를 개발한 뒤에 취업에 성공하면서 한두달 가량 뒤에 빠지게 되었다. 취업을 축하하면서 4명의 팀원으로 프로젝트를 이어갔고, 나는 다른 팀원과 함께 로그인 &amp; 유저를 담당하여 개발을 진행했다.</p>
<h3 id="spring-security">Spring Security</h3>
<p>특히 Spring Security를 사용해보자는 이야기에 반감을 표했으나, 결국 Spring Security를 사용해서 진행하게 되었다. SNS 로그인을 제외한 유저 부분을 내가 맡기로 했는데, 지금 생각해보면 그 때 Spring Security를 쓰자는 말을 뜯어 말렸어야 했지 않았을까.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/cbdae313-e279-4325-a368-456a624b8a93/image.png" alt="노"></p>
<p>당시에 Spring Security는 생각보다 인터넷에 있는 자료들은 버전이 낮아 변경된 문법이 많았다. 개발을 진행하면서 다른 사람이 Spring Security를 사용할 것까지 생각하면 문서화도 진행해야 했기 때문에 시간이 꽤나 오래 걸렸다. 심지어 당시에 장기 현장 실습을 나가 있어서 해가 떠있는 동안에는 회사에서 개발하고 해가 지면 집에서 Spring Security 공부를 했었다. 8월 쯤엔 2주가량 2~4시간 정도 자면서 회사에 나가고 밤엔 공부 및 개발을 하는 생활을 하다보니 많이 예민해졌던 기억이 난다.</p>
<p>동아리에서 보통 프로젝트를 6개월 정도로 진행했고, 쩝쩝박사도 마찬가지였다. 그래서 보통 1달 정도로 API 개발을 어느 정도 완성하고 이후 클라이언트를 투입하고 클라이언트 쪽에서 부족한 요구사항에 대한 유지보수 및 버그 픽스 등을 진행하는게 일반적이었다. 하지만 새로운 기술에 대해 공부와 병행하며 도입하니 API 개발 자체가 2달 정도로 지연되어 다른 클라이언트 트랙에 미안한 마음이 꽤 들었다.</p>
<h2 id="api-완성">API 완성</h2>
<p>그렇게 2달 정도 바득바득 하고 나니 API 같은 윤곽이 잡혔다. 기본적인 기능이 제공되었고, 유저와 리뷰 같은 서로 의존성이 존재하는 기능 간의 연결도 완성됐다. 다만 상점 API 개발에 있어서 저작권 및 요금에 대해 기획과 잦은 마찰이 있어서 메인 기능이 제일 늦춰지는 문제가 있었다.</p>
<p>로그인 기능에 FE가 붙으면서 당시에 Refresh Token에 대해서 많이 찾아보고 고민했다. Refresh Token을 어디 저장해야 하는가? 그러면 세션과 다른 점이 뭘까? Refresh Token을 탈취 당한다면? 꼬리에 꼬리를 물면서 당시 Refresh Token에 대해서 주변에 물어보러 다니고 많이 고민했다. 그 과정에서 Refresh Token에 대한 개념 뿐만 아니라 어떤 개념을 깊게 고민하고 각각의 견해에 대해 생각하는 방법에 대해 많이 배웠다.</p>
<p>뿐만 아니라 이메일 인증을 처리하면서 안드로이드 측의 요청으로 Firebase의 Dynamic Link를 사용해서 디바이스에 따라 리다이렉션을 별도로 지정했다. 원래는 JS를 통해 리다이렉션을 구성했지만, 브라우저에서 자동 리다이렉션을 막는다는 것을 보고 변경하게 되었다.</p>
<h2 id="개발-지연">개발 지연</h2>
<p>그렇게 API의 기본적인 윤곽이 잡히고, 장기 현장 실습을 진행하는 동안 프로젝트가 뭔가 이상해졌다. 로그인 연결 이후 별 다른 API 사용이 보이지 않고, API에 대한 수정 요청이나 문제 제기가 이뤄지지 않았다. 지금 생각하면 아마 당시 BE 인원의 수가 많아 별도의 프로젝트를 추가로 진행했는데, 나머지 클라이언트 트랙이 다른 프로젝트에 투입되면서 쩝쩝박사 프로젝트에 큰 힘을 못쓴 것으로 생각된다.</p>
<p>거기에 당시 나는 장기 현장 실습에서 기초 교육이 마무리되고 실무에 투입되었다. 새로운 과제가 계속 주어졌기 때문에 지연되고 있는 프로젝트에 신경 쓰지 못했다. 1주마다 진행했던 프로젝트 회의에선 API가 완성된 채로 별도의 유지보수 요청이 없었기 때문에 우리끼리 API를 보수하면서 진행했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/1ce1c22d-875a-4498-843b-44298109230d/image.png" alt="노관심"></p>
<p>거기에 우리 동아리는 교내 동아리인 만큼 시험 기간에 대해 휴식 기간을 잠깐씩 가졌는데, 대부분 졸업반이고 실습 활동을 하는 인원이 대부분이라 BE는 별도의 휴식을 진행하지 않았다. 원래는 이게 실습 등 다른 활동과 병행하면서 뒤쳐진 진도를 따라가기 위함이었는데, 개발이 지연되면서 오히려 우리가 질문하면 시험 기간에 겹쳐 2주 간 기획이나 디자인에 대한 답변을 얻지 못하기 일쑤였다.</p>
<p>결국 내가 현장 실습을 했던 6개월이라는 시간 동안 프로젝트는 완성되지 못했고, 개발 인원의 불만만 키운 채 다음 단계로 나아가질 못했다.</p>
<p>그대로 프로젝트 인원 전체가 프로젝트와 함께 동결되었다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/f516116f-e91f-4bac-a6db-8597f57fb6b6/image.png" alt="냉동인간"></p>
<h2 id="1차-해동">1차 해동</h2>
<p>여기서 사실 API는 4개월 차쯤 서로 코드를 보고 문제점을 찾아 유지보수 하는 작업도 거의 끝났고, 인원들이 갈피를 못잡기 시작했다. 분명 실제로 작동하게 되면 문제가 생길텐데, Swagger로만 작업하고 있으니 더 이상 진행하지 못하고 불안하기만 했다.</p>
<p>거기에 6개월이 지나고, 나를 포함한 2명이 현장 실습이 종료되었지만 나머지 2명이 현장 실습을 시작했다. 시간이 계속 지연되면서 다른 동아리원이 안된다고 생각했는지 클라이언트 측에서 API를 사용하려 했는데, 또 다른 인원을 통해서 내 귀에 API가 기획과 달라 사용하기에 불완전하다는 이야기가 들렸다.</p>
<p>문제가 무엇인지 살펴보았다. 동아리에 별도로 기획 팀이 없다보니 디자인 팀이 아이디어가 아닌 세부 기획을 같이 진행했다. 와이어 프레임에서 화면 설계로 넘어오면서 디자인이 수정되었고, 그에 따라 기획이 조금씩 변경된 것이다. 클라이언트 입장에선 디자인된 화면으로 컴포넌트를 개발하니, API가 기획가 맞지 않은 상태였다.</p>
<p>그래서 API와 기획이 다른 부분을 찾아 하나씩 기능을 수정해나갔는데, 여기서 새로운 문제가 생겼다. 디자인 측에서 디자인을 이렇게 해보고 저렇게 해보면서 기능이 생겼다 사라졌다 했다. API 개발하는 입장에선 1~2주씩 들여서 개발하고 있으면 해당 기능이 사라지고, 해당 페이지에서 필요한 API를 다 추출해서 완성했다고 생각했는데 필요한 API가 생기기도 했다. 한 번은 어떤 기능인지 이해가 되지 않아 질문을 남겼는데, 답변보다 기능 삭제가 빨리 이뤄진 경우도 있었다.</p>
<h2 id="1차-배포">1차 배포</h2>
<p>결국 디자인은 계속 수정되고, API도 완성 상태에서 계속 미완성 상태로 내려갔다. 그런 상황이다보니 전체적으로 리딩할 사람이 필요하다고 생각했는지 새로운 인원을 투입했다. PM으로 투입된 인원은 개발이 지연되는 이유에 대해 의견을 받았고, 나는 가감없이 기획 변경으로 인한 API 변경을 1순위, 인력 공백에 따른 개발 및 리뷰 지연을 2순위로 뽑았다.</p>
<p>기획 변경에 대한 불만이 꽤 많았는지 하루는 날을 잡았다. 기획을 보면서 1차 배포 전까지 기획 변경이 없이 진행할 수 있도록 의견을 교환했다. 나는 내가 이럴 줄 몰랐는데, 개발자가 안된다고 말했다의 주인공이 됐다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/1dbf08f1-aa1a-422c-ae47-cb39f49242fb/image.png" alt="오늘도 개발자가 안된다고 말했다"></p>
<p>예를 들면, 유저가 이메일, 아이디, 비밀번호, 닉네임을 모두 수정할 수 있게 화면이 구성된 경우가 있었다. 아이디가 변경되는 이유는 유저 검색 시 자신을 더 잘 표현할 수 있는 아이디가 필요하다는 이유였다. 이 경우 회원에 대한 고윳값이 하나도 남지 않아, 로그인 뿐만 아니라 DB에 남는 이력에 대해서 어떤 회원이 어떤 회원인지 알 수가 없었다. 이외의 비슷한 상황에 대해 의견을 전달해야 했다.</p>
<p>이외에도 이번엔 몇 개월의 시간을 더 가지고 1차 배포를 진행하기로 했기 때문에 기간 상 실행할 수 없는 기능에 대해서도 할 수 없다는 의사를 비췄다.</p>
<p>그렇게 다시 개발이 진행되는듯 싶었으나, 현장 실습으로 인한 인원 공백 등의 문제와 함께 다시 클라이언트가 참여하기 힘들어지면서 회원 측 기능을 완성하고 다시 프로젝트가 동결됐다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/1a8740b8-256c-497d-a59e-b9ba3e9b6da5/image.png" alt="황장군"></p>
<h2 id="production-배포">Production 배포</h2>
<p>이후 나머지 2명의 현장 실습도 종료됐다. 현장 실습이 6개월 정도니 프로젝트 시작한지 약 1년 정도 된 것이다. 그동안 나는 SSAFY에 들어갔고, 다른 프로젝트 인원 1명도 나중에 알고 보니 나와 같은 기수로 입과했다고 한다. 다른 인원은 취업했다는 소식도 들렸다.</p>
<p>간간히 API 사용과 API 유지보수 요청이 있었지만 전체적인 프로젝트는 진행되지 못했고, 프로젝트 회의도 간격이 길어지다가 그만하게 되면서 사실상 프로젝트를 끝내려고 했다.</p>
<p>그런데 프로젝트를 Production 배포할 것이란 소식과 함께 다시 개발하러 일어나라는 소식을 받는다. QA 시트를 준비하고, SSAFY를 가고 다른 일을 하면서 직접 참여는 못했지만 QA 소식에 대해 들을 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/b4e30c55-fec8-481a-833d-f969f19dd1f9/image.png" alt="캡틴 아메리카"></p>
<p>그렇게 SSAFY를 진행하거나 취업한 인원이 끌어올려져 다시 API 유지보수를 진행했다. 해동된 캡틴 아메리카 마냥 알고 있던 API를 담당 인원은 어디 가고 새로운 인원과 합을 맞춰 프로젝트를 진행했다. 그리고 약 한 달 만에 프로젝트 Production 배포까지 설정하여 배포가 이뤄졌다.</p>
<p>그렇게 1년 6개월간 냉동 해동을 반복해 흐물흐물해진 프로젝트가 끝이 났다. 생각보다 API를 수정할 내용은 없었고 이렇게 한 달 만에 배포할 수 있는 내용이란 것이 제일 충격이었다.</p>
<h1 id="후기">후기</h1>
<h2 id="실패에-대한-후기">실패에 대한 후기</h2>
<p>6개월짜리 프로젝트가 1년 반이 걸리면서 누가 봐도 규모에 비해 너무 많은 시간이 소요된 프로젝트가 됐다. 그렇다고 완성도가 그렇게 뛰어난지 잘 모르겠다. 냉정히 프로젝트에 실패라는 타이틀을 걸 수도 있을 것이다.</p>
<p>하지만 실패에 대한 회고도 필요하다고 생각한다. 실패를 실패로 두기 보단 왜 실패했는지 찾아보고 배울 점을 배우고 고칠 점은 고쳐야 한다.</p>
<h3 id="프로젝트를-이끌어-나가는-건-누구">프로젝트를 이끌어 나가는 건 누구</h3>
<p>프로젝트를 진행하면서 수평적인 구조가 오히려 아쉬웠다. 제대로 PM이 있는 것도 아니고, BE를 리딩하는 인원이 다른 프로젝트와 병행하면서 프로젝트에 대해서 제대로 토의가 이뤄지지 못했다. 결국은 BE 팀원들의 의견을 모아 목소리를 낼 필요가 있었는데, 수평적인 구조에 갇히고 다른 트랙을 잘 모른다는 점에 갇혀서 제대로 된 의견 교환이 이뤄지지 못했다.</p>
<p>그렇다면 프로젝트는 누가 어떻게 이끌어 가는가. 결국은 우리 모두라는 말을 할 수밖에 없다. 일방적인 의견은 언젠간 부러지고 만다. 우리는 의견의 마찰이 필요하다. 부딪혀서 더 단단한 의견을 만들어야 한다. 이번 프로젝트는 그런 점이 너무 부족했다.</p>
<h3 id="기획자와-한판-승부">기획자와 한판 승부</h3>
<p>프로젝트가 지연되고 여러 번 재기동하면서 부딪힌 문제는 기획, 디자인과의 견해차였다. 기획 내지 디자이너는 대개 정확히 어떤 구조로 프로젝트가 구성되는지 모른다. 이를 설득하고 타협점을 찾아내는 것이 필요하다.</p>
<p>하지만 처음 기획과 부딪히는 것은 힘든 일이다. 여러 번 개발하면서 경험이 쌓인 것이 아니라면 확답을 주기도 힘들고, 업무 영역을 침범하는 행위인지도 걱정된다. 그렇지만 기획과 부딪히면서 제일 중요한 부분은 서로서로 모른다는 점이다. 되는 것과 안 되는 것을 구분하고, 불필요한 수정에 드는 시간 소요를 줄이기 위한 과정이다. 그러니 다음엔 힘껏 부딪힘에 두려워하지 말아야 겠다고 생각이 든다.</p>
<h2 id="기술적으로-아쉬운-점">기술적으로 아쉬운 점</h2>
<p>프로젝트 중간에 SSAFY에 참여하면서 프로젝트에 유지보수에 집중하면서 기능적으로 아쉬운 부분이 많이 남았다.</p>
<h3 id="유저-프로필-presigned-url">유저 프로필 Presigned URL</h3>
<p>유저의 프로필을 업로드하는 API가 있는데, 이에 대해 단순히 이미지 파일을 전송하는 API를 구성했다. 이런 이미지 전송 API는 이미지의 크기가 클수록 커넥션이 오래 걸릴 뿐만 아니라 이를 리사이징 하는 과정이 서버에 꽤 큰 부하가 가해진다.</p>
<p>그래서 클라이언트에서 미리 적절한 이미지로 수정하여 Presigned URL로 버킷에 올리고 서버에 알려 부하를 피할 수 있는데, 이 부분을 개발 당시 고려하지 못했다.</p>
<h3 id="dynamic-link-서비스-종료">Dynamic Link 서비스 종료</h3>
<p>프로젝트 당시에 JS로 자동 리다이렉션이 종료되어 Dynamic Link를 사용한다고 얘기했는데, QA 당시 유지보수를 위해 문서를 확인하면서 Dynamic Link가 곧 종료된다는 것을 확인했다. 프로젝트 기간이 너무 길어짐에 따라 연결된 서비스가 먼저 종료되는 것이다. 이에 따른 추가적인 유지보수가 필요할텐데 제대로 진행하지 못해 아쉬운 부분이 많이 남는다.</p>
<h2 id="최종-후기">최종 후기</h2>
<p>프로젝트를 마무리하면서 드는 생각은 회의감이었다. 약 3년에서 4년가량의 동아리 활동을 지속했고, 특히 쩝쩝박사는 1년 반이라는 시간이 소요된 프로젝트였다. 하지만 소위 말하는 누군가의 멱살 캐리로 당장 끝날 프로젝트가 이렇게 지연됐다는 것이 안타까움을 많이 느꼈다. 특히 이전 동아리 프로젝트도 API를 완성 후 클라이언트 미완성으로 미배포 종료되었는데, 이번에도 거의 비슷한 양상이라 더욱 안타까운 것 같다.</p>
<p>이젠 SSAFY에서 프로젝트를 진행하고, 취업 준비에 본격적으로 들어가면서 더 이상 동아리에서의 프로젝트가 힘들 것 같다. 이번에 쩝쩝박사를 끝낸 인원이 동아리의 다음 프로젝트를 잘 이끌 것이라고 생각한다. 회고에 적은 일 이외에도 크고 작은 일이 몇 번 있었는데, 시간이 오래되면서 어렴풋이 기억난다. 여러모로 아쉬움이 많이 드는 프로젝트였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2024 SSAFY 공통 프로젝트 - "Speechless" 회고]]></title>
            <link>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-SSAFY-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@cloud_365/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-SSAFY-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 18 Feb 2024 10:30:30 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>2024년 1월부터 2월까지 약 6주간 SSAFY 2학기 공통 프로젝트를 진행했다. 블로깅 자체를 프로젝트를 시작하면서 시작했기 때문에, 몇 번의 프로젝트를 했지만 회고를 처음 남겨보면 좋겠다는 생각이 들었다.</p>
<p><a href="https://github.com/insukL/Speechless">프로젝트 주소</a></p>
<h1 id="ssafy-첫-프로젝트">SSAFY 첫 프로젝트</h1>
<h2 id="주제">주제</h2>
<p>어느 팀이든 팀장과 발표자를 선정하는 과정이 제일 힘들다고 생각한다. 실제로 간단히 업무 분장을 하는 과정에서 발표자를 선정하는 과정이 제일 오래 걸렸다. 주제 선정하면서 이 부분이 묘하게 웃겨 생각났고, 이 불편함 아닌 불편함에서 기안하며 우리 팀은 발표, 면접을 연습할 수 있는 플랫폼이라는 주제를 선정했다.</p>
<p>진짜로 아래의 문구를 기획 배경으로 내걸며 프로젝트를 시작했다.</p>
<blockquote>
</blockquote>
<p>팀 프로젝트를 진행하면서 다른 역할은 분담이 쉽게 이뤄졌으나 다들 발표는 꺼려하는 문제가 있었습니다. 그래서 문득 저희 같은 사람들을 위한 프로젝트를 만들어 발표에 자신감을 얻을 수 있으면 좋겠다고 생각해 프로젝트를 시작했습니다.</p>
<h2 id="프로젝트-진행">프로젝트 진행</h2>
<p>프로젝트를 진행하면서 자기소개서, 면접 연습 정보, GPT를 활용한 질문 및 피드백 생성과 관련한 API를 담당했다.</p>
<h3 id="jpa-사용하기">JPA 사용하기</h3>
<p>프로젝트에 들어가기 앞서 제일 큰 문제는 JPA를 제대로 이해한 인원이 없다는 점이었다. 나는 이전 프로젝트를 진행하면서 JPA를 간단하게만 써본 상황이고 다른 BE 인원은 처음 사용하는 프로젝트였다. 이에 JPA 공부를 진행했지만..</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/195a650c-6d3d-4bde-9a4d-48494fbe4e06/image.jpg" alt="끝이 없네"></p>
<p>어느 정도 예상했지만 JPA를 알고 쓰기엔 양이 너무 많았고, 전부 알고 시작하기엔 6주 프로젝트의 대부분을 JPA 공부로 사용하게 생겼기에 공부와 개발을 병행했다. 그러면서 당연히 생각 못한 오류도 엄청나게 나왔다.</p>
<h4 id="에러-1-영속성-전파-문제">에러 1. 영속성 전파 문제</h4>
<p>제일 먼저 JPA를 다루면서 맞닥뜨린 문제는 영속성 전파로 인한 문제였다. 자기소개서와 자기소개서 문항을 1:N 관계로 유지하고 있었는데 이를 한 번에 Insert하려 했다. 이를 한 번에 Entity 객체로 전환하고 이를 Persist하려 했다. 하지만 Entity 설계를 제대로 하지 않아 영속성이 전파되면서 그대로 자기소개서 문항 엔티티들이 연관 관계 매핑이 되지 않아서 그대로 에러가 발생했다.</p>
<p>영속성에 대해 알고 있어서 각각의 엔티티에 우선 매핑하고 저장함으로 해당 문제를 해결했지만, 문제는 해당 문제가 <strong>나한테만 발생하진 않았다는 점</strong>이다.</p>
<p>발표에 대해 여러 명이 참가하면서 참가 정보를 저장하고 있었는데, 참가 정보를 저장하면 발표 그룹에 대한 정보가 같이 삭제되는 문제가 있었다. 찾아보니 cascadeType을 ALL로 지정해뒀는데, 삭제하면서 관련된 엔티티가 모두 영속화되면서 그룹 엔티티도 같이 삭제한 듯 싶었다. cascadeType을 PERSIST로 수정해서 해결했는데, 미리 공부해두고 비슷한 문제를 겪으면서 문제를 빨리 파악하지 못했다면 시간을 많이 쓸 수도 있던 문제였다.</p>
<h4 id="에러-2-연관-관계-문제">에러 2. 연관 관계 문제</h4>
<p>한 번은 로그인하고 자기소개서를 작성했는데, 다른 사람의 자기소개서가 모두 보이는 문제가 있었다. JPA를 통해서 SQL은 올바르게 생성했는데, 출력되는 데이터에 문제가 있으니 로직 확인을 그만두고 데이터를 확인했다. 문제는 <strong>자기소개서의 회원 FK 부분이 NULL로 설정되어 있었고, 전부 NULL이니 당연히 다른 사람의 자기소개서도 확인</strong>할 수 있었다.</p>
<p>데이터에서 어느 부분이 문제인지 확인하고, 이를 바탕으로 매핑이 제대로 이뤄지지 않는다는 결론을 내렸다. 그래서 로직을 다시 확인하면서 연관 관계의 주인을 확인하지 않은 것을 알았다. 로그인한 회원을 확인하고, 회원에 자기소개서를 추가했었다. 하지만 <strong>1:N 관계에서 연관 관계의 주인은 N측</strong>에 있고, ManyToOne인 자기소개서 엔티티엔 유저에 대한 정보가 매핑되지 않으면서 해당 문제가 발생했다.</p>
<p>그래서 <strong>연관 관계에 대해 편의 메소드를 작성</strong>했다. 회원 측 엔티티에 자기소개서를 입력하면 자기소개서에도 회원 측 엔티티가 매핑되도록 작성하니, 그제서야 데이터에 회원 FK가 올바르게 입력됐다.</p>
<blockquote>
<p>두 문제 모두 JPA에 대한 기본기가 부족해서 생긴 문제였다. 문제가 생긴 원인은 공부한 부분을 잊고 설계를 진행한 점이고, 문제를 해결한 부분도 공부한 부분이 떠올라서 해결했다. 기술을 적용함에 있어 작동 배경 등 기본기를 충실히 익혀야 한다는 걸 배웠다.</p>
</blockquote>
<h3 id="openvidu">openVidu</h3>
<p>발표 연습은 여러 사람이 모여서 진행하고, 면접에선 발음 평가를 위해 서버 측에도 음성 데이터가 필요했기 때문에 WebRTC를 사용했다. 하지만 시간이 적고 서버 중간에 미디어 서버 구현을 위해 비교적 쉽게 사용할 수 있는 openVidu를 사용했는데, 여기도 문제가 있었다.</p>
<h4 id="비동기-처리해주세요">비동기 처리해주세요</h4>
<p>서비스 중 입력한 자기소개서에 대해서 GPT API를 통해 질문을 생성하고, 이에 답변을 진행하면 답변을 STT로 변환하여 다시 피드백을 받아오는 기능이 있다. 여기서 문제는 GPT가 요청에 텍스트를 받아 이를 인식하고 반환하는 요청을 로컬에서 테스트하면서 약 15초 가량 걸렸다.</p>
<p>FE에서 Request를 걸어두고 15초보다 더 걸릴지도 모르는데, 오래 연결을 유지하는 것은 timeout을 길게 잡아야 하는 등 우려가 있다는 의견을 나눴다. 이에 메소드 자체를 비동기로 진행하자고 이야기해서 개발을 진행했다. @Async를 통해서 메소드를 비동기로 작동하도록 구성한 것은 좋았으나, 이제 생성한 질문과 피드백을 보내주는 것에 어려움이 있었다.</p>
<p>다행이 이미 면접 내에 카메라 정보를 받아와야 했기 때문에 카메라 영상을 전송하기 위해 소켓 연결이 이뤄져 있었다. 그래서 이미 연결된 openVidu의 signal 기능을 이용해서 결과 메시지를 보내서 해결했다.</p>
<h4 id="에러-3-비동기-transaction-문제">에러 3. 비동기 Transaction 문제</h4>
<p>메소드 내 로직을 비동기로 작동 시키면서 @Async를 처음 사용해봤는데, 이 과정에서 Transaction에 접근할 수 없다는 문제가 생겼다. 찾아보니 @Async는 별도의 스레드를 생성하여 해당 스레드에서 비동기적으로 로직을 진행하는데, 별도의 스레드로 복사하는 과정에서 Transaction이 닫히는 문제였다.</p>
<p>다행히 복잡하게 로직이 얽혀있는 것이 아니라 GPT 요청 후 메시지 전송의 과정을 Util과 Service로 분리해두었기 때문에 @Transactional을 붙여 새로운 Transaction을 생성하는 것으로 해결했다.</p>
<h4 id="에러-4-java-인증서-오류">에러 4. JAVA 인증서 오류</h4>
<pre><code> sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target</code></pre><p>Transaction 문제는 해결했으나 openVidu 서버로 요청을 보내는 과정에서 위 에러가 발생했다. 찾아보니 신뢰할 수 있는 인증서를 찾을 수 없는 문제라고 했다.</p>
<p>인프라 구성을 담당한 팀원과 이야기해봤지만, openVidu 구성 시 인증서를 등록했다고 했다. 실제로 openVidu에 해당하는 포트로 접근하면 Https 접근이 가능한 것을 확인했다.</p>
<p>해당 문제를 해결하면서 밤을 샜는데, 이 뒤에도 몇 번 밤을 샜지만 이 문제에서 2번째로 밤을 샜다. 관련 정보를 한참 찾은 결과 JAVA 내에서 다른 서버로 Https 연결을 요청할 경우 인증서를 등록해야 한다는 문제였다. 결국 openVidu 포트로 접속해 이미 등록된 인증서를 가져와 keytools를 가지고 JAVA에 인증서를 추가하고 문제가 해결됐다.</p>
<blockquote>
<p>openVidu와 관련된 두 에러는 전혀 모르는 곳에서 발생한 에러라 시간을 많이 들였는데, 다시금 에러를 가지고 정보를 찾아보는 과정을 경험할 수 있었다.</p>
</blockquote>
<h3 id="gerrit">Gerrit</h3>
<p>프로젝트를 진행하면서 Gerrit을 사용했다. PR을 바탕으로 한 리뷰에 익숙했었는데, 커밋 단위로 리뷰를 하다보니 적응이 잘 안됐다. 나중에 알고 보니 우리 반에서 우리 팀만 Gerrit을 사용했다. ㅋㅋ..</p>
<h3 id="서기">서기</h3>
<p>위에서 에러 이야기만 가득하지만, 개발 이외에도 기획, 문서화를 모두 진행했기 때문에 개발 이외에도 업무 분장이 필요했다. 나는 회의록 기록 등 문서화와 관련된 부분을 담당했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/e9647592-93ee-42dc-b9d7-197887869ff3/image.png" alt="노션 페이지"></p>
<p>전부 내가 작성한 것은 아니지만, 필요한 문서가 있으면 최대한 공유를 위해 노션으로 페이지를 구성하고 이를 최신화하기 위해 힘썼다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/731722ce-6c80-48ab-bfa9-c9c32bb6bf42/image.png" alt=""></p>
<p>프로젝트 문서 정리 뿐만 아니라 기술 이해가 덜 된 팀원을 위해 기술에 대해서 알려주기도 했는데, 이 과정에서 팀원이 글 정리가 잘 되어 있다고 블로깅을 한 번 해보는 것은 어떻냐고 했다. 이전부터 공부한 기술을 한 곳에 모아두고 싶었고, 생각만 하고 있었는데 권유 받은 김에 블로깅을 시작했다.</p>
<h2 id="프로젝트-마무리">프로젝트 마무리</h2>
<p>생각보다 마지막까지 몰려서 내가 담당한 API의 개발이 끝났기 때문에 남아있는 일을 정리하고 담당자 설정 및 진행도 확인을 진행했다. 일종의 PM 역할을 일주일 간 맡은 셈인데, 발표 자료도 만들어야 했기 때문에 생각보다 체력 소모가 컸다. 하루는 하루종일 책상 근처를 돌아다니며 남은 진행도를 확인했는데, 이 이야기를 했더니 내 성격에 다른 사람을 쪼았다며 구경 갔어야 했다고 했다.</p>
<p>그래도 일주일 간 남아있는 작업 중 가능한 것과 불가능한 것을 확인하고 중간 중간 방향을 확인하니 확실히 작업 속도가 빠른 것이 보였다. 결과적으로 개발 마무리 및 문서 작업은 당초 제출일보다 하루 일찍 끝났기 때문에 다른 팀은 밤도 샌다고 했는데, 우리 팀은 밤은 새지 않고 프로젝트를 마무리할 수 있었다.</p>
<blockquote>
<p>솔직히 처음부터 PM 역할을 도맡아서, 편하게 말해서 쪼았다면 좀 더 계획한 기능을 더 만들고 더 기능에 충실한 프로젝트를 만들 수 있었을까?</p>
</blockquote>
<p>우여곡절 끝에 프로젝트를 완성했다. </p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/9a8504a4-9fa2-483a-808b-7427b6f2d522/image.png" alt="메인 페이지"></p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/f4bc855a-fbd4-4725-acfa-587488bde1d7/image.webp" alt="발표 화면"></p>
<p>결과적으로 프로젝트는 우수 프로젝트가 됐다. 못만들지 않았을까 많이 걱정했는데, 생각보다 결과가 좋게 나와서 다행이라고 생각한다.</p>
<h1 id="후기">후기</h1>
<h2 id="프로젝트-회고">프로젝트 회고</h2>
<h3 id="초기-설계">초기 설계</h3>
<p>프로젝트를 마무리하고 회고를 진행하면서 초기 설계가 부실했다는 말이 제일 많이 나왔다. 위에 작성한 큼직한 에러 이외에도 다른 사람이 발생 시킨 문제도 있고, 자잘한 충돌도 많았다.</p>
<blockquote>
<p>자잘한 문제..?</p>
</blockquote>
<ul>
<li>JWT Payload 내 유저 이메일 저장<ul>
<li>PK를 통해 인덱스를 사용하기 위해 PK 저장으로 수정</li>
</ul>
</li>
<li>DB 설계 시 자료형 설정</li>
<li>히스토리성 컬럼 정규화</li>
<li>파일 데이터 저장</li>
</ul>
<p>하지만 이런 문제 이외에도 Validation이나 기능에 접근하기 위한 사용자의 플로우 등 기획 당시 정했어야 하는 부분을 기술을 모른다는 이야기로 많이 유예했다. 결과적으로 나중에 모두가 바쁜 상태에서 다시 의견을 모으고 결정을 내려야 하면서 시간이 배로 들었다.</p>
<p>뿐만 아니라 짧은 개발 기간에 비해 업무 분담이 제대로 이뤄지지 않으면서 개발이 병렬적으로 진행되지 못했다. 그러면서 특정 기능 개발을 기다리거나 다음으로 무엇을 해야 할지 몰라 결과적으로 기능 개발 자체는 굉장히 빠듯하게 진행됐다.</p>
<h4 id="어디까지-정해야-하는가">어디까지 정해야 하는가</h4>
<p>초기 설계 문제를 겪으면서 든 생각은 &#39;맞닥뜨리는 문제에 대해 어디까지 정해야 하는가?&#39;였다. 새로운 기술을 사용하면 당연히 어떤 Request와 Response, DB Schema가 필요한지 아무도 모른다. 자주 나온 이야기로 &#39;우린 디자이너가 아니다&#39;, &#39;우린 DA, DBA가 아니다&#39;였는데, 이런 부분을 전부 나중에 알고 나면 정하자고 유예하면 너무 오랜 시간이 걸린다.</p>
<p>내가 프로젝트를 하면서 고민한 결과는 책임만 질 수 있으면 된다는 것이다. 확실하지 않은 부분을 정하려니 책임이 필요하고, 그 책임을 지기 싫어 미적지근하게 정하게 된다. 하지만 이렇게 정한 부분은 더 큰 문제가 되고, 더 복잡한 문제만 떠안게 된다. <strong>간단하게 책임을 지자.</strong> 수정이 필요하면 수정하고, 새로 구성해야 하면 새로 만들면 된다.</p>
<h3 id="나는-얼마나-못난가">나는 얼마나 못난가</h3>
<p>프로젝트가 끝나고 느낀 다른 점은 우리는 우리가 얼마나 못난지 알아야 한다는 점이다. 서로 서로가 얼마나 못난지 알아야 프로젝트 일정 관리가 훨씬 편해진다. 부끄러워 하기보단 자신이 얼마나 할 수 있는지, <strong>얼마나 못하는지 당당하게 밝혀야 한다.</strong></p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/c817bcf9-e1fe-4337-b05f-e33e81045234/image.png" alt="못생김"></p>
<p>이번 프로젝트의 경우에 나는 대충 어림잡아 잘하겠지 하고 업무량을 산정했다. 하지만 생각보다 팀원별로 알고 있는 지식이나 기술이 제각각이었고, 이미 산정된 업무량에 비해 우리팀의 인력은 모자람이 보였다. 그 뒤에 다시 공부하면서 진도를 각각 맞추기엔 일정에 어려움이 컸다.</p>
<p>물론 내가 다 잘하면 괜찮겠지만 못났으니까. 얼마나 못났는지 알아야겠다.</p>
<h2 id="나의-후기">나의 후기</h2>
<p>6주하면서 밤도 새고, 새로운 기술도 보면서 고생했는데, 결과가 잘 나와서 다행이다. 그렇지만 기간이 너무 짧아서 아쉬운 부분이 있다. 좀 더 개선하면 좋지만, 다음 프로젝트가 다시 시작되기 때문에 어떤 부분을 가져가고 어떤 부분을 버릴지 고민하는 시간이 필요하다고 생각된다.</p>
<h3 id="인기-글-캐싱">인기 글 캐싱</h3>
<p>프로젝트의 메인 페이지에 인기글을 가져오는 부분이 있어서 매번 요청을 바탕으로 가져오는 부분이 있다. 시간이 부족해 추가적인 시스템 구조를 추가하지 못해서 극단적으로 MySQL만 쓰는 프로젝트가 되었는데, 매번 MySQL에서 가져오는 것을 Redis로 캐싱해주면 좋았을 거라 생각한다.</p>
<h3 id="데이터-모델링에-대한-관점">데이터 모델링에 대한 관점</h3>
<p>초기 설계가 부족했고, 데이터나 엔티티에 대한 설계가 부족했다. 그래서 DB가 자주 수정되면서 오류가 종종 발생했는데, 이에 데이터 모델링에 관해서 공부해 보고 싶다는 생각이 들었다. 물론 DA만큼의 데이터 모델링 능력을 갖추기엔 부족하겠지만, 모형화를 바탕으로 탄탄한 애플리케이션을 구성하고 싶다는 맘이 들었다.</p>
<h2 id="다음으로-할-일">다음으로 할 일</h2>
<h3 id="데이터베이스-공부">데이터베이스 공부</h3>
<p>최근에 어플리케이션에서 데이터베이스 쪽으로 많이 관심을 가지고 있는데, 조금씩 공부를 시작해야겠다. 데이터베이스 구조에 대한 공부나 데이터 모델링에 대해 관심이 간다. 아래 책을 가지고 공부할까 생각만 가지고 있는데, 짬짬히 진행하면 좋을 것 같다.</p>
<p><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=148233799&amp;start=slayer">친절한 SQL 튜닝</a>
<a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=237005175">핵심 데이터 모델링</a>
<a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=278488709">Real MySQL</a></p>
<p>빅데이터를 건들게 되면서 NoSQL을 사용하는 것도 고려가 필요할 것 같다. <a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=266821597&amp;start=slayer">MongoDB 완벽 가이드</a>를 가지고 공부할 생각인데, 조금 걱정이 되긴 한다.</p>
<h3 id="대용량-데이터-공부">대용량 데이터 공부</h3>
<p>다음 프로젝트의 주제를 빅데이터(분산)으로 정했는데, 확정인지 모르겠다. 해당 주제로 된다면 내가 원해서 고른 주제인 만큼 공부를 많이 해야겠다. 대용량를 다루는 프로젝트를 할 일이 별로 없는데, 이번에 기회가 되었으니 관련 기술을 공부해야겠다.</p>
<p>공부할 책은 아래 책을 고려하고 있는데, 과연 저 책들을 빨리 읽을 수 있을지 모르겠다..</p>
<p><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=103031150&amp;start=slayer">하둡 완벽 가이드</a>
<a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=281606911">실전 카프카 개발부터 운영까지</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FK를 사용하지 않는 DB]]></title>
            <link>https://velog.io/@cloud_365/FK%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-DB</link>
            <guid>https://velog.io/@cloud_365/FK%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-DB</guid>
            <pubDate>Sat, 03 Feb 2024 11:40:56 GMT</pubDate>
            <description><![CDATA[<h1 id="외래-키가-없는-rdb">외래 키가 없는 RDB</h1>
<p>데이터베이스의 이론적인 베이스 지식보다 실제 개발을 먼저 접하면서, DB의 물리적인 설계에 외래 키를 설정하지 않고 시작했다. 그래서 실제 개발에선 사용하지 않는 것이 당연하다는 감각으로 진행했고, 이후 DB에 대한 기초 지식을 공부하면서 왜 실제론 제약 조건을 사용하지 않는지 고민했던 경험이 있다.</p>
<p>이후 SSAFY를 진행하면서 프로젝트 DB 설계에 대한 피드백 시간이 있었다. 피드백 중 다른 팀에서 FK를  제거하라는 이야기를 듣고, 무슨 소린지 이해하지 못해 나에게 질문이 한 일이 생겼다. 질문에 대답하면서 예전에 했던 고민이 떠올라 따로 정리해두면 좋다고 생각해 작성했다.</p>
<h1 id="fk-외래-키란">FK, 외래 키란?</h1>
<p>설명에 앞서 정확히 RDB 내에서 FK란  무엇일까?</p>
<h2 id="무결성">무결성</h2>
<p>우리는 생각보다 많은 양의 데이터를 생성하고, 이를 저장하다보면 자연스레 중복이 발생한다. 중복 데이터를 모두 저장하면, 참조하는 데이터에 따라 오류가 발생하고 정확하지 않은 데이터가 제공된다. 결과적으로 원하는 결과의 서비스를 만들지 못하고, 오류가 발생하게 된다.</p>
<p>이런 문제를 해결하기 위해 데이터를 모델링함에 있어, 무결성을 지키고자 설계하게 된다. 그리고 RDB에선 이를 PK(Primary Key)를 통해서 개체에 고유함을 부여하고 이에 대한 <strong>개체 무결성을 보장</strong>한다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/2d723573-b21a-41e4-8601-53638f157300/image.png" alt=""></p>
<p>그림을 보면 수강 과목과 과목을 수강하는 학생에 대한 정보를 같이 저장하고 있다. 그 과정에서 홍길동이란 학생이 중복 입력되나, <strong>학번이라는 고유한 값</strong>을 통해 미적분과 C언어를 수강하고 있는 홍길동이 동일한 학생임을 보장할 수 있다.</p>
<h2 id="정합성">정합성</h2>
<p>하지만 데이터의 종류에 데이터에서 공통으로 사용하는 부분이 중복되고, 이런 데이터는 전체가 관리되지 못하면 한 쪽만 수정이 이뤄져 각 데이터가 공통 데이터를 사용함에도 다른 값을 가지게 된다.</p>
<p>예를 들어, 이전과 동일한 그림에서 홍길동 학생이 기숙사에서 퇴실하게 되었다. 그래서 명부에서 제일 최근 데이터에서 기숙사 여부를 X 처리했는데, 전체 명부를 보면 데이터가 불일치하는 문제가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/fbfa069d-2ed3-43a2-8551-02c4a3f762bc/image.png" alt=""></p>
<p>이렇게 <code>동일한 데이터가 같은 값을 가져야 한다</code>라는 개념이 <strong>정합성</strong>이다. 그림처럼 어떠한 이유로 데이터의 정합성이 맞지 않는 경우, <strong>정합성이 훼손</strong>되었다고 한다. 그리고 위 사례처럼 데이터를 수정하면서, 중복 데이터 중 일부 데이터만 수정되면서 정합성이 훼손되는 경우를 <strong>갱신 이상</strong>이라고 한다.</p>
<p>그럼 정합성을 보장하기 위해 어떻게 해야 할까? 아래 그림처럼 테이블을 분리해보자.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/448ddaea-5622-4e40-8213-b6d4febff060/image.png" alt=""></p>
<p>그림처럼 테이블을 분리하면 학번이라는 고유한 값을 통해 다른 과목임에도 동일한 학생을 가리킨다. 이렇게 되면 학생 데이터가 갱신되어도 과목에 따라 다른 값을 가져올 염려가 없어진다. 이런 과정을 <strong>정규화</strong>라고 한다.</p>
<h2 id="foregin-key">Foregin Key</h2>
<p>위 그림을 그대로 가지고 데이터베이스 설계에 사용해보자. 우린 데이터베이스에서 학생 테이블을 만들었고, 이에 대한 PK로 학번을 만들었다. 그리고 수강 과목 테이블을 만드는데, 학생 테이블의 정보를 그림처럼 가리키고 싶다. </p>
<p>이처럼 정규화 이후 화살표와 같이 특정 컬럼을 가져와 다른 테이블을 레코드를 가리킬 수 있는데 가져온 컬럼을 <strong>FK(Foregin Key), 외래 키</strong>라고 한다. DBMS 상에서 FK에 대한 제약을 사용할 수 있고, 이를 바탕으로 DB 상에서 각 데이터의 정합성, 무결성을 보장할 수 있다.</p>
<h1 id="근데-안-써">근데 안 써?</h1>
<p>이야기를 들으면 데이터의 정합성을 지키기 위해 무조건적으로 FK를 사용해야 할 것 같다. 하지만 배경에서 말한 사례처럼 FK를 제거하고 개발하는 경우가 존재한다. 엄밀히 말하면 <strong>논리적으로 테이블 간의 관계를 설정</strong>해두고, <strong>DBMS 상에서 FK 제약조건만 제거</strong>한다.</p>
<h2 id="fk가-불편한-사람들">FK가 불편한 사람들</h2>
<h3 id="성능-이슈">성능 이슈</h3>
<ul>
<li>FK도 하나의 제약조건으로 데이터를 삽입하는 과정에 있어서 DBMS는 항상 FK를 검사하는 추가적인 과정을 거친다</li>
<li>삭제, 수정하는 과정에도 외래 키 옵션에 따라 추가적인 과정이 소요된다</li>
</ul>
<h3 id="개발-확장-과정에서의-처리">개발, 확장 과정에서의 처리</h3>
<ul>
<li>개발을 진행하면서, 무조건적으로 먼저 부모 테이블을 만들고 부모 레코드를 생성해야 한다. 다시 말해 테이블의 관계에 따라 각 Task 간의 순서가 정해지고, 이 순서에 얽매여 작업이 진행되어야 한다</li>
<li>개발 과정에서 테이블 구조가 수정되는 경우 외래키를 별도로 다 처리하면서 진행하면서 당초 작업보다 더 큰 시간과 계획이 필요해진다</li>
<li>FK가 없는 기존 데이터에 대해서 테이블이 추가되어 확장되며, 기존 데이터와의 정합성이 불일치하는 문제가 발생할 수 있다</li>
</ul>
<h3 id="데드락-이슈">데드락 이슈</h3>
<ul>
<li>FK에 따라 Lock이 전파되기 때문에 부모 자식 테이블을 접근하는 순서에 따라 데드락이 발생할 수 있다</li>
<li>데드락 가능성을 최대한 줄이기 위해 FK 제약 조건 설정하는 경우를 피하는 경우가 존재한다</li>
</ul>
<h2 id="꼭-삭제해야-할까">꼭 삭제해야 할까?</h2>
<p>FK가 불편한 점이 많다고는 하나 삭제함으로 얻는 이점이 있다면, 삭제해서 생기는 부작용도 있다. 뿐만 아니라 경우에 따라 삭제해야 하는 이유가 없어지기도 한다. 고로 우리는 넓게 생각하고 넣는다면 넣는 이유, 삭제한다면 삭제하는 이유에 대해서 고민해야 한다.</p>
<h3 id="성능-이슈-1">성능 이슈?</h3>
<ul>
<li>대부분 FK 제약 조건 검증이 문제가 될 정도로 지연되는 경우는 적다</li>
<li>성능이 아주 아주 중요한 시스템이 아니라면 약간의 성능 상승을 위해 데이터 정합성을 희생하면서까지 삭제할 이유가 없다</li>
</ul>
<h3 id="어플리케이션-내-처리">어플리케이션 내 처리</h3>
<ul>
<li>FK 제약 조건을 삭제하면 제약 조건에 대한 검증을 어플리케이션 측면에서 검증하는 방향으로 수정이 이뤄진다</li>
<li>어플리케이션에서 처리한다고 해도 결국 부모 테이블의 데이터를 가져와 검증하는 로직이 필요하고, 이 과정에서 DBMS 내에서 처리되는 것보다 더 큰 지연이 있다는 의견도 있다</li>
</ul>
<h3 id="대용량-관리">대용량 관리</h3>
<ul>
<li>시스템에 의한 제약 조건 처리에서 사람에 의한 검증 및 처리로 변경되고, 이에 휴먼 에러가 발생할 가능성이 생긴다</li>
<li>현재 내가 진행하는 작은 규모의 프로젝트라면 괜찮지만, 테이블의 갯수나 데이터의 갯수가 훨씬 많아지는 대규모 데이터 프로젝트에선 모든 데이터 관리가 힘들어진다</li>
</ul>
<h1 id="후기">후기</h1>
<p>예전부터 가진 의문에 대한 대답을 좀 더 구체화해보았다. 어떻게 보면 매 프로젝트의 설정에 맞춰 따라가면서 개발했는데, 조금 명확해지는 기분이 든다. 하지만 역시 완벽한 방법은 없다고 생각한다. 성능, 안정성 사이의 줄다리기는 설계를 진행하는 한 계속 사이에 매달려 있어야 하지 않을까</p>
<h3 id="참고">참고</h3>
<p><a href="https://engineering-skcc.github.io/oracle%20tuning/foreign_key_%EC%97%86%EC%9D%B4_%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94_DB/">Foreign Key 없이 구축하는 관계형 데이터베이스 시스템에 대한 생각</a>
<a href="https://okky.kr/questions/586565">대용량 디비에서는 외래키를 안쓰나요?</a>
<a href="https://velog.io/@destiny1616/Foreign-Key-07mrx6w4#%EC%84%B1%EB%8A%A5-%EC%98%A4%EB%B2%84%ED%97%A4%EB%93%9C">Foreign Key</a>
<a href="https://velog.io/@haron/%EC%99%B8%EB%9E%98%ED%82%A4Foreign-Key%EC%99%80-%EB%8D%B0%EB%93%9C%EB%9D%BDDeadLock-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%BF%BC%EB%A6%AC-%EC%A7%80%EC%97%B0-%EC%8B%A4%ED%96%89-eruedsy4">[트러블슈팅 - DB] 외래키(Foreign Key)와 데드락(DeadLock) 그리고 쿼리 지연 실행</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Commit에 이름 짓고 으깨기]]></title>
            <link>https://velog.io/@cloud_365/Git-Squash-Merge</link>
            <guid>https://velog.io/@cloud_365/Git-Squash-Merge</guid>
            <pubDate>Sun, 28 Jan 2024 16:20:03 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p>프로젝트를 진행하면서 자연스레 협업을 위해 git을 사용했다. git을 단순히 저장소의 버전 관리 용도로 사용하긴 쉽지만, 팀원들과 함께 협업을 위한 툴로 사용하기엔 많은 룰을 정해야 했다.</p>
<p>완벽한 정답은 없고 완벽한 프로젝트도 없지만 프로젝트를 거치면서 git을 어떻게 하면 조금 더 잘 쓸지 고민해본 부분을 작성했다.</p>
<h1 id="git를-사용한-프로젝트">Git를 사용한 프로젝트</h1>
<p>처음으로 시작한 프로젝트는 제대로 된 git 전략도 없이 시도했고, fork를 하면서 작성했던 커밋들은 툭하면 충돌이 발생했다. 명목뿐인 PR이 이뤄졌고, 말 그대로 저장소로 GitHub를 사용했다. 프로젝트를 진행하면서 점점 버전 관리가 힘들어지고 git을 사용함에 룰이 필요하다고 절실히 느꼈다.</p>
<h1 id="두-번째-프로젝트">두 번째 프로젝트</h1>
<h2 id="브랜치-전략-설정">브랜치 전략 설정</h2>
<p>다음 프로젝트에서 제일 처음 시작한 부분은 fork를 버리고, 하나의 공통 레포에서 브랜치를 사용했다. 동아리 활동을 하면서 작성한 레포였고, 선배들에게 <a href="https://techblog.woowahan.com/2553/">우린 Git-flow를 사용하고 있어요</a>를 추천 받아 읽고 적용하기 시작했다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/477fd011-8162-4124-a6b9-31fe93e0d659/image.png" alt=""></p>
<h3 id="브랜치-네이밍">브랜치 네이밍</h3>
<p>기능 단위로 팀원들이 개발을 담당하면서 개발하는 기능 단위로 브랜치 네이밍을 정했다. 개발 초기엔 잘 나뉘어 이뤄졌지만, 프로젝트의 기간이 길어지면서 단점이 생겼다.</p>
<ul>
<li>너무 많은 양의 브랜치가 남는다
기능 단위로 정했기 때문에 해당 기능에 수정이 발생한 경우가 있어 브랜치를 삭제하지 못하고 남겼고, 그 결과 너무 많은 브랜치를 남기게 되었다. 또 브랜치를 다시 상위 브랜치와 동기화시키면서 커밋 히스토리가 보기 힘들어진다는 문제가 있었다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/540186dc-d40a-4eb5-9788-e47016b91e00/image.png" alt=""></p>
<ul>
<li>브랜치명을 정하기 힘들다
기능 단위로 나누다 보니 기능 수정을 위해 새로운 브랜치를 생성하기엔 이름이 중복되는 경우가 많았다.</li>
</ul>
<h3 id="3-way-merge">3 way-merge</h3>
<p>프로젝트 내에서 fast-forward를 하지 않고, Pull Request와 3 way-merge를 최대한 활용했다.</p>
<p>3 way-merge는 merge 대상과 fast-forward 관계로 브랜치가 정리되더라도 강제로 merge 커밋을 생성하는 방식으로 실행하면 아래와 같은 그림이 된다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/db016639-7923-4b19-83b7-b4ed9bed658f/image.png" alt=""></p>
<pre><code>$ git merge --no-ff &lt;branch name&gt;</code></pre><h4 id="장점">장점</h4>
<ul>
<li><p>merge에 대한 정보뿐만 아니라 브랜치에서 진행한 내용을 커밋 단위로 확인할 수 있다</p>
</li>
<li><p>문제가 발생한 커밋에 대해 되돌리기 쉽다</p>
</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>커밋 히스토리가 복잡해진다
사진에서 머지 커밋의 메시지를 지정하지 않았지만.. 메시지를 작성하더라도 브랜치 내역 전체가 남으면서 전체적인 히스토리가 길고 복잡해진다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/bffff935-e69e-47fc-a1c0-7e5e962b98bc/image.png" alt=""></p>
<ul>
<li>생각보다 세세한 내역의 히스토리를 찾지 않는다
대부분의 코드 문제는 컴파일 과정에서 찾아지고, 각 브랜치에서 담당자가 테스트를 마친 후 PR을 진행하기 때문에 설정 파일 등의 환경 차이에서 문제가 발생한다. 대부분은 커밋을 거슬러 올라가 찾기보단 새로운 해결법을 적용하는 방식으로 진행했다. </li>
</ul>
<h1 id="현재-프로젝트">현재 프로젝트</h1>
<h2 id="이슈-관리">이슈 관리</h2>
<p>이전 프로젝트를 끝내고 현재 프로젝트를 진행하면서 이슈 관리 방식을 도입했다. GitHub Issue나 JIRA를 사용하여 이슈를 관리하고 그에 대해 이슈 번호를 붙여 진행하고 있다.</p>
<h3 id="브랜치-네이밍-1">브랜치 네이밍</h3>
<p>이슈를 도입하면서 브랜치명을 <code>feature/3</code>과 같은 방식으로 이슈 번호를 사용하여 정했다. 이슈를 통해 주요 업무 내용과 담당자를 정했고 이를 확인하여 브랜치를 관리하는 방식을 사용했다.</p>
<p>이슈와 연계하여 브랜치를 정하면서 이전 프로젝트의 문제였던 동일 기능에 대한 브랜치 문제를 해결할 수 있었다. 이슈에서 업무 내용을 기술하고 있기 때문에 동일 기능이라도 다른 이슈 번호를 통해 네이밍을 다르게 설정하면서 문제를 해결했다.</p>
<p>뿐만 아니라 이름 중복이 삭제되고, 이전 브랜치를 다시 동기화하며 사용할 이유가 없어지면서 종료된 이슈에 대한 브랜치를 삭제함으로 브랜치의 개수를 관리할 수 있었다.</p>
<h3 id="squash-merge">Squash Merge</h3>
<p>현재 프로젝트에선 Squash Merge를 활용하여 이력을 관리하고 있는데, Squash Merge를 활용하는 경우 아래의 그림처럼 브랜치의 커밋 내역이 하나의 커밋으로 압축되어 브랜치에 머지된다.</p>
<blockquote>
<p>Merge 외에도 rebase interactive 모드를 사용하여 여러 개의 커밋을 하나의 커밋으로 압축하여 Squash Merge 시 생성되는 머지 커밋과 동일하게 생성할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/cloud_365/post/ecce4060-bed1-4d68-bd45-68747fcef640/image.png" alt=""></p>
<pre><code>$ git merge --squash &lt;branch name&gt;</code></pre><h4 id="장점-1">장점</h4>
<ul>
<li><p>커밋 히스토리가 깔끔하다
머지된 브랜치는 하나의 커밋만 진행되기 때문에 해당 브랜치에서 확인하면 다른 브랜치 없이 하나의 큰 줄기로 커밋 단계를 확인할 수 있다</p>
</li>
<li><p>이슈 단위 브랜치 사용 시 관리가 편해진다
종료된 브랜치는 재사용할 염려가 없고, 머지된 내역은 Squash 되어 관리되므로 종료된 이슈에 대한 브랜치를 삭제하기 쉬워진다</p>
</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li><p>커밋 단위의 복구가 어렵다
커밋이 하나의 커밋으로 압축되기 때문에 커밋 단위로 빠른 복구를 진행하기 어렵다.</p>
</li>
<li><p>이름이 못생겨진다
커밋하면서 열심히 정한 메시지가 다 커밋의 Body로 들어가게 된다.</p>
</li>
</ul>
<h1 id="마무리">마무리</h1>
<p>Rebase Merge도 있으므로 또 고민이 생기면 시도해보겠지만 현재 프로젝트에서 Squash Merge로 꽤 만족스러운 히스토리 관리가 이뤄지고 있다고 생각한다.</p>
<p>Squash Merge는 커밋 레벨의 확인이 어렵고, 복구가 어렵다고 많이 들었다. 하지만 사용하면서 커밋 단위를 기능으로 잘 나누었다면 Merge 커밋도 기능 단위로 관리되므로 확인이나 복구도 쉬울 것으로 생각된다.</p>
<p>하지만 완벽한 기술은 없고 프로젝트 초기에 내 눈에만 완벽해 보이기 때문에 전략을 설정함에서 프로젝트의 성격 및 팀 내 정책에 따라 잘 반영하는 것이 중요하다고 생각된다.</p>
]]></description>
        </item>
    </channel>
</rss>