<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>combi_jihoon.log</title>
        <link>https://velog.io/</link>
        <description>쿄쿄</description>
        <lastBuildDate>Sat, 12 Nov 2022 14:08:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>combi_jihoon.log</title>
            <url>https://images.velog.io/images/combi_jihoon/profile/51ddf54b-d64a-40e1-b2c6-114b717e21ac/김지훈 이모지.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. combi_jihoon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/combi_jihoon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[데이터 분석 실시간 처리 | Kinesis의 모니터링 지표 및 Lambda 활용법]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B2%98%EB%A6%AC-Kinesis%EC%9D%98-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%A7%80%ED%91%9C-%EB%B0%8F-Lambda-%ED%99%9C%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@combi_jihoon/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B2%98%EB%A6%AC-Kinesis%EC%9D%98-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%A7%80%ED%91%9C-%EB%B0%8F-Lambda-%ED%99%9C%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 12 Nov 2022 14:08:16 GMT</pubDate>
            <description><![CDATA[<p>최근 로깅 시스템을 새롭게 구축하기 위해 그 방법을 찾고 있었는데 그 중 찾은 한 가지 방법은 다음과 같다.</p>
<ol>
<li>Kinesis Data Streams에 데이터를 쌓는다.(기존과 동일)</li>
<li>데이터가 들어오면 이에 대한 이벤트를 트리거 한다.  <ul>
<li>Lambda를 통해 DynamoDB에 로그 데이터를 쌓는다.</li>
</ul>
</li>
</ol>
<p>그런데 여기서 어떤 지표에 유의해야 할 지, 그리고 Lambda의 성능을 어떻게 높일 수 있을 지를 자세히 알아보기 위해 aws summit 2002에서 발표한 <a href="https://kr-resources.awscloud.com/aws-summit-korea-2022-technology-analysis">&quot;데이터 분석 실시간으로 처리하기: Kinesis Data Streams vs MSK&quot;</a> 강연을 참고했다.</p>
<br>

<h1 id="모니터링-지표">모니터링 지표</h1>
<h2 id="제약-조건">제약 조건</h2>
<blockquote>
<p>API 제약 조건</p>
</blockquote>
<p>PutRecord의 경우 초 당, 샤드 당 1MB만 가능하며 1000개의 레코드만 넣을 수 있다. 반면 GetRecord의 경우 초 당, 샤드 당 10MB만 가능하며 한 API 호출 당 10000개의 레코드만 읽을 수 있다.</p>
<br>

<blockquote>
<p>Transactions</p>
</blockquote>
<p>하나의 샤드에 대해 초 당 5번의 read만 가능하다(5 transactions). 이 조건을 넘어 갈 경우 counsumer들은 추가 데이터를 읽기 위해 wait 하게 되어 샤드 안에서 읽어지지 못한 데이터들이 남아서 대기하는 시간이 길어지게 된다. 따라서, 샤드 내의 레코드들이 얼마나 오랫동안 처리되지 못하고 대기하고 있는 지 모니터링 하는 것이 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/d264900c-e069-4b67-bbc7-d4daa795d211/image.png" alt=""></p>
<br>
<br>

<h2 id="모니터링-지표-1">모니터링 지표</h2>
<ol>
<li>Incoming data(count)<ul>
<li>스트림에서 수집한 개별 레코드의 수</li>
</ul>
</li>
<li>Incoming data - sum(bytes)<ul>
<li>스트림에서 수집한 총 데이터의 양</li>
</ul>
</li>
<li>GetRecords.IteratorAgeMilliseconds<ul>
<li>&#39;iterator age&#39;가 증가하면 데이터 소비량이 데이터 수집량보다 뒤쳐지고 있음을 나타낸다.</li>
</ul>
</li>
<li>WriteProvisionedThroughputExceeded<ul>
<li>위에서 언급한 Write throughtput을 초과한 레코드의 수</li>
</ul>
</li>
<li>ReadProvisionedThroughputExceeded<ul>
<li>위에서 언급한 Read throughput을 초과한 레코드의 수</li>
</ul>
</li>
</ol>
<p>위 지표들 중 &quot;샤드 내에서 레코드들이 얼마나 오랫동안 처리되지 못하고 대기하고 있는 지를 알 수 있는&quot; 3번 메트릭이 가장 중요하다. 만약 이 메트릭 값이 길어지면 consumer가 데이터를 빠르게 읽지 못하고 있거나 샤드 당 1초에 5번만 읽을 수 있는 제약 조건을 넘어선 경우일 수 있기 때문에 샤드 수를 추가해 해당 이슈를 해결할 수 있다.</p>
<br>
<br>

<h1 id="lambda를-consumer로-이용하기">Lambda를 Consumer로 이용하기</h1>
<p>람다가 kinesis에서 데이터를 읽어들일 때 throughput을 늘리고 latency를 줄일 수 있는 방법을 알아보자.</p>
<h2 id="throughput-늘리기">Throughput 늘리기</h2>
<blockquote>
<p>배치 사이즈/ 배치 윈도우 조정</p>
</blockquote>
<p>배치 사이즈를 늘리거나 배치 윈도우를 늘리면 각 호출마다 함수에 전달 되는 평균 레코드 수를 늘릴 수 있을 뿐만 아니라 호출 횟수를 줄이고 비용을 최적화 할 수 있는 효과도 있다. 이 때, 배치 윈도우는 최대 300초까지 늘릴 수 있다.</p>
<br>

<blockquote>
<p>Enhanced fan-out</p>
</blockquote>
<p>다수의 Enhanced fan-out(EFO) consumers 기능을 사용하면 각 소비자에게 초 당 2MB의 전용 읽기 처리량을 제공할 수 있다.</p>
<br>

<blockquote>
<p>Parallelization factor 조정하기</p>
</blockquote>
<p>Parallelization factor는 람다가 polling 하는 동시 배치 수로, 동시에 병렬로 많은 레코드를 처리해야 하는 애플리케이션에 대해 설정하는 값이다. 기본적으로 람다 함수는 한 샤드에 대해서만 읽도록 설정되어 있어서 기본 값은 1이다. 이는 최대 10까지 설정이 가능하다. 만약 100개의 kinesis data shard가 있고 Parallelization factor를 2로 설정 했다면 최대 200개의 람다 함수를 동시에 호출할 수 있어 데이터 처리량을 늘릴 수 있다.</p>
<br>
<br>


<h2 id="latency-줄이기">Latency 줄이기</h2>
<blockquote>
<p>배치 사이즈/ 배치 윈도우 조정</p>
</blockquote>
<p>위의 throughput에서와 방법은 동일하다.</p>
<br>

<blockquote>
<p>Provisioned concurrency 사용하기</p>
</blockquote>
<p>데이터 처리량이 증가하는 경우 람다 리소스의 cold start 때문에 latency가 증가할 수 있다. 이 경우 람다의 Provisioned concurrency를 사용해 cold start 시간을 줄일 수 있다.
(이는 또 다른 대안으로 cron으로 주기적으로 dummy data를 넣는 방법도 사용할 수 있을 것이다.)</p>
<br>

<blockquote>
<p>Enhanced fan-out</p>
</blockquote>
<p>HTTP/2 데이터 검색 API를 통해 latency를 1초에서 70ms 이내로 단축할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 분석 실시간 처리 | Kinesis의 특징]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B2%98%EB%A6%AC-Kinesis%EB%A5%BC-buffer%EB%A1%9C-%EC%9D%B4%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@combi_jihoon/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B2%98%EB%A6%AC-Kinesis%EB%A5%BC-buffer%EB%A1%9C-%EC%9D%B4%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 12 Nov 2022 13:27:40 GMT</pubDate>
            <description><![CDATA[<p>로깅 시스템을 개발하며 Kinesis를 이용하고 있는 기존의 레거시 시스템을 어떻게 더 개선할 수 있을까 고민하다가 aws summit 2022에서 발표한 <strong>데이터 분석 실시간으로 처리하기: Kinesis Data Streams vs MSK</strong> 영상을 발견해 영상에서 설명한 Kinesis Data Streams에 관한 부분만 정리하고, 그동안 내가 잘못 알고 있던 내용을 스스로 바로 잡는 시간을 가져 보았다.</p>
<br>
<br>

<h1 id="kinesis의-특징">Kinesis의 특징</h1>
<h2 id="분산-queue">분산 Queue</h2>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/61d3cea1-e256-4dd0-9eec-aaf5e645eb95/image.png" alt=""></p>
<blockquote>
<p>Shard</p>
</blockquote>
<p>Kinesis Data Streams는 분산 Queue라는 특징을 가지고 있다. 그런데, 인메모리 자료 구조인 큐와 다른 점은 Producer가 많아지고 이에 따라 많은 데이터가 큐에 들어오면 큐가 많은 트래픽을 처리 할 수 있도록 해야 하는데 이 때 Kinesis Data Streams에서 택한 방식은 &quot;Scale-out&quot;을 하는 것이다. 이렇게 scale-out을 통해 만들어진 여러 개의 큐를 kinesis에서는 &quot;Shard&quot;라고 부른다.</p>
<br>

<blockquote>
<p>Hash function</p>
</blockquote>
<p>그런데, 여러 개의 큐를 사용하다 보니 Producer에서 들어오는 데이터를 각각의 샤드로 분배하기 위한 전략이 필요하게 되었다. 즉, 큐 하나에만 데이터가 몰리는 것을 방지하기 위한 방법이 필요하게 된 것이다. 따라서, Kinesis에서는 Hash function을 이용해 들어오는 데이터를 각 샤드에 분배해 준다. 이 때, Producer에서 데이터를 보낼 때 &quot;partition key&quot;를 함께 보내 주면 이 key를 기준으로 여러 개의 샤드로 데이터를 분산할 수 있다.</p>
<br>

<blockquote>
<p>읽기 &amp; 쓰기 성능 향상</p>
</blockquote>
<p>이를 통해, producer의 관점에서는 여러 개의 데이터를 보내더라도 각 데이터가 분산 되어 여러 큐에 들어가고 반면 consumer의 관점에서는 consumer가 더 추가 되어도 consumer들은 각 샤드에서 데이터를 읽을 수 있게 된다. 이러한 방법으로, 동일한 큐를 scale-out 하여 쓰기 &amp; 읽기 성능을 올릴 수 있다. 논리적으로, 여러 consumer들을 하나의 consumer group으로 볼 수 있고 내부적으로는 여러 개의 큐로 이루어져 있지만 외부에서 볼 때는 하나의 커다란 큐가 있는 것으로 생각할 수 있다.</p>
<br>
<br>

<h2 id="스트림-스토리지">스트림 스토리지</h2>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/ce560c88-67fc-4d14-8951-6230f2aab3c4/image.png" alt=""></p>
<blockquote>
<p>스토리지</p>
</blockquote>
<p>인메모리 큐 데이터 관점에서 생각해 보면, 데이터를 한 번 읽어 가면 그 데이터는 삭제 된다. 이러한 방식을 kinesis data streams에도 도입하게 되면 2번 consumer가 2번 인덱스 앞에 있는 데이터(2번 샤드)를 읽을 경우 다른 consumer는 2번 인덱스 앞에 있는 데이터를 더 이상 읽을 수 없다. 그러나, kinesis data streams에서는 특정 consumer가 데이터를 읽었다고 해서 그 데이터를 삭제하는 것이 아니라 그대로 남겨 둔다. 또한, 남겨둔 뒤 그 다음에 읽어들어야 하는 위치만 표시를 해준다(next consumer offset). 이렇게 할 경우 2번 consumer가 2번 인덱스의 데이터를 소비했더라도 다른 consumer가 데이터를 읽어 들이는 데 문제가 발생하지 않는다. 이런 의미에서 kinesis를 일종의 스토리지라고 생각할 수 있다. </p>
<br>

<blockquote>
<p>리텐션</p>
</blockquote>
<p>그러나, 일반적인 스토리지와는 달리 &quot;리텐션&quot;을 설정할 수 있다. 즉, 데이터를 얼마나 오랫동안 보관할 것인 지를 가령 24시간/1주일 등으로 설정할 경우 리텐션 기간이 지난 데이터는 오래된 순서부터 자동으로 삭제 된다. 이러한 특징 때문에 Kinesis를 <strong>스트림 스토리지</strong> 라고 한다. </p>
<br>
<br>

<h2 id="throughput-프로비저닝-모델">Throughput 프로비저닝 모델</h2>
<p>Kinesis는 throughput 기반으로 성능을 올리는 방식을 사용한다. 즉, kinesis data streams에 데이터를 초당 몇 번 쓰고 몇 번 읽을 지에 대한 throughtput 설정을 변경해 성능을 올릴 수 있다.</p>
<br>
<br>

<h2 id="shards-수의-증가와-감소가-가능">Shards 수의 증가와 감소가 가능</h2>
<p>이러한 특징 덕분에 트래픽이 많은 시간대에 샤드 수를 크게 늘렸다가 야간에 트래픽이 줄어들면 샤드 수도 함께 줄이는 방식으로 kinesis를 운영할 수 있다.
<br>
<br></p>
<h1 id="스트림-스토리지-도입의-효과">스트림 스토리지 도입의 효과</h1>
<blockquote>
<p>생산자와 소비자 분리(decoupling) 통한 영구적인 버퍼 역할</p>
</blockquote>
<p>이를 통해, producer가 빠르게 데이터를 생산하고 consumer가 조금 느리게 소비 하더라도 둘은 서로 분리 되어 있기 때문에 서로 영향을 주지 않고 producer가 생성한 데이터를 일정한 기간 동안 버퍼링 해서 저장하는 것이 가능하다. 이 때문에 kinesis data streams는 <strong>영구적인 버퍼 역할</strong>을 할 수 있다.</p>
<br>

<blockquote>
<p>다수의 스트림을 수집하는 것이 가능함</p>
</blockquote>
<p>Kinesis data streams를 일종의 큐처럼 사용 함으로써 producer가 하나의 큐에 데이터를 넣어 주면 consumer들이 이를 소비하는 방식으로 전체 구조를 단순화 시킴으로써 다수의 스트림을 수집할 수 있다.</p>
<br>

<blockquote>
<p>메시지의 순서 유지가 가능함</p>
</blockquote>
<p>Producer가 데이터를 입력한 순서대로 consumer가 데이터를 소비할 수 있도록 데이터의 입력 순서가 보장 된다. 하지만, 큐가 하나가 아니라 내부적으로 여러개이기 때문에 내부적인 단일 큐, 즉 샤드 안에서는 데이터의 입력 순서가 보장 되지만 <strong>샤드와 샤드 간의 데이터 순서는 보장 되지 않는다.</strong> 즉, 전체 순서를 보장하는 것이 아니라 샤드 안에서만 순서가 보장 된다. 이 부분을 주의 해야 할 것 같다. 그렇다면 이 말은 사실 kinesis data streams를 프로비저닝 하여 샤드를 2개 이상으로 구성할 경우 순서를 관리할 수 있는 다른 방법을 찾아야 할 수도 있다는 말과 같기 때문이다.</p>
<br>

<blockquote>
<p>병렬적인 소비</p>
</blockquote>
<p>Consumer들이 서로 다른 샤드에서 개별적으로 동시에 데이터를 읽을 수 있다.</p>
<br>

<blockquote>
<p>스트리밍 MapReduce</p>
</blockquote>
<p>파티션 키를 기준으로 입력 데이터들이 그룹화 되어 서로 다른 샤드나 파티션에 저장 되기 때문에 여러 consumer들이 그룹화 된 데이터를 동시에 소비할 수 있다. 이러한 데이터 처리 방식을 <strong>스트리밍 맵 리듀스</strong>라고 한다.
<img src="https://velog.velcdn.com/images/combi_jihoon/post/95cf209e-6963-4b2e-8477-f8351fa692b6/image.png" alt=""></p>
<br>

<p><strong><em>참고</em></strong>
참고로, MSK(Amazon Managed Streaming for Apache Kafka)와 Kinesis Data Streams를 비교해 보면 kafka의 브로커는 kinesis의 stream에 대응 되며 kafka의 partition은 kinesis의 샤드에 대응 된다. 이러한 점에서 구조적으로는 둘이 매우 비슷하지만 kinesis data streams가 MSK에 비해서 추상화 레벨이 높은 편이라 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis]]></title>
            <link>https://velog.io/@combi_jihoon/Redis</link>
            <guid>https://velog.io/@combi_jihoon/Redis</guid>
            <pubDate>Fri, 14 Oct 2022 02:57:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/e0943ece-0340-4de0-8725-c175a3d05e40/image.png" alt=""></p>
<h1 id="캐시">캐시</h1>
<h2 id="캐시란">캐시란?</h2>
<ul>
<li>캐시는 자주 접근하는 데이터를 임시적으로 저장하는 <strong>메모리 버퍼</strong>이다.</li>
<li>캐시를 이용하면 데이터를 가져오기 위해 데이터 소스로부터 여러 번 요청하지 않아도 되어 성능을 향상시켜 준다는 장점이 있다. </li>
<li>캐시의 예시로는 웹 브라우저 캐시가 있는데 이를 통해 처음 요청된 객체를 저장해 둠으로써 동일 객체를 여러 번 가져오지 않도록 만들 수 있다.</li>
</ul>
<br>
<br>

<h2 id="in-memory-database">In-Memory Database</h2>
<ul>
<li>이는 메인 메모리 데이터베이스 라고도 불린다.</li>
<li>인메모리 데이터베이스는 데이터를 하드 디스크가 아닌 램에 저장해 더 빠른 응답이 가능하도록 만들어 준다.</li>
<li>인메모리 데이터베이스를 스케일 업 하면 vertical scaling만 가능하다.</li>
</ul>
<br>
<br>

<h2 id="in-memory-distributed-caching">In-Memory Distributed Caching</h2>
<ul>
<li>분산 캐시는 여러 개의 노드로 구성된 하나의 클러스터에 배포할 수 있으며 이렇게 배포된 클러스터는 하나의 logical view를 형성한다.</li>
<li>이 때 Caching clients는 클러스터 노드 상에서의 객체의 위치를 결정하기 위해 해싱 알고리즘을 사용한다.</li>
</ul>
<br>
<br>
<br>


<h1 id="redis">Redis</h1>
<blockquote>
<ul>
<li>레디스는 인메모리 데이터 저장소로, 오픈소스이다.</li>
<li>레디스는 데이터베이스, 캐시, 메시지 broker로 사용 된다.</li>
<li>레디스는 현재 가장 유명한 분산 캐싱 엔진이다.</li>
</ul>
</blockquote>
<br>

<h2 id="데이터베이스-시스템과의-차이">데이터베이스 시스템과의 차이</h2>
<ul>
<li>Redis는 저장소와 캐시를 동시에 고려할 수 있는 시스템 아이디어를 이용한다.</li>
<li>Redis가 사용하는 디자인은 데이터가 메인 컴퓨터 메모리로부터 항상 수정되고 읽히지만 랜덤 데이터 액세스에는 부적합한 형태의 포맷으로 디스크에 저장 하는 방식이다.</li>
<li>Redis는 시스템이 다시 시작된 후에만 데이터를 다시 메모리에 재구성 한다.</li>
<li>레디스는 RDBMS에서 데이터를 저장하는 방식과는 다른 방식으로 데이터를 저장 한다.<ul>
<li>레디스에서 사용되는 사용자 명령은 데이터베이스 엔진이 실행할 쿼리를 설명하는 것이 아니라 주어진 추상 데이터에 대해 수행되는 특정 작업을 설명한다.<ul>
<li>레디스는 데이터가 나중에 빠르게 검색할 수 있도록 하는 형태로 저장한다.</li>
</ul>
</li>
</ul>
</li>
<li>레디스는 시스템 콜에 해당하는 Fork를 이용해 데이터를 들고 있는 프로세스를 복제한다. <ul>
<li>이에 따라 parent process는 클라이언트에 대응하고 그동안 child process는 disk에 데이터 복제본을 생성한다.</li>
</ul>
</li>
</ul>
<br>
<br>

<h2 id="왜-레디스를-사용할까">왜 레디스를 사용할까?</h2>
<ul>
<li>세션 스토어 <ul>
<li>여러 개의 서버를 이용하더라도 웹 사이트의 세션은 동일하게 유지되어야 한다. <ul>
<li>이 때 레디스를 이용하면 여러 개의 서로 다른 인스턴스에 세션 스토어가 공유 되기 때문에 사용자는 세션은 유지할 수 있다. </li>
</ul>
</li>
</ul>
</li>
<li>메시지 전파<ul>
<li>레디스와 연결된 채널을 이용하는 여러 서버에 메시지를 전파할 수 있다.<ul>
<li>이 때 레디스가 제공하는 Pub/Sub 아키텍쳐를 이용 한다.
  -&gt; 레디스의 Pub/Sub 아키텍쳐:  여러 서버가 접근해야 하는 랜덤한 데이터를 저장한 뒤 메시지나 job queue를 이용해 마스터는 job을 게시하고 workers는 메시지나 job queue를 통해 master가 게시한, 수행해야 하는 job을 가져오는 구조이다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>
<br>

<h2 id="레디스의-장점">레디스의 장점</h2>
<ol>
<li>레디스는 한 번에 최대 512MB의 key-value를 저장할 수 있다.</li>
<li>레디스는 Redis Hashing이라는 해싱 메커니즘을 제공한다.</li>
<li>레디스는 데이터 복제 기능을 제공한다.<ul>
<li>Master-slave 구조를 이용한다.</li>
<li>따라서, master node가 업데이트 되면 slave node는 비동기 방식으로 자동으로 업데이트 된다.</li>
</ul>
</li>
<li>많은 양의 데이터를 캐시에 쉽게 저장할 수 있도록 제공한다.<ul>
<li>짧은 시간 동안 많은 양의 데이터를 한 번에 저장하는 것이 가능하다.</li>
</ul>
</li>
<li>트랜잭션을 제공한다.<ul>
<li>레디스의 value를 업데이트 할 때 트랜잭션이 제공된다면 데이터 정합이 맞지 않는 문제를 해결할 수 있다.</li>
<li>이를 통해 여러 명령이 동시에 처리되는 것이 아니라 큐에 담겨 순차적으로 처리될 수 있다.</li>
</ul>
</li>
<li>레디스는 NoSQL 데이터베이스이다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 | 무결성 & 정합성]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1</link>
            <guid>https://velog.io/@combi_jihoon/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EC%A0%95%ED%95%A9%EC%84%B1</guid>
            <pubDate>Sun, 04 Sep 2022 01:06:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/4cf5f577-7991-45ed-8423-37c922b19d4d/image.png" alt=""></p>
<h1 id="데이터-무결성과-정합성">데이터 무결성과 정합성</h1>
<h2 id="무결성">무결성</h2>
<blockquote>
<p>무결성(Integrity)란 데이터의 정확성, 일관성 및 유효성이 유지되는 특성을 말한다.</p>
</blockquote>
<ul>
<li>데이터가 정확하고 완전해야 한다.<ul>
<li>즉, 개발자가 의도한 상태로 존재해야 한다.</li>
</ul>
</li>
<li>무결성에는 제약 조건이 존재한다.</li>
</ul>
<br>


<h3 id="무결성-제약조건의-종류">무결성 제약조건의 종류</h3>
<h4 id="개체-무결성entity-integrity">개체 무결성(Entity Integrity)</h4>
<blockquote>
<p>pk(기본키)는 NULL이 올 수 없으며 unique 해야 한다.</p>
</blockquote>
<ul>
<li>개체 식별에 오류가 없도록 하기 위한 조건이다.</li>
</ul>
<br>


<h4 id="참조-무결성referential-integrity">참조 무결성(Referential Integrity)</h4>
<blockquote>
<p>외래키는 참조할 수 없는 값을 가질 수 없다.</p>
</blockquote>
<ul>
<li>개체의 fk는 참조되는 개체의 pk와 일치하거나 NULL이어야 한다.</li>
<li>이는 두 개체의 연관된 인스턴스 사이의 일관성을 유지하기 위한 제약이다.</li>
</ul>
<br>


<h4 id="도메인-무결성domain-integrity">도메인 무결성(Domain Integrity)</h4>
<blockquote>
<p>속성 값과 관련된 제약으로, 올바른 데이터가 입력되었는지 확인하는 것을 말한다.</p>
</blockquote>
<ul>
<li>필드 타입, null 값의 허용 등 해당 속성에 맞는 데이터가 들어가야 한다.</li>
<li>가령, 전화번호 필드에는 숫자가 들어가야 하는데 “전화번호입니다~”가 들어간다면 이는 무결성이 훼손된 것이다.</li>
</ul>
<br>


<h4 id="키-무결성key-integrity">키 무결성(Key Integrity)</h4>
<blockquote>
<p>릴레이션(테이블)에는 최소한 하나의 키가 존재해야 한다.</p>
</blockquote>
<br>


<h4 id="null-무결성null-integrity">NULL 무결성(Null Integrity)</h4>
<blockquote>
<p>“유저 아이디”와 같은 특정 속성 값은 null을 가질 수 없다.</p>
</blockquote>
<ul>
<li>따라서 스키마를 정의할 때 해당 속성이 null을 가질 수 없음을 미리 정의할 경우, 해당 속성에는 null이 들어가면 안된다.</li>
</ul>
<br>


<h4 id="고유-무결성unique-integrity">고유 무결성(Unique Integrity)</h4>
<blockquote>
<p>릴레이션의 특정 속성이 고유한 값을 갖도록 조건이 주어진다면, 그 속성들은 모두 달라야 한다(고유한 값을 가져야 한다).</p>
</blockquote>
<br>
<br>

<h2 id="정합성">정합성</h2>
<blockquote>
<p>어떤 데이터들의 값이 서로 일치할 때 “데이터 정합성이 맞다”고 표현한다.</p>
</blockquote>
<ul>
<li>중복 데이터를 많이 사용할 경우(반정규화 되어 있는 경우) 데이터 정합성이 깨질 수 있다.</li>
<li>데이터는 서로 모순 없이 일관되게 일치해야 한다<img src="https://velog.velcdn.com/images/combi_jihoon/post/43087feb-2f4c-4c7c-b055-53086e52c3dd/image.png" alt="">
.</li>
</ul>
<p>어떤 데이터는 정합성에는 이상이 없지만 무결성은 훼손되어 있을 수 있다.</p>
<p>즉, 사용자의 잔고에 int가 아닌 boolean이 동일하게 들어간 경우 중복 데이터가 전부 동일하지만 그 값이 올바르지 않은 상태이다. 이 경우, 정합성은 만족하지만 무결성은 훼손된 상태인 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django의 OneToOneField를 커스터마이징 한 CustomOneToOneField]]></title>
            <link>https://velog.io/@combi_jihoon/Django%EC%9D%98-OneToOneField%EB%A5%BC-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95-%ED%95%9C-CustomOneToOneField</link>
            <guid>https://velog.io/@combi_jihoon/Django%EC%9D%98-OneToOneField%EB%A5%BC-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95-%ED%95%9C-CustomOneToOneField</guid>
            <pubDate>Mon, 15 Aug 2022 11:27:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/abaf7874-2c93-4585-a3ab-2b465fcf17b1/image.png" alt=""></p>
<p>CustomOneToOneField가 왜 필요 했는지, 그리고 어떤 특성을 가지고 있는지 알아보기 전에 OneToOneField가 무엇인지 잠시 알아보자.</p>
<br>

<h1 id="onetoonefield">OneToOneField</h1>
<ul>
<li>OneToOneField는 장고에서 사용되는 개념적인 필드이다. </li>
<li>OneToOneField는 <code>unique=True</code> 속성을 가진 ForeignKey라 볼 수 있다.<ul>
<li>그러나 ForeignKey와는 달리 reverse relation을 조회하면 <strong>하나의</strong> 객체만 조회할 수 있다. </li>
</ul>
</li>
<li>OneToOneField는 ForiegnKey에서 사용하는 모든 extra arguments를 사용할 수 있다.</li>
<li>해당 모델이 related 될 클래스를 위치 인자로 추가해야 한다.<ul>
<li>이 때 만약 related될 클래스의 <code>related_name</code>을 설정하지 않는다면 장고는 해당 모델 이름의 소문자를 <code>related_name</code>으로 간주한다.<ul>
<li>예를 들어, OneToOneField로 User를 추가 한다면 <code>related_name</code>은 <code>user</code>가 되는 것이다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>아래의 예시는 Django Docs에서 가져왔다.</p>
<pre><code class="language-python">from django.db import models

class MySpecialUser(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE,
    )
    supervisor = models.OneToOneField(
        User,
        on_delete=models.CASCADE,
        related_name=&#39;supervisor_of&#39;,
    )</code></pre>
<p>이 경우 MySpecialUser는 user, supervisor 필드가 모두 <code>User</code> 테이블에 대해 OneToOne 관계를 갖는다. 이렇게 두 필드가 모두 같은 테이블에 관계를 가지고 있을 때는 어느 한 쪽은 꼭 <code>related_name</code>을 설정해 주어야 한다.</p>
<p>그러면 조회시 아래와 같은 결과를 보인다.</p>
<ul>
<li>User 모델은 <code>myspecialuser</code>(related_name을 지정하지 않았기 때문에 user 필드는 MySpecialUser의 소문자를 related_name으로 갖는다)와 <code>supervisor_of</code>를 모두 attribute로 갖는다.<pre><code class="language-python">&gt;&gt;&gt; user = User.objects.get(pk=1)
&gt;&gt;&gt; hasattr(user, &#39;myspecialuser&#39;)
True
&gt;&gt;&gt; hasattr(user, &#39;supervisor_of&#39;)
True</code></pre>
</li>
</ul>
<br>

<h2 id="relatedobjectdoesnotexist">RelatedObjectDoesNotExist</h2>
<ul>
<li>RelatedObjectDoesNotExist 에러는 reverse relationship에 대한 액세스를 할 때 related 테이블에 데이터가 존재하지 않으면 발생하는 에러이다. </li>
<li><code>Model.DoesNotExist</code> 에러를 상속 받아 만들어진 에러 케이스이다.</li>
</ul>
<p>아래 예시는 Django Docs에서 가져온 위의 예시에 대해 related_object를 조회했을 때 RelatedObjectDoesNotExist가 발생하는 케이스이다.</p>
<ul>
<li>User 모델의 related_object인 <code>supervisor_of</code>가 존재하지 않는 경우에 에러가 발생한다.</li>
</ul>
<pre><code class="language-python">&gt;&gt;&gt; user.supervisor_of
Traceback (most recent call last):
    ...
RelatedObjectDoesNotExist: User has no supervisor_of.</code></pre>
<br>
<br>

<p>이제 본론으로 돌아가, 이 글을 작성한 이유를 설명해 보려 한다. 바로 <strong>CustomOneToOneField</strong>이다.
위에서 RelatedObjectDoesNotExist가 발생하는 케이스를 확인해 보았다. 즉, related_object가 존재하지 않는 케이스이다. 이를 위해 OneToOneField를 조금 더 뜯어보자.</p>
<br>
<br>

<h1 id="customonetoonefield">CustomOneToOneField</h1>
<p>CustomOneToOneField를 만들게 된 계기는 OneToOneField를 조회할 때 에러를 발생시키지 말고 <code>get_or_create</code>처럼 해당 object를 생성하도록 하는 것이 어떨까 하는 생각 때문이었다. </p>
<br>

<p><em>그런데 이게 굳이 왜 필요할까?</em></p>
<p>최근에 개발 하면서 User 모델에 one to one으로 연결 되어야 하는 테이블을 추가해야 하는 상황이 발생한 적이 있다. 예를 들자면, 서비스에서 기존에는 유저에게 포인트를 지급하지 않았는데 이제는 포인트를 쌓는 시스템이 추가 되어야 한다면 유저 별로 포인트 계좌 테이블이 필요할 것이다. 포인트 계좌를 한 유저가 여러 개 갖고 있는 상황이 발생하면 안되기 때문에 User와 포인트 계좌는 one to one 관계가 되어야 한다.</p>
<p>이 때, <strong>배포시에 마이그레이션을 해주어야 하는데 이 과정에서 모든 유저 별 계좌를 생성해 주어야 하며 무중단 배포시 계좌를 생성하는 과정에서 새로 가입한 유저들의 계좌가 생성되지 않은 것은 없는지 확인해야 한다</strong>. </p>
<p>이러한 번거로움을 해결하기 위해 CustomOneToOneField를 만들었다.
이를 위해 OneToOneField를 이용해 related object가 조회되는 과정을 디버거로 뜯어 보았다.</p>
<p>OneToOneField는 내부적으로 related_accessor_class 라는 클래스 변수에 <code>ReverseOneToOneDescriptor</code>를 정의해 놓는데 이 안의 <code>__get__</code> 메소드를 이용해 related object를 조회한다.</p>
<br>

<p><strong>기존의 ReverseOneToOneDescriptor</strong></p>
<pre><code class="language-python">
class ReverseOneToOneDescriptor:
    def __init__(self):
        ...

    def __get__(self, instance, cls=None):
        &quot;&quot;&quot;
        Get the related instance through the reverse relation.

        With the example above, when getting ``place.restaurant``:

        - ``self`` is the descriptor managing the ``restaurant`` attribute
        - ``instance`` is the ``place`` instance
        - ``cls`` is the ``Place`` class (unused)

        Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
        &quot;&quot;&quot;
        if instance is None:
            return self

        # The related instance is loaded from the database and then cached
        # by the field on the model instance state. It can also be pre-cached
        # by the forward accessor (ForwardManyToOneDescriptor).
        try:
            rel_obj = self.related.get_cached_value(instance)
        except KeyError:
            related_pk = instance.pk
            if related_pk is None:
                rel_obj = None
            else:
                filter_args = self.related.field.get_forward_related_filter(instance)
                try:
                    rel_obj = self.get_queryset(instance=instance).get(**filter_args)
                except self.related.related_model.DoesNotExist:
                    rel_obj = None
                else:
                    # Set the forward accessor cache on the related object to
                    # the current instance to avoid an extra SQL query if it&#39;s
                    # accessed later on.
                    self.related.field.set_cached_value(rel_obj, instance)
            self.related.set_cached_value(instance, rel_obj)

        if rel_obj is None:
            raise self.RelatedObjectDoesNotExist(
                &quot;%s has no %s.&quot;
                % (instance.__class__.__name__, self.related.get_accessor_name())
            )
        else:
            return rel_obj</code></pre>
<p><strong>조회시 없으면 생성 되도록 변경한 CustomReverseOneToOneDescriptor</strong></p>
<ul>
<li><p>ReverseOneToOneDescriptor에서  self.related.related_model.DoesNotExist 에러가 발생할 경우 <code>rel_obj</code>를 None으로 만들지 않고 <code>self.related.field.name</code>를 이용해 related object의 소문자 이름을 가져 왔다.</p>
</li>
<li><p>그 다음, <code>field_args = {self.related.field.name: instance}</code>를 정의해 이를 이용해 related_model을 생성하도록 변경했다.</p>
</li>
<li><p>OneToOneField를 상속 받은 CustomOnetoOneField를 선언해 이렇게 만든 CustomReverseOneToOneDescriptor를 related_accessor_class 클래스 변수로 사용하도록 오버라이딩 했다.</p>
<pre><code class="language-python">class CustomReverseOneToOneDescriptor(ReverseOneToOneDescriptor):
  def __get__(self, instance, cls=None):
      &quot;&quot;&quot;
      Get the related instance through the reverse relation.

      With the example above, when getting ``place.restaurant``:

      - ``self`` is the descriptor managing the ``restaurant`` attribute
      - ``instance`` is the ``place`` instance
      - ``cls`` is the ``Place`` class (unused)

      Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
      &quot;&quot;&quot;
      if instance is None:
          return self

      # The related instance is loaded from the database and then cached
      # by the field on the model instance state. It can also be pre-cached
      # by the forward accessor (ForwardManyToOneDescriptor).
      try:
          rel_obj = self.related.get_cached_value(instance)
      except KeyError:
          related_pk = instance.pk
          if related_pk is None:
              rel_obj = None
          else:
              filter_args = self.related.field.get_forward_related_filter(instance)
              try:
                  rel_obj = self.get_queryset(instance=instance).get(**filter_args)
              except self.related.related_model.DoesNotExist:
                  field_args = {self.related.field.name: instance}
                  rel_obj = self.related.related_model.objects.create(**field_args)
          self.related.set_cached_value(instance, rel_obj)

      return rel_obj

</code></pre>
</li>
</ul>
<p>class CustomOnetoOneField(OneToOneField):
    &quot;&quot;&quot;
    OneToOneField가 없을 경우 조회시 바로 생성할 수 있게 만들어 준다.
    &quot;&quot;&quot;</p>
<pre><code>related_accessor_class = CustomReverseOneToOneDescriptor</code></pre><p>```</p>
<br>
<br>



<p><strong>참고</strong></p>
<ul>
<li><a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.OneToOneField">https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.OneToOneField</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Database의 Index 파헤치기(특징 & 자료구조)]]></title>
            <link>https://velog.io/@combi_jihoon/Database%EC%9D%98-Index%EB%8A%94-%EB%AC%B4%EC%8A%A8-%EC%9D%BC%EC%9D%84-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@combi_jihoon/Database%EC%9D%98-Index%EB%8A%94-%EB%AC%B4%EC%8A%A8-%EC%9D%BC%EC%9D%84-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sat, 13 Aug 2022 08:16:50 GMT</pubDate>
            <description><![CDATA[<p>데이터베이스의 검색 속도 향상에는 index가 사용된다. 이 index는 어떤 일을 하는지, 그리고 어떻게 사용해야 검색 속도를 향상시킬 수 있는 지를 알아본다.</p>
<br>

<h1 id="인덱스의-특징">인덱스의 특징</h1>
<h2 id="read-performance-향상">Read Performance 향상</h2>
<ul>
<li>보통의 테이블은 id를 pk로 갖는다. id는 정수로 구성되며 1씩 증가하는 값이기 때문에 id를 이용해 테이블을 검색 할 때는 데이터베이스가 테이블의 모든 행을 조사하지 않아도 된다. <ul>
<li>왜냐하면, 데이터베이스가 이미 id를 정렬한 복사본 컬럼을 가지고 있기 때문이다.</li>
</ul>
</li>
<li>인덱스 컬럼은 실제 테이블의 행에 대한 포인터를 가지고 있어서 만약 조회시 인덱스에서 이미 원하는 id를 찾았다면 포인터를 통해 해당 id에 대한 나머지 데이터를 어디서 찾아야 하는 지 바로 알 수 있다.</li>
</ul>
<br>

<p><strong>게시판 테이블을 가정해 보자</strong>
<em>만약 게시판 테이블이 <code>유저 이름</code>, <code>제목</code>, <code>내용</code>, <code>날짜</code>로 구성되어 있을 때 유저 이름으로 게시물들을 검색하고 싶다면?</em></p>
<ul>
<li>이 때는 유저 이름에 대해 인덱스를 설정해 놓는 것이 좋다. 그러면 데이터베이스는 유저 이름으로 정렬된 인덱스 컬럼을 생성해 놓고 조회할 때마다 해당 컬럼을 이용해 빠르게 검색할 수 있다.
이처럼 검색에 자주 이용될 컬럼을 인덱스로 설정하는 것이 좋다. </li>
</ul>
<p>_만약 <code>내용</code>에 대해 인덱스를 설정 한다면? _</p>
<ul>
<li>보통 내용 전체를 통째로 쿼리 하지는 않는다. 즉, 이는 자주 발생하는 케이스는 아니기 때문에 굳이 index로 지정할 필요가 없다.</li>
</ul>
<br>
<br>

<h3 id="외래키를-검색에-사용하기">외래키를 검색에 사용하기</h3>
<p>_위에서 예를 들었던 게시판 테이블을 다시 가져와서 생각해 보자. _
게시판 테이블이 <code>User</code> 테이블의 pk를 외래키로 갖는다고 가정하자. 이 때 이 외래키는 <code>user_id</code>이다. 
만약 정수로 구성된 <code>user_id</code>를 인덱스로 설정해 놓으면 특정 유저가 작성한 게시물을 조회할 때 해당 유저의 id에 유저가 작성한 게시물들이 포인터로 연결 되어 있기 때문에 해당 유저가 작성한 게시물들을 더 빠르게 조회할 수 있다.</p>
<br>

<h2 id="여러-컬럼에-index-적용하기">여러 컬럼에 Index 적용하기</h2>
<p>_테이블에 여러 개의 인덱스를 설정해 놓을 수도 있다. _</p>
<p>도서관에서 내가 원하는 책을 찾을 때를 생각해 보자. 보통 각 책장들은 책의 장르 별로 구분이 되어 있으며 해당 책장에 가보면 그 안에서 작가의 이름이 가나다 순으로 정렬 되어 있다. 그렇다면 이 도서관은 장르와 작가명이라는 두 가지 index를 사용하고 있는 것이다.
데이터베이스에서도 도서관에서와 같이 인덱스를 여러 개 설정할 수 있다. 이 때 검색에 많이 사용되는 컬럼들을 인덱스로 지정하면 된다.</p>
<br>

<p><strong>여러 개의 인덱스를 지정할 때는 순서가 중요하다.</strong>
게시판의 예시를 다시 가져와 생각해 보자. 만약 우리 서비스에 유저가 로그인 했을 때 본인이 작성한 게시물을 조회하는 페이지로 들어왔다고 해보자.</p>
<p>이 경우<code>user_id</code>와 <code>날짜</code> 두 컬럼을 인덱스로 지정하면 된다. </p>
<p>이 때 두 컬럼을 어떤 순서로 인덱스로 설정하는 것이 데이터베이스 성능 향상에 더 도움이 될까?</p>
<ol>
<li>(<code>user_id</code>, <code>날짜</code>)<ul>
<li><code>user_id</code>가 먼저 오고 그 다음 <code>날짜</code>가 온다면 원하는 <code>user_id</code>로 조회한 뒤 그 <code>user_id</code>에 대한 <code>날짜</code>를 찾게 된다. </li>
<li>즉, 원하는 첫 번째 인덱스를 통해 원하는 데이터를 발견하면 두 번째 인덱스를 이용할 때는 원하는 <code>user_id</code>가 아닌 데이터들은 모두 조회 대상에서 제거 된다.</li>
</ul>
</li>
<li>(<code>날짜</code>, <code>user_id</code>)<ul>
<li>로그인 후 내가 작성한 모든 게시물을 조회해야 하는데 <code>날짜</code>를 첫 번째 인덱스로 사용할 경우 날짜를 특정할 수 없다. 따라서, <code>날짜</code>를 인덱스로 사용할 수 없다. 즉, 인덱스 컬럼에서 원하는 값을 발견한 뒤 첫 번째 인덱스를 기준으로 두 번째 인덱스에서 원하는 값을 찾는 것이 불가능하다.</li>
<li>이렇게 할 경우 인덱스를 사용하는 의미가 없다. 즉, 두 번째 인덱스에는 로그인 한 유저의 id가 여러 개 발견되기 때문에 속도가 향상되지 않는다.</li>
</ul>
</li>
</ol>
<br>

<h2 id="index를-사용할-경우-read-vs-write-속도-비교">Index를 사용할 경우 Read VS Write 속도 비교</h2>
<p>DB에 대한 접근 요청 중 Read보다 Write가 더 많은 경우 index를 사용시 DB의 성능이 오히려 저하될 수 있다.
인덱스도 데이터베이스의 10% 정도에 해당하는 저장 공간을 차지 하는데 write 요청이 빈번한 경우 인덱스 크기가 비대해질 수 있기 때문에 오히려 성능이 저하될 수 있는 것이다.</p>
<p><em>여러 개의 index를 사용하는 경우를 생각해 보자.</em>
테이블에 새로운 row를 추가할 경우 데이터베이스는 각 인덱스 컬럼에도 역시 새로운 데이터에 대한 명단을 추가해야 한다. index 컬럼 내에 새로운 데이터가 들어갈 위치를 찾아 데이터가 잘 정렬될 수 있도록 해야 한다. 이렇게 새로운 데이터의 인덱스를 추가할 위치를 찾기 위한 과정에서 인덱스 데이터가 많아질수록 데이터베이스의 성능은 떨어지게 되며 이는 CREATE, UPDATE, DELETE 모두에 해당한다.</p>
<p>인덱스는 항상 최신의 상태로 유지되어야 하며 데이터의 추가 및 삭제에 따라 인덱스 역시 추가 및 삭제 되어야 한다. 따라서, 다음의 사항들을 적절히 고려해야 한다.</p>
<ul>
<li>불필요한 인덱스는 아닌가?<ul>
<li>더이상 기존에 설정했던 인덱스가 사용되지 않는다면 이 인덱스는 제거하는 것이 좋다.</li>
</ul>
</li>
<li>읽기 요청보다 쓰기 요청이 더 많이 일어나지는 않는가?<ul>
<li>쓰기 요청이 더 많다면 index를 쓰지 않는 것이 더 나을 수도 있다.</li>
</ul>
</li>
</ul>
<p>그렇다면, 인덱스는 언제 사용하면 좋을까?</p>
<ul>
<li>데이터 수가 많은 테이블 조회시</li>
<li>JOIN, WHERE, ORDER BY가 자주 사용되는 테이블</li>
</ul>
<br>
<br>


<h1 id="인덱스의-자료구조">인덱스의 자료구조</h1>
<p>인덱스의 자료구조는 B+tree를 이용한다. 먼저, 이와 유사한 B+tree에 대해 알아보자.</p>
<h2 id="b-tree">B-tree</h2>
<p>B-tree는 Binary search tree와 유사하지만, 한 노드 당 자식 노드를 2개 이상 가질 수 있으며 데이터는 정렬 되어 있다는 특징을 보인다.
B-tree는 루트로부터 리프까지의 거리가 일정한 &#39;균형 트리&#39;이기 때문에 어떤 값에 대해서도 같은 시간을 들여 결과를 얻을 수 있는 ‘균일성’을 갖는다. 그렇기 때문에 안정된 성능을 보인다. 
그러나, 테이블 갱신의 반복이 이어지면 서서히 균형이 깨지게 되며 자료를 찾는 데 걸리는 시간이 증가하게 되어 성능이 악화 된다. 그렇기 때문에 빠른 성능을 위해 사용하는 인덱스의 자료 구조로는 적합하지 않다.</p>
<p><img src="https://images.velog.io/images/combi_jihoon/post/a1b92bdd-f276-424c-979d-8f2fcac83296/B-Tree%20Example.jpeg" alt=""></p>
<br>


<h2 id="btree">B+tree</h2>
<p>오직 리프 노드에만 key와 data를 저장하며 리프 노드는 linked list로 서로 연결되어 있다. 이 때, 브랜치 노드에는 key만 담아두고 data는 담지 않는다. 리프 노드에만 데이터를 담기 때문에 메모리를 더 확보해 많은 key들을 수용할 수 있다.
따라서, B-tree는 하나의 노드에 많은 key들을 담을 수 있어 트리의 높이는 더 낮아지게 되고 이에 따라 <code>cache hit</code>을 높일 수 있다.
풀 스캔시 B-tree는 모든 노드에서 선형 탐색을 해야 하지만 B+tree는 모든 데이터가 리프 노드에 있기 때문에 리프 노드에서만 한 번의 선형 탐색을 하면 된다. 그렇기 때문에 인덱스의 자료구조로 사용 된다.
<img src="https://images.velog.io/images/combi_jihoon/post/9c4f6a7e-f4e6-4ced-b8cc-081092d4b123/btree.png" alt=""></p>
<br>
<br>

<hr>
<p><strong>&lt;용어 정리&gt;</strong></p>
<ol>
<li>Cache hit<ul>
<li>CPU에서 요청한 데이터가 캐시에 존재하는 경우를 말한다.</li>
<li>이와 반대로 cache miss는 캐시 메모리에 데이터가 존재하지 않는 경우를 의미한다.<ul>
<li>cache miss가 발생하면 데이터 저장소로부터 필요한 캐시를 찾아 캐시 메모리에 로드한다.</li>
</ul>
</li>
</ul>
</li>
</ol>
<br>
<br>


<p><strong>참고</strong></p>
<ul>
<li><a href="https://medium.com/@jimmyfarillo/the-basics-of-database-indexes-for-relational-databases-bfc634d6bb37">https://medium.com/@jimmyfarillo/the-basics-of-database-indexes-for-relational-databases-bfc634d6bb37</a></li>
<li><a href="https://mangkyu.tistory.com/96">https://mangkyu.tistory.com/96</a></li>
<li><a href="https://zorba91.tistory.com/293">https://zorba91.tistory.com/293</a></li>
<li><a href="http://www.btechsmartclass.com/data_structures/b-trees.html">http://www.btechsmartclass.com/data_structures/b-trees.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 | 명령 패턴]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Command-Pattern</link>
            <guid>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Command-Pattern</guid>
            <pubDate>Thu, 11 Aug 2022 15:07:48 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 헤드 퍼스트 디자인 패턴을 읽고 정리한 것입니다.</em></p>
<br>

<h1 id="커맨트-패턴이란">커맨트 패턴이란?</h1>
<blockquote>
<p>커맨트 패턴은 객체 지향 디자인 패턴을 통해 요청을 하는 객체와 요청을 받고 실행하는 객체를 분리한 패턴으로 요청자(invoker)와 수신자(receiver)가 decoupling 되어 있다.</p>
</blockquote>
<h2 id="실행-순서">실행 순서</h2>
<ol>
<li>클라이언트가 command 객체를 생성한다.</li>
<li>클라이언트는 호출자(invoker)에 command 객체를 저장하기 위해 <code>setCommand()</code>를 실행한다.</li>
<li>클라이언트가 호출자에게 명령을 실행하도록 요청한다.<ul>
<li>명령이 호출자에게 등록 되면 해당 명령은 실행 후 삭제될 수도 있지만 그대로 남아 있을 수도 있으며 여러 번 사용하는 것 역시 가능하다.</li>
</ul>
</li>
</ol>
<br>

<ul>
<li>Command 객체는 수신자(receiver)에 가해지는 일련의 행동들을 연결(bind) 함으로써 요청을 캡슐화 한다. </li>
<li>그러면 command 객체는 행동과 수신자를 하나의 객체로 포장한 뒤 요청을 실행할 수 있는 하나의 메소드, 예를 들면 <code>execute()</code>만 노출 시킨다. </li>
<li>이후 이 메소드는 수신자에 가해야 하는 특정 행동이 발생하도록 만든다.</li>
<li>외부적으로 볼 때 다른 객체들은 수신자에 어떤 일이 일어났는 지 알 수 없으며 <code>execute()</code> 메소드가 실행 되었다는 것만 안다.</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/5033c4f6-75aa-4df4-ab85-96d41cd37334/image.png" alt=""></p>
<p><strong>예시</strong>
리모컨으로 불을 켜는 것에 대한 책의 예시를 파이썬으로 작성 했다.</p>
<pre><code class="language-python">from abc import *


# The example from the Head First Design Pattern
&quot;&quot;&quot;
Receiver: Some appliances 
&quot;&quot;&quot;


class Light:
    def on(self):
        print(&quot;on&quot;)

    def off(self):
        print(&quot;off&quot;)


&quot;&quot;&quot;
Command: on, off
&quot;&quot;&quot;


class Command(metaclass=ABCMeta):
    @abstractmethod
    def execute(self):
        raise NotImplementedError()


class LightOnCommand(Command):
    def __init__(self, light: Light):
        self.light = light

    def execute(self) -&gt; None:
        self.light.on()


&quot;&quot;&quot;
Invoker: RemoteControl
&quot;&quot;&quot;


class RemoteControl:
    def __init__(self, command: Command):
        self.slot = command  # A kind of setter in Java

    def button_pressed(self) -&gt; None:
        self.slot.execute()


# Usage
class RemoteControlTest:
    @classmethod
    def run(cls):
        light = Light()
        light_on = LightOnCommand(light=light)

        remote = RemoteControl(command=light_on)
        remote.button_pressed()  # on
</code></pre>
<br>

<h2 id="커맨드-확장하기">커맨드 확장하기</h2>
<h3 id="macro-커맨드">Macro 커맨드</h3>
<ul>
<li>Macro 커맨드는 여러 개의 커맨드가 요청될 수 있도록 하는 방법으로, 커맨드를 리스트에 넣어 <code>execute()</code> 실행시 리스트에 들어 있는 커맨드들이 하나씩 실행되도록 만들면 된다.</li>
</ul>
<p><strong>예시</strong>
책의 예시를 파이썬으로 바꾸어 작성해 보았다.</p>
<pre><code class="language-python"># The example from the Head First Design Pattern
&quot;&quot;&quot;
Receiver: Some appliances
&quot;&quot;&quot;
from abc import ABCMeta, abstractmethod
from typing import List


class Light:
    def on(self, type: str):
        on_by_types = {
            &quot;living_room&quot;: &quot;living_room_light_on&quot;,
            &quot;bath_room&quot;: &quot;living_room_light_on&quot;,
        }
        print(on_by_types.get(type))

    def off(self, type: str):
        on_by_types = {
            &quot;living_room&quot;: &quot;living_room_light_off&quot;,
            &quot;bath_room&quot;: &quot;living_room_light_off&quot;,
        }
        print(on_by_types.get(type))


class Stereo:
    def on(self):
        print(&quot;on&quot;)

    def off(self):
        print(&quot;off&quot;)

    def set_cd(self):
        print(&quot;CD in&quot;)


&quot;&quot;&quot;
Command: on, off
&quot;&quot;&quot;


class Command(metaclass=ABCMeta):
    @abstractmethod
    def execute(self):
        raise NotImplementedError()


class LightOnCommand(Command):
    &quot;&quot;&quot;Light&quot;&quot;&quot;

    def __init__(self, light: Light, type: str):
        self.light = light
        self.type = type

    def execute(self) -&gt; None:
        self.light.on(self.type)


class LightOffCommand(Command):
    &quot;&quot;&quot;Light&quot;&quot;&quot;

    def __init__(self, light: Light, type: str):
        self.light = light
        self.type = type

    def execute(self) -&gt; None:
        self.light.off(self.type)


class StereoOnWithCDCommand(Command):
    &quot;&quot;&quot;Stereo&quot;&quot;&quot;

    def __init__(self, stereo: Stereo):
        self.stereo = stereo

    def execute(self) -&gt; None:
        self.stereo.on()
        self.stereo.set_cd()


&quot;&quot;&quot;
Invoker: RemoteControl
&quot;&quot;&quot;


class RemoteControl:
    def __init__(self, on_commands: List[Command], off_commands: List[Command]):
        self.on_commands = on_commands  # A kind of setter in Java
        self.off_commands = off_commands  # A kind of setter in Java

    def on_button_pressed(self, slot: int) -&gt; None:
        self.on_commands[slot].execute()

    def off_button_pressed(self, slot: int) -&gt; None:
        self.off_commands[slot].execute()


# Usage
class RemoteLoader:
    @classmethod
    def run(cls):
        light = Light()
        stereo = Stereo()

        on_commands = [
            LightOnCommand(light=light, type=&quot;living_room&quot;),
            StereoOnWithCDCommand(stereo=stereo),
        ]
        off_commands = [
            LightOffCommand(light=light, type=&quot;living_room&quot;),
            StereoOnWithCDCommand(stereo=stereo),
        ]

        remote = RemoteControl(on_commands=on_commands, off_commands=off_commands)

        length_of_commands = len(on_commands)
        for slot in range(length_of_commands):
            remote.on_button_pressed(slot=slot)</code></pre>
<br>

<h3 id="전체-커맨드-취소-기능">전체 커맨드 취소 기능</h3>
<ul>
<li>커맨드 패턴을 이용하면 전체 커맨드를 취소하는 것도 가능한데 이는 가장 마지막(최근의) 상태를 저장해 요청자의 <code>execute()</code> 메소드가 불리기 이전의 상태로 되돌리면 된다.</li>
</ul>
<br>

<h2 id="특징">특징</h2>
<ul>
<li>실행될 기능이 변경 되어도 호출자(invoker) 클래스를 수정 하지 않아도 된다.<ul>
<li>따라서, 변경이 잦거나 다양한 이벤트들이 발생해야 하는 경우 커맨트 패턴을 이용하기 좋다.</li>
</ul>
</li>
<li>호출자 클래스는 <code>execute()</code>만 실행하면 되며, 이 메소드는 각각의 이벤트에 대한 커맨트를 실행한다.</li>
<li>만약 요청을 하는 객체와 요청을 어떻게 실행해야 하는 지 아는 객체를 서로 분리(decouple) 해야 한다면 커맨드 패턴을 쓰면 된다.</li>
<li>요청자는 Command를 통해 파라미터화 될 수 있는 1급 객체이며 런타임에 파라미터화 하는 것도 가능하다.<ul>
<li>1급 객체: 변수나 데이터에 할당할 수 있으며 객체에 인자로 넘길 수 있고 객체의 리턴 값으로 반환할 수 있는 객체를 말한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[What is ORM?]]></title>
            <link>https://velog.io/@combi_jihoon/What-is-ORM</link>
            <guid>https://velog.io/@combi_jihoon/What-is-ORM</guid>
            <pubDate>Thu, 11 Aug 2022 02:30:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/36f2c200-9468-4acc-9492-dc2981c14f22/image.png" alt=""></p>
<h1 id="orm이란">ORM이란?</h1>
<blockquote>
<p>ORM(Object-Relational-Mapper)은 관계형 데이터베이스 테이블에 저장된 데이터를 객체로 자동 전송하는 코드 라이브러리이다.</p>
</blockquote>
<p><strong>그렇다면 Object-Relational Mapping은 뭘까?</strong>
Object-Relational-Mapping은 raw 쿼리를 사용하는 것이 아닌, 개발자가 현재 사용하고 있는(SQL보다 더 친숙한) 언어의 객체 지향 패러타임을 이용해 쿼리하는 것을 말한다. 
이를 가능하게 해주는 라이브러리가 바로 Object-Relational-Mapper이다.</p>
<br>

<h2 id="orm의-특징">ORM의 특징</h2>
<ul>
<li>ORM은 관계형 데이터베이스에 대한 고수준의 추상화를 통해 SQL을 이용해 CRUD를 하는 대신 특정 언어를 이용해 쿼리할 수 있도록 해준다.</li>
<li>또한 ORM을 이용하면 ORM이 제공하는 고급 기능들을 사용할 수 있다. 그 기능들은 예를 들면 트랜잭션 지원, 커넥션 풀링, 마이그레이션 같은 것들이 있다.</li>
<li>이밖에도 개발 환경과 운영 환경 등 서로 다른 상황에서도 여러 다양한 관계형 데이터베이스를 함께 사용할 수 있다. <ul>
<li>즉, ORM은 RDBMS에 의존적이지 않기 때문에 개발 환경에서는 SQLite를 사용하고 운영 환경에서는 PostgreSQL을 사용하는 등 서로 다른 데이터베이스를 사용하는 것이 가능하다. <ul>
<li>그러나 실제 개발 시에는 개발 환경과 운영 환경이 보통 동일한 RDBMS를 사용하기 때문에(서로 다른 RDBMS를 사용함으로써 생길 수 있는 문제를 방지하기 위해) 이 부분은 굳이 알 필요 없을 것 같다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="프로그래밍-언어는-orm으로만-db-접근이-가능할까">프로그래밍 언어는 ORM으로만 DB 접근이 가능할까?</h2>
<p>프로그래밍 언어를 이용해 관계형 데이터베이스에 접근할 때 ORM 라이브러리가 항상 필요한 것은 아니다. ORM보다 비교적 저수준의 접근이 가능하도록 구현된 psycopg 또는 MySQL-python이 제공하는 databse connector를 이용하는 것도 가능하다.
이를 통해 필요시 프로그래밍 언어를 이용해 raw SQL로 쿼리하는 것도 가능하다.
가령 window function을 이용한 복잡한 쿼리를 해야하는 경우 ORM으로 쿼리를 짜는 것이 어려울 수 있는데 이럴 때 raw 쿼리가 도움이 된다.</p>
<br>

<h2 id="orm-사용시-발생할-수-있는-문제점">ORM 사용시 발생할 수 있는 문제점</h2>
<h3 id="impedance-mismatch">Impedance Mismatch</h3>
<p>Impedance mismatch는 데이터베이스 모델과 프로그래밍 언어의 모델이 서로 달라서 발생할 수 있는 문제점이다.</p>
<h4 id="데이터-타입-mismatch">데이터 타입 mismatch</h4>
<ul>
<li>프로그래밍 언어에서 사용하는 데이터의 타입과 데이터 모델에서 사용하는 데이터 타입이 서로 다를 수 있다.<ul>
<li>따라서, 각 속성 유형에 대한 호환이 가능하도록 프로그래밍 언어끼리 언어 유형 별 바인딩을 가지고 있어야 한다.</li>
</ul>
</li>
</ul>
<h4 id="tuple의-사용">Tuple의 사용</h4>
<ul>
<li>대부분의 쿼리의 결과는 튜플로 구성 된다. 따라서, 각 데이터에 접근하기 위해서는 튜플에 어떻게 접근할 것인지를 파악해야 한다.</li>
</ul>
<br>

<h3 id="성능-저하">성능 저하</h3>
<ul>
<li>ORM을 이용할 경우 프로그래밍 언어를 SQL문으로 변환하게 되면서 성능 저하가 생길 수 있다. <ul>
<li>ORM이 SQL 변환될 때 이 SQL문이 적절히 튜닝되어 있을 가능성이 적기 때문이다. 왜냐하면, 보통 ORM을 마스터 하지 않는 이상 장고에서 사용하는 <code>select_related()</code>와 같은 고급 쿼리를 접하기는 어렵기 때문이다.<ul>
<li>이 문제는 오히려 DBA와 같은 분들이 raw 쿼리를 통해 성능 최적화를 함으로써 해결될 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>


<h3 id="데이터를-코드-상에서-사용하며-생기는-문제">데이터를 코드 상에서 사용하며 생기는 문제</h3>
<ul>
<li>ORM이 사용되기 전에는 데이터베이스 프로시저를 통해 데이터베이스 로직을 캡슐화 하여 사용했으나 ORM의 경우 코드 내부에서 데이터를 조작하기 위한 로직이 들어가야 하기 때문에 이렇게 데이터를 다루기 위해 추가적인 코드가 더 필요하게 된다.</li>
<li>이는 앱에 디자인 패턴이 잘 적용되어 있다면 문제가 되지 않겠지만 그렇지 않다면 데이터를 제대로 캐싱하지 않아 DB에 데이터를 여러 번 요청하며 과부하를 발생시키는 등의 문제가 발생할 수 있다.</li>
</ul>
<br>
<br>


<p><strong>참고</strong></p>
<ul>
<li><a href="https://www.fullstackpython.com/object-relational-mappers-orms.html">https://www.fullstackpython.com/object-relational-mappers-orms.html</a></li>
<li><a href="https://blog.bitsrc.io/what-is-an-orm-and-why-you-should-use-it-b2b6f75f5e2a">https://blog.bitsrc.io/what-is-an-orm-and-why-you-should-use-it-b2b6f75f5e2a</a></li>
<li><a href="https://www.geeksforgeeks.org/impedance-mismatch-in-dbms/">https://www.geeksforgeeks.org/impedance-mismatch-in-dbms/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 | 데코레이터 패턴]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Wed, 10 Aug 2022 05:10:37 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 헤드퍼스트 디자인 패턴을 읽고 정리한 것입니다.</em></p>
<br>

<p><strong>상속만으로 해결할 수 없는 것</strong>
Subclassing을 통해 클래스를 상속하면 complie time에 해당 클래스의 행동이 static 하게 고정 된다. 즉, 자식 클래스의 행동이 고정 되어 부모 클래스와 같은 행동을 하게 된다.
그런데 만약 composition(구성)을 통해 runtime에 객체의 행동을 다양화 할 수 있다면 <em>변경에는 닫혀 있지만 확장에는 열려 있는</em> 구조를 만들 수 있을 것이다.
또한, 이를 통해 자식 클래스에 여러 책임을 부여할 수 있기 때문에 기존 코드를 수정함으로써 생기는 오류 발생을 줄일 수 있다.</p>
<br>

<h1 id="데코레이터-패턴">데코레이터 패턴</h1>
<p><strong>상황 가정해 보기</strong>
카페에서 커피를 팔고 있다. 이 때 커피의 종류는 다음과 같다.</p>
<ul>
<li>HouseBlend, DarkRoast, Dcaf, ...<ul>
<li>모두 Beverage를 상속 받는다.</li>
</ul>
</li>
</ul>
<p>이 때 고객은 여러 가지 종류의 토핑을 얹을 수 있다.</p>
<ul>
<li>코코아, 휘핑, 시나몬, ...<ul>
<li>각 토핑에 따라 가격은 달라진다.</li>
</ul>
</li>
</ul>
<p>또한 커피 사이즈는 tall/grande/venti로 3가지이며 사이즈에 따라 가격이 달라진다.
이를 만약 전략 패턴을 이용 한다면 다음과 같은 방법을 생각해 볼 수 있다.</p>
<ul>
<li>CoCoaHouseBlend: Beverage -&gt; HouseBlend -&gt; 코코아 토핑</li>
<li>WhipHouseBlend: Beverage -&gt; HouseBlend -&gt; 휘핑
...</li>
</ul>
<p>토핑의 종류가 증가할 때마다 variation이 수도 없이 생길 수 있을 것이다.
이렇게 되면 확장성이 매우 떨어진다.
이를 해결할 수 있는 방법 중 하나가 앞서 말했던 _데코레이터 패턴_이다.</p>
<br>

<h2 id="예시">예시</h2>
<p>아래는 어떤 문제 풀이 방식을 사용하는 지에 따라서 행동이 달라지는 케이스에 대한 예시이다. 파이썬으로는 데코레이터를 사용할 수 있어 조금 더 편하게 작성한 것 같다.</p>
<ul>
<li>LevelExam과 ProblemBasedLearn은 퀘스트 업데이트 + 포인트 지급 </li>
<li>RetryProblem은 퀘스트 업데이트만</li>
<li>SimilarProblem은 프리미엄 여부 확인 + 퀘스트 업데이트 + 포인트 지급</li>
</ul>
<br>

<p><strong>Python</strong></p>
<pre><code class="language-python">from abc import *

from functools import wraps

&quot;&quot;&quot;
데코레이터
&quot;&quot;&quot;


def quest_update(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f&quot;{func.__name__}에서 퀘스트 업데이트 완료&quot;)
        return func(*args, **kwargs)

    return wrapper


def give_point(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f&quot;{func.__name__}에서 포인트 지급 완료&quot;)
        return func(*args, **kwargs)

    return wrapper


def premium_validated(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f&quot;{func.__name__}는 프리미엄만 가능합니다^^&quot;)
        return func(*args, **kwargs)

    return wrapper


&quot;&quot;&quot;
문제 
&quot;&quot;&quot;


class Problem(metaclass=ABCMeta):
    @abstractmethod
    def solved_info(self):
        raise NotImplementedError(&quot;반드시 만들어라.....&quot;)


class LevelExam(Problem):
    @give_point
    @quest_update
    def solved_info(self):
        print(&quot;진단고사 풀이 완료&quot;)


class ProblemBasedLearn(Problem):
    @quest_update
    @give_point
    def solved_info(self):
        print(&quot;문제 기반 풀이 완료&quot;)


class RetryProblem(Problem):
    @quest_update
    def solved_info(self):
        print(&quot;복습 완료&quot;)


class SimilarProblem(Problem):
    @premium_validated
    @quest_update
    @give_point
    def solved_info(self):
        print(&quot;유사 문제 풀이 완료&quot;)
</code></pre>
<p><strong>java</strong></p>
<pre><code class="language-java">public abstract class Problem {
    String solveProblem = &quot;Solve your problem&quot;;

    public String solveProblem() {
        return solveProblem;
    }
}

public class LevelExam extends Problem {
    public LevelExam() {
        solveProblem = &quot;진단고사 완료&quot;;
    }
}

public class ProblemBasedLearn extends Problem {
    public ProblemBasedLearn() {
        solveProblem = &quot;문제 기반 풀이 완료&quot;;
    }
}


public class RetryProblem extends Problem {
    public RetryProblem() {
        solveProblem = &quot;복습 완료&quot;;
    }
}

public class SimilarProblem extends Problem {
    public SimilarProblem() {
        solveProblem = &quot;유사 문제 풀이 완료&quot;;
    }
}

// Decorators
// Decorator class should have the same type of object as its superclass
public abstract class ProblemDecorator extends Problem {
    public abstract String solveProblem();
}

public class QuestUpdateDecorator extends ProblemDecorator {
    Problem problem;

    public QuestUpdateDecorator(Problem problem) {
        this.problem = problem;
    }
    public String solveProblem() {
        return &quot;퀘스트 업데이트 완료&quot; + problem.solveProblem();
    }
}

public class GivePointDecorator extends ProblemDecorator {
    Problem problem;

    public GivePointDecorator(Problem problem) {
        this.problem = problem;
    }
    public String solveProblem() {
        return &quot;포인트 지급 완료&quot; + problem.solveProblem();
    }
}

public class PremiumValidationDecorator extends ProblemDecorator {
    Problem problem;

    public PremiumValidationDecorator(Problem problem) {
        this.problem = problem;
    }
    public String solveProblem() {
        return &quot;프리미엄만 사용 가능합니다&quot; + problem.solveProblem();
    }
}

// 사용하기
public class SolveYourProblem {
    public static void main(String args[]) {
        Problem problem = new SimilarProblem();
        problem = new GivePointDecorator(problem);
        problem = new QuestUpdateDecorator(problem);
        problem = new PremiumValidatonDecorator(problem);

        System.out.println(problem.solveProblem());
    }
}</code></pre>
<br>

<h2 id="특징">특징</h2>
<ul>
<li>데코레이터는 데코레이터가 감싸는 객체에 특정 행동을 부여하기 위해 사용한다.</li>
<li>데코레이터를 이용하면 유연성이 늘어나며 런타임에 여러 행동을 추가할 수 있다.<ul>
<li>상속만을 이용할 경우 superclass가 갖는 행동 또는 이를 오버라이딩 한 행동을 가져야 하기 때문에 다양성을 확보하기 어렵다.</li>
</ul>
</li>
<li>데코레이터를 이용할 경우 데코레이터는 데코레이팅 하는 객체와 같은 타입을 갖도록 해야 한다.</li>
</ul>
<br>

<p><em>단점</em></p>
<ul>
<li>여러 개의 작은 데코레이터 클래스가 대상 클래스에 추가 되기 때문에 의도가 명확히 드러나지 않아 직관적이지 않다.</li>
<li>데코레이터를 사용하면 구성 요소를 인스턴스화 하는 데 필요한 코드가 복잡해진다.</li>
</ul>
<p>단점으로 제기된 것들은 모두 &#39;복잡성&#39;에 관한 것들이며 이를 감안하거나 또는 감당 가능할 정도의 수 만큼 행동을 다양화 하고 싶은 경우 데코레이터를 사용하는 것은 좋은 방법인 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Compile Time & Run time]]></title>
            <link>https://velog.io/@combi_jihoon/Compile-Time-Run-time</link>
            <guid>https://velog.io/@combi_jihoon/Compile-Time-Run-time</guid>
            <pubDate>Tue, 09 Aug 2022 05:48:23 GMT</pubDate>
            <description><![CDATA[<h1 id="compile-time">Compile Time</h1>
<blockquote>
<p>Compile time은 컴퓨터 프로그램 또는 코드를 CPU가 이해할 수 있는 즉, machine-readable 코드로 변환하는 단계를 의미한다.</p>
</blockquote>
<ul>
<li>이 과정은 compiler에 의해 실행 된다.<ul>
<li>Compiler를 사용하는 언어: C, Java...</li>
</ul>
</li>
<li>compile 중에 소스 언어의 코드는 특정 타겟 언어(byte)로 변환 된다.</li>
<li>Compile 단계 이후에는 Load time을 거쳐 execution time으로 가게 되는데 여기서 execution time은 runtime에 포함 된다.<ul>
<li>Load time에는 프로그램이 메모리에 로드 된다(loader를 이용함).<ul>
<li>Load 시에는 loader가 프로그램이 지시사항을 읽어 실행에 필요한 리소스들이 준비될 수 있도록 한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="compile-과정-중-일어나는-일">Compile 과정 중 일어나는 일</h2>
<ul>
<li>compile 중에는 syntax(문법적)나 semantic(의미론적) 분석을 진행한다.<ul>
<li>즉, 사용 언어에 문법적 오류가 없는지 그리고 문법은 정상이나 실행의 결과가 원하는 대로 나오지 않는 경우는 없는 지를 분석한다.</li>
</ul>
</li>
<li>컴퓨터의 특정 물리적 메모리 위치에 프로그램 명령을 연관시키는(association) 일을 한다.</li>
</ul>
<br>

<h2 id="compiler를-사용하지-않는-언어-python">Compiler를 사용하지 않는 언어 Python</h2>
<ul>
<li>파이썬은 C, 자바와는 달리 인터프리터에 의해 실행되는 스크립트 언어이다.</li>
<li>인터프리터는 소스 코드를 처음부터 한 줄씩 차례대로 해석(기계어로 변환)하며 실행하는 프로그램이다.<ul>
<li>따라서 컴파일러에 비해 실행 속도가 느리다는 단점이 있지만 코드가 완전히 작성되지 않아도 작성된 부분까지만 테스트 하는 것이 가능하다.</li>
</ul>
</li>
</ul>
<br>

<h2 id="compile-errors">Compile Errors</h2>
<ul>
<li>Syntax Errors</li>
<li>Semantic Errors<ul>
<li><code>a + b = c;</code>의 경우 좌변에 두 개 이상의 변수가 할당 되는 것이 불가능하기 때문에 <code>c = a + b;</code>로 수정 되어야 한다. 따라서 이 경우 semantic error가 발생한다.</li>
</ul>
</li>
</ul>
<br>
<br>


<h1 id="run-time">Run Time</h1>
<blockquote>
<p>Run time은 컴퓨터 시스템에 의해 프로그램이 실행 되는 단계를 말한다.</p>
</blockquote>
<ul>
<li>Execution time이라고도 불리는 run time은 유저 또는 다른 os에 의해 프로그램이 종료되기 전까지 프로그램이 메모리 내에서 실행되는 시간을 말한다.</li>
<li>Run time은 프로그램이 프레임워크, 컴포넌트 또는 라이브러리와 함께 메모리에 load되는 순간부터 시작 된다. <ul>
<li>이 때 os가 프로그램이 run time 동안에 필요로 하는 메모리, 프로세스, I/O 리소스를 할당한다.</li>
</ul>
</li>
</ul>
<br>

<h2 id="run-time-errors">Run time Errors</h2>
<p>런타임 에러는 개발자들이 디버깅 하기 어려운 에러 중 하나로, 예상했던 대로 코드가 실행되지 않는 경우에 발생한다.
런타임 에러는 os가 찾아내는 것이 아니며 trap이라는 자기 평가(self-assessment)를 통해 소프트웨어가 스스로를 진단해 발견한다. 보통 런타임 에러가 발생하면 프로그램이 종료되거나 frozen 상태에 도달하게 된다.
가능한 원인에는 여러가지가 있는데 보통 아래와 같은 것들이 있다.</p>
<ul>
<li>실행 애플리케이션에 의한 이슈</li>
<li>메모리 이슈</li>
<li>소프트웨어 이슈</li>
<li>오래된 하드웨어</li>
<li>등등,,,,</li>
</ul>
<p>일반적으로 발생하는 런타임 에러에는 다음과 같은 것들이 있다.</p>
<ul>
<li>ZeroDivisionError</li>
<li>NullPointError</li>
<li>Segmentation fault / bus error <ul>
<li>존재하지 않는 메모리 위치에 접근하는 경우</li>
</ul>
</li>
<li>무한 루프에 빠지는 경우</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQLD 정리 - 1]]></title>
            <link>https://velog.io/@combi_jihoon/SQLD-1</link>
            <guid>https://velog.io/@combi_jihoon/SQLD-1</guid>
            <pubDate>Tue, 09 Aug 2022 00:06:43 GMT</pubDate>
            <description><![CDATA[<h1 id="with-구문">WITH 구문</h1>
<ul>
<li>서브 쿼리를 사용해 임시 테이블이나 뷰처럼 사용 가능하다.</li>
<li>별칭을 지정할 수 있다.</li>
<li>DB는 WITH 구문을 인라인 뷰나 임시 테이블로 여긴다.</li>
</ul>
<pre><code class="language-sql">WITH TableName AS (
    SELECT *
    FROM C_INFO
    WHERE NAME LIKE &#39;%a%&#39;
)</code></pre>
<h2 id="서브쿼리">서브쿼리</h2>
<blockquote>
<p>SELECT문 내에 SELECT 문이 또 쓰인 쿼리</p>
</blockquote>
<h2 id="인라인-뷰">인라인 뷰</h2>
<blockquote>
<p>서브 쿼리가 FROM 절 내에 쓰인 쿼리</p>
</blockquote>
<pre><code class="language-sql">SELECT *
FROM (SELECT * FROM C_INFO WHERE name LIKE &#39;%A%&#39;);</code></pre>
<h2 id="뷰-테이블">뷰 테이블</h2>
<blockquote>
<p>일종의 가상 테이블로, 실제 데이터가 하드웨어에 저장 되지는 않으며 실제 데이터를 갖고 있지도 않다. 또한, 테이블의 구조가 변경되어도 독립적으로 존재한다.</p>
</blockquote>
<ul>
<li>사용상의 편의를 위해 사용한다.</li>
<li>수행 속도의 향상을 위해 사용한다.<ul>
<li>임시 저장을 미리 해 놓고 수행 속도를 향상 시킨다.</li>
</ul>
</li>
<li>SQL 성능 향상을 위해 사용한다.</li>
<li>임시 작업을 위해 사용한다.</li>
<li>보안 관리를 위해 사용한다.</li>
</ul>
<br>
<br>

<h1 id="rowid">ROWID</h1>
<ul>
<li>INSERT를 하면 DB 상에는 보이지 않는 ROWID가 생성 된다.</li>
<li>해당 데이터가 어떤 데이터 파일 상에서 어느 블록에 저장되어 있는지 알려준다.</li>
<li>ROWID의 번호는 데이터 블록에 데이터가 저장된 순서이다.</li>
<li>테이블에 데이터를 입력하면 자동 생성 된다.</li>
<li>데이터베이스에 저장되어 있는, 데이터를 구분할 수 있는 유일한 값이다.</li>
</ul>
<br>
<br>


<h1 id="group-함수">Group 함수</h1>
<h2 id="rollup">ROLLUP</h2>
<ul>
<li>부분 합계와 전체 합계 값을 보여준다.</li>
<li>인수의 순서에 영향을 받는다.<ul>
<li>앞의 인수만 집계 결과를 리턴한다.</li>
</ul>
</li>
<li>아래와 같이 성별과 연령대에 대해 rollup 함수를 실행해 group by를 하면 성별 - 연령대 가능한 케이스들에 대해 sum 집계를 하고 마지막 행에 성별에 대한 전체 합을 리턴한다.<ul>
<li>즉, 성별 x 연령 별 합계 &amp; 성별 별 합계 &amp; 전체 합계를 리턴한다.<pre><code class="language-sql">SELECT 성별, 연령, SUM(성별, 연령대)
FROM 결제
GROUP BY ROLLUP(성별, 연령대)
ORDER BY 성별, 연령;</code></pre>
<img src="https://velog.velcdn.com/images/combi_jihoon/post/7a8aeafa-c435-4175-9cb5-bbaacaf747f6/image.png" alt=""></li>
</ul>
</li>
</ul>
<br>

<h2 id="cube">CUBE</h2>
<ul>
<li>그룹화 될 수 있는 모든 경우에 대해 집계 한다.<ul>
<li>즉, 성별 x 연령 별 합계 &amp; 성별 별 합계 &amp; 연령 별 합계 &amp; 전체 합계를 리턴한다.<pre><code class="language-sql">SELECT 성별, 연령, SUM(성별, 연령대)
FROM 결제
GROUP BY CUBE(성별, 연령대)</code></pre>
<img src="https://velog.velcdn.com/images/combi_jihoon/post/8b348914-672a-452f-bc9c-d712bec00633/image.png" alt=""></li>
</ul>
</li>
</ul>
<br>

<h2 id="grouping-sets">GROUPING SETS</h2>
<ul>
<li>괄호 묶은 집합 별로 집계 가능하다.</li>
</ul>
<p>&lt;<img src="https://velog.velcdn.com/images/combi_jihoon/post/84094d11-5671-4cb4-8bd7-aef4f3615bd4/image.png" alt="">
성별, 거주지 &gt;</p>
<pre><code class="language-sql">SELECT 성별, 연령, SUM(성별, 연령대)
FROM 결제
GROUP BY GROUPING SETS(성별, 연령대)</code></pre>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/20746ccf-60be-435d-8221-9b6ccb435b5f/image.png" alt=""></p>
<p>&lt;(성별, 연령대), 거주지 &gt;</p>
<pre><code class="language-sql">SELECT 성별, 연령, SUM(성별, 연령대)
FROM 결제
GROUP BY GROUPING SETS((성별, 연령대), 거주지)</code></pre>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/81cfd714-a79c-4ad8-98ac-5285b7bffca6/image.png" alt=""></p>
<br>
<br>

<h1 id="쿼리가-실행되는-순서">쿼리가 실행되는 순서</h1>
<p>from -&gt; group by -&gt; select -&gt; order by</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 | 옵저버 패턴]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Fri, 05 Aug 2022 00:17:57 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 헤드퍼스트 디자인 패턴(한빛 미디어, 에릭 프리먼 외)를 읽고 정리한 것입니다.</em></p>
<h1 id="옵저버-패턴이란">옵저버 패턴이란?</h1>
<blockquote>
<p>옵저버 패턴은 한 객체의 상태가 바뀌면 해당 객체에 의존하는 다른 객체에 연락이 가고, 자동으로 내용이 갱신되는 방식의 &quot;일대다 의존성&quot;을 정의하는 패턴이다.</p>
</blockquote>
<p>책에 나온 예시에 따르면 옵저버 패턴은 <strong>신문사와 구독자</strong>로 이루어진 신문 구독 서비스와 비슷하다. 즉, 신문사가 <strong>주제 객체</strong>이며 구독자가 <strong>옵저버 객체</strong>가 된다.</p>
<ul>
<li>주제 객체<ul>
<li>구독자에게 전달할 중요 데이터를 관리한다.<ul>
<li>주제의 데이터가 바뀌면 옵저버에게 그 소식을 전한다.</li>
<li>ex) 신문사</li>
</ul>
</li>
</ul>
</li>
<li>옵저버 객체<ul>
<li>바뀐 주제의 데이터 갱신 내용을 전달 받는다.<ul>
<li>ex) 신문을 구독하는 개인/ 호텔/ 회사 등이 이에 해당한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>만약 주제를 구독하고 있는 옵저버가 아니라면 주제의 데이터 갱신 내용을 전달 받을 수 없다.</p>
<br>

<h1 id="구현-방식">구현 방식</h1>
<ul>
<li>보통 <strong>주제 인터페이스</strong> &amp; <strong>옵저버 인터페이스</strong>를 갖는 클래스 디자인으로 구현 된다.</li>
<li>주제와 옵저버는 일대다 관계로 구현 된다. 즉, 한 주제에 여러 개의 옵저버가 있을 수 있다.</li>
<li>옵저버는 데이터가 변경되었을 경우 주제에서 데이터를 갱신해 주길 기다려야 하기 때문에 주제에 의존적이다.</li>
</ul>
<br>

<p><em>(아래의 인터페이스와 구상 클래스는 책에서 든 예시를 가져온 것입니다.)</em></p>
<h3 id="인터페이스">인터페이스</h3>
<ul>
<li><strong>Subject</strong><ul>
<li>registerObserver(): 옵저버로 등록하고 싶을 때 사용하는 메소드<ul>
<li>removeObserver(): 옵저버에서 탈퇴하고 싶을 때 사용하는 메소드</li>
<li>notifyObserver(): 주제에 변경 사항이 생겼을 때 옵저버에게 알리기 위해 사용하는 메소드</li>
</ul>
</li>
</ul>
</li>
<li><strong>Observer</strong><ul>
<li>update(): 주제의 상태가 바뀌었을 때 데이터를 갱신하기 위해 사용하는 메소드</li>
</ul>
</li>
</ul>
<h3 id="구상-클래스">구상 클래스</h3>
<ul>
<li><strong>ConcreteSubject</strong><ul>
<li>registerObserver() {}<ul>
<li>removeObserver() {}</li>
<li>notifyObserver() {}</li>
<li>getState(): 주제의 옵저버가 알고 싶을 경우 옵저버는 주제의 상태를 pull 할 수 있다.</li>
<li>setState(): 주제가 옵저버에게 상태를 알리고 싶을 경우 옵저버는 주제에게 상태를 push 할 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>ConcreteObserver</strong><ul>
<li>update(): 옵저버가 특정 주제에 등록되어 있다면 주제의 상태 변경을 갱신하기 위해 사용할 수 있다.</li>
</ul>
</li>
</ul>
<br>

<h1 id="옵저버-패턴의-느슨한-결합">옵저버 패턴의 느슨한 결합</h1>
<blockquote>
<p>느슨한 결합은 객체들이 상호작용 하며 서로에 대해 잘 알지 못하는 관계를 말한다.</p>
</blockquote>
<p>옵저버 패턴은 주제와 옵저버가 느슨한 결합을 이루고 있다. 느슨한 결합을 이용하면 시스템에 변경 사항이 생겨도 오류를 최소화 한 유연한 객체지향 시스템을 만들 수 있다. 
아래에 나열된 내용은 이러한 느슨한 결합의 장점이다.</p>
<ul>
<li>주제는 옵저버의 구상 클래스가 무엇인지, 그리고 어떤 일을 하는지 알지 못한다.<ul>
<li>주제는 옵저버가 특정 인터페이스를 구현한다는 것만 안다.</li>
</ul>
</li>
<li>실행 중에 하나의 옵저버를 바꾸거나 새로 추가해도 계속해서 다른 옵저버에게 데이터를 보낼 수 있다.</li>
<li>주제와 옵저버는 서로 독립적이며 재사용 가능하다.</li>
<li>주제와 옵저버의 변화가 서로에게 영향을 미치지 않는다.<ul>
<li>단, 주제와 옵저버는 각각에 맞는 인터페이스를 구현해야 한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Docs | select_for_update()]]></title>
            <link>https://velog.io/@combi_jihoon/Django-selectforupdate</link>
            <guid>https://velog.io/@combi_jihoon/Django-selectforupdate</guid>
            <pubDate>Tue, 26 Jul 2022 09:16:24 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 <a href="https://docs.djangoproject.com/en/4.0/ref/models/querysets/">장고 Docs</a>를 참고해 작성했습니다.</em></p>
<br>

<p>앱을 사용하다 보면 여러 사람이 동시에 같은 row를 조회하게 되는 경우가 생길 수 있다. </p>
<p>가령 고객에게 DB에 있는 상품권을 지급하는 경우 여러 유저가 동시에 상품권을 조회하게 된다면 같은 상품권을 조회하게 될 수 있다는 문제점이 있다. 물론 상품권 조회에 대해 transaction lock을 걸어 놓겠지만 그렇게 되면 동시에 접근한 두 유저 중 한 유저는 에러를 보게 될 것이다.</p>
<p>이런 문제점을 해결하기 위해 사용할 수 있는 쿼리셋이 바로 <code>select_for_update()</code>이다. 이 쿼리셋을 이용하면 락이 걸린 rows는 건너 뛰거나 아니면 트랜잭션이 끝날 때까지 기다리도록 설정할 수 있다.</p>
<br>

<h1 id="select_for_update">select_for_update()</h1>
<h2 id="raw-query">Raw Query</h2>
<p>select_for_update()를 사용하면 이 쿼리셋을 통해 만들어지는 로우쿼리는 <code>SELECT ... FOR UPDATE</code> 형태가 된다.</p>
<h2 id="사용-방법">사용 방법</h2>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>skip_locked</code><ul>
<li>True로 설정하면 락이 걸린 rows는 넘어가고 락이 걸리지 않은 rows를 찾는다.</li>
</ul>
</li>
<li><code>nowait</code><ul>
<li>True인 경우 락이 걸린 트랜잭션이 끝날 때까지 기다리지 않고 바로 <strong>DatabaseError</strong>를 뱉어 낸다.<ul>
<li>False인 경우 해당 row에 락이 걸린 트랜잭션이 끝날 때까지 기다렸다가 끝나면 작업을 진행한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h3 id="추가-옵션">추가 옵션</h3>
<ul>
<li><code>of</code><ul>
<li>기본적으로 <code>select_for_update()</code>는 조회하는 모든 쿼리에 락을 잡는다. 따라서 select시 참조하는 모든 모델에 락을 잡는다. 그런데 만약 이러한 상황을 원하지 않는다면 <code>of</code> 옵션을 이용해 원하는 objects를 지정해 지정된 objects만 락을 잡도록 설정할 수 있다.<ul>
<li><strong>self</strong>로 지정할 경우 자기 자신만 조회하도록 한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h3 id="주의-사항">주의 사항</h3>
<ol>
<li><p>transaction.atomic() 블럭 안에서 <code>select_for_update()</code>를 사용해야 한다.</p>
<ul>
<li>그렇지 않으면 <code>select_for_update()</code>는 작동하지 않는다.</li>
<li>이 블럭 안에 있으면 트랜잭션 블럭이 끝나기 전까지 블럭 안에서 조회한 쿼리에 락이 잡히게 된다. 따라서, 다른 트랜잭션은 이 트랜잭션을 획득하거나 변화를 줄 수 없다.</li>
</ul>
</li>
<li><p><code>skip_locked</code> 옵션과 <code>nowait</code> 옵션은 상호 배타적인 관계로, 만약 두 옵션을 모두 사용하게 되면 <strong>ValueError</strong>가 발생 한다.</p>
</li>
<li><p><code>select_for_update()</code>는 null을 참조하는 경우에는 사용할 수 없다.</p>
<ul>
<li>따라서, 이런 경우 <strong>exclude</strong>를 이용해 None인 경우를 제외하고 사용해야 한다.<br><code>Person.objects.select_related(&#39;hometown&#39;).select_for_update().exclude(hometown=None)</code></li>
</ul>
</li>
<li><p><strong><code>select_for_update()</code>는 eager loading을 한다.</strong></p>
<ul>
<li><p>개발 하면서 알게 된 내용으로, <code>select_for_update()</code> 사용시 where 조건을 걸어 특정 유저 혹은 id에 대한 쿼리를 실행하지 않으면 해당 테이블 전체에 락을 잡아 앱의 성능이 매우 느려지는 이슈가 발생한다.</p>
</li>
<li><p>아마도 <code>select_for_update()</code>는 기존의 <code>select</code> 쿼리와 달리 db를 바로 조회하는 것 같다. </p>
</li>
<li><p>따라서, 아래와 같이 사용해야 한다.</p>
<pre><code class="language-python"> # 사용 O     
order = Order.objects.select_for_update(nowait=False).get(user=user)

# 사용 X
order_qs = Order.objects.select_for_update(nowait=False)
order = order_qs.get(user=user)</code></pre>
</li>
</ul>
</li>
</ol>
<br>
<br>


<ul>
<li>참고<ul>
<li><a href="https://medium.com/@shivanikakrecha/transaction-atomic-in-django-87b787ead793">https://medium.com/@shivanikakrecha/transaction-atomic-in-django-87b787ead793</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 | 전략 패턴]]></title>
            <link>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@combi_jihoon/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 16 Jun 2022 09:53:32 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 헤드퍼스트 디자인 패턴(한빛 미디어, 에릭 프리먼 외)를 읽고 정리한 것입니다.</em></p>
<br>

<p><strong>전략 패턴이란?</strong></p>
<blockquote>
<p>알고리즘군을 정의하고 캡슐화 하여, 각각의 알고리즘군을 수정해서 쓸 수 있도록 만드는 전략이다. 이를 통해 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경하는 것이 가능하다.</p>
</blockquote>
<br>

<h1 id="전략-패턴이-필요한-상황-생각하기">전략 패턴이 필요한 상황 생각하기</h1>
<p>디자인 패턴 책에는 전략 패턴을 사용하는 예시로 Duck 클래스를 생성하는 경우를 보여준다.
가령, Duck 추상 클래스가 있고 오리의 종류에 따라 해당 부모 클래스를 상속 받는 구상 클래스가 있다고 해보자.</p>
<ol>
<li>추상 클래스</li>
</ol>
<ul>
<li>Duck<ul>
<li>quack(): 울음 소리를 내는 메소드<ul>
<li>fly(): 나는 데 사용하는 메소드</li>
<li>display(): 형태를 보여주기 위한 메소드</li>
</ul>
</li>
</ul>
</li>
</ul>
<ol start="2">
<li>구상 클래스(추상 클래스를 상속 받음)</li>
</ol>
<ul>
<li>MallardDuck<ul>
<li>quack, fly, display 모두 가능하다.</li>
</ul>
</li>
<li>RedheadDuck<ul>
<li>quack, fly, display 모두 가능하다.</li>
</ul>
</li>
<li>RubberDuck<ul>
<li>quack, fly가 불가능하다.</li>
</ul>
</li>
</ul>
<p>위와 같은 상황에서 만약 나무로 된 가짜 오리를 추가해야 한다면 어떻게 될까? quack과 display 메소드를 오버라이딩 해서 새로운 특징을 추가해야 할 것이다. 그러나 이렇게 하면 아래와 같은 문제점이 있다.</p>
<ul>
<li>서브클래스에서 코드가 중복 된다.</li>
<li>실행 시에 특징을 바꾸기가 어렵다.</li>
<li>코드를 변경했을 때 다른 오리들에게 원치 않은 영향을 끼칠 수 있다.</li>
<li>모든 오리의 행동을 알기 어렵기 때문에 계속해서 메소드가 늘어나게 된다.</li>
</ul>
<br>

<h1 id="전략-패턴-적용하기">전략 패턴 적용하기</h1>
<h2 id="문제-파악">문제 파악</h2>
<p>애플리케이션에서 달라지는 부분은 나머지 코드에 영향을 주지 않도록 캡슐화 해야 한다. 이를 통해 나중에 바뀌지 않는 부분에는 영향을 주지 않는 선에서 바뀌는 부분만 고치거나 확장하는 것이 가능하다.</p>
<p>따라서, <strong>구성</strong>을 이용하는 방법을 통해 위의 예시에서 오리의 행동은 상속 받지 않고 오리의 행동 인터페이스를 통해 구성한 행동 객체를 부여 받도록 해야 한다.
-&gt; 즉, &quot;A는 B이다&quot;가 아니라 <strong>&quot;A에 B가 있다&quot;</strong>가 되도록 하는 것이다.</p>
<br>

<p><strong>유의할 점</strong></p>
<ul>
<li>실제 실행 시에 쓰이는 객체가 코드에 고정되지 않도록 상위 형식에 맞추어 프로그래밍 하여 다형성을 활용해야 한다.</li>
<li>객체를 변수에 대입할 때 상위 형식을 구현한 형식은 어떤 객체든 넣을 수 있다는 장점이 있다.</li>
</ul>
<br>

<h2 id="오리의-행동-구현하기">오리의 행동 구현하기</h2>
<h3 id="생성할-인터페이스">생성할 인터페이스</h3>
<p>아래의 인터페이스 각각에 대해 구체적인 행동을 구현하기 위한 클래스를 생성할 수 있다.</p>
<p><strong>FlyBehavior</strong></p>
<ul>
<li>메소드: fly()</li>
<li>구상 클래스: FlyWithWings, FlyNoWay</li>
</ul>
<p><strong>QuackBehavior</strong></p>
<ul>
<li>메소드: quack()</li>
<li>구상 클래스: Quack, Squeak, MuteQuack</li>
</ul>
<p>위의 방법과 같이 메소드를 사용하지 않고 다른 클래스에 위임하는 방법을 통해 다른 형식의 객체에서도 나는 행동과 꽥꽥 거리는 행동을 <strong>재사용</strong>할 수 있다.</p>
<h3 id="통합하기">통합하기</h3>
<p>오리의 특징을 정의할 Duck 클래스(abstract)를 생성한다.</p>
<p><strong>Duck</strong></p>
<ul>
<li><p>인스턴스 변수 선언</p>
<ul>
<li>FlyBehavior flyBehavior</li>
<li>QuackBehavior quackBehavior</li>
</ul>
</li>
<li><p>메소드</p>
<ul>
<li><p>performQuack()</p>
<pre><code class="language-java">public abstract class Duck {
  QuackBehavior quackBehavior;

     public void performQuack() {
      quackBehavior.quack();
  }  // performQuack 함수는 quackBehavior 인터페이스에 선언된 quack() 함수를 실행한다.
}</code></pre>
<ul>
<li>swim()</li>
<li>display()</li>
<li>performFly()</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>위에서 정의한 Duck 추상 클래스를 이용해 물오리의 특징을 정의할 MallardDuck 클래스를 생성할 수 있다.</p>
<pre><code class="language-java">public class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();  // 물오리는 날 수 있기 때문에 FlyWithWings 객체를 생성한다.
    }
    public void display() {
        System.out.println(&quot;물오리입니다&quot;);
    }
}
</code></pre>
<br>

<h1 id="전략-패턴-정리">전략 패턴 정리</h1>
<p>먼저, 전략 패턴을 살펴보면서 아래와 같은 객체 지향 원칙이 그 안에 녹아 있음을 확인할 수 있었다.</p>
<p><strong>객체 지향 원칙</strong></p>
<ul>
<li>바뀌는 부분을 캡슐화 한다.</li>
<li>상속 보다는 <strong>구성</strong>을 활용한다.</li>
<li>구현 보다는 인터페이스에 맞춰서 프로그래밍 한다.</li>
</ul>
<p>위에서 살펴본 오리의 나는 행동/ 꽥꽥 거리는 행동은 각 행동 집합에 대한 알고리즘군으로 생각할 수 있다. 각 행동은 또한 바뀔 수 있기 때문에 캡슐화 되어 있는 구조이다. </p>
<p>디자인 패턴 책에 따르면, 이처럼 알고리즘군을 이용하는 또 다른 방식으로는 세금 계산 방식을 구현하는 클래스가 있다고 한다. 이밖에도 우리 앱에서 이 패턴을 적용해 보기 위해서는 가령 장학금을 주는 방식이 여러가지라 가정했을 때 사용해 볼 수 있을 것 같다. 예를 들어 장학금을 현금으로 주는 방식, 상품권을 주는 방식, 특정 상품을 주는 방식 등으로 다양하다고 한다면 이를 알고리즘군으로 생각하고 전략 패턴을 적용해 볼 수도 있겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cloudfront Invalidation with Lambda Function]]></title>
            <link>https://velog.io/@combi_jihoon/Cloudfront-Invalidation-with-Lambda-Function</link>
            <guid>https://velog.io/@combi_jihoon/Cloudfront-Invalidation-with-Lambda-Function</guid>
            <pubDate>Fri, 03 Jun 2022 01:42:29 GMT</pubDate>
            <description><![CDATA[<p>CloudFront를 사용하면 한 번 요청한 객체에 대해서 캐싱을 해 전 세계에서 사용자와 가장 가까운 지역에 캐싱되어 있던 객체를 사용자에게 제공 한다. 그렇다면, 파이썬 코드로 어떻게 Cloudfront Invalidation을 실행할 수 있는지 알아보자. 그리고 이를 람다 환경에서 실행할 수 있도록 만들 것이다.</p>
<p>아래는 invalidation 메소드를 가진 CloudFrontManager라 명명한 클래스이다.</p>
<br>

<h3 id="cloudfrontmanager">CloudFrontManager</h3>
<pre><code class="language-python">
class CloudFrontManager:
    def __init__(self):
        self.client = boto3.client(&quot;cloudfront&quot;)

    def get_invalidation_id(self, key_list_to_be_invalidated: list) -&gt; str:
        response = self.client.create_invalidation(
            DistributionId=settings.CLOUDFRONT_INFO[
                &quot;DISTRIBUTION_ID&quot;
            ],
            InvalidationBatch={
                &quot;Paths&quot;: {
                    &quot;Quantity&quot;: 1,
                    &quot;Items&quot;: key_list_to_be_invalidated,
                },
                &quot;CallerReference&quot;: str(time.time()).replace(&quot;.&quot;, &quot;&quot;),
            },
        )
        invalidation_id = response[&quot;Invalidation&quot;][&quot;Id&quot;]

        return invalidation_id

    def get_invalidation_status(self, key_list_to_be_invalidated: list) -&gt; str:
        &quot;&quot;&quot;
        If finished successfully, response[&#39;Invalidation&#39;][&#39;Status&#39;] would be &quot;Completed&quot;
        &quot;&quot;&quot;

        response = self.client.get_invalidation(
            DistributionId=settings.CLOUDFRONT_INFO[
                &quot;DISTRIBUTION_ID&quot;
            ],
            Id=self.get_invalidation_id(
                key_list_to_be_invalidated=key_list_to_be_invalidated
            ),
        )
        return response[&quot;Invalidation&quot;][&quot;Status&quot;]</code></pre>
<br>

<ul>
<li><code>get_invalidation_id</code><ul>
<li>이 메소드를 이용하면 invalidation에 사용할 id를 얻을 수 있다.<ul>
<li>invalidation id를 얻기 위해서는 어떤 객체를 무효화 할 지에 대한 정보가 필요한데 여기에 사용되는 인자가 <code>key_list_to_be_invalidated</code>이다.</li>
<li><code>key_list_to_be_invalidated</code>는 [&#39;/path/to/invalidate/<em>&#39;]와 같은 형식으로 이루어져야 한다. 만약 &#39;invalidate/&#39; 디렉토리 내의 모든 객체를 무효화 하려면 &#39;</em>&#39; 표시를 사용하고, 특정 객체를 지정하려면 &#39;*&#39;을 없애고 특정 파일에 대한 key 값을 적으면 된다. 내가 무효화 하려는 객체는 <code>invalidate</code> 디렉토리 내의 모든 파일이기 때문에 위와 같이 <code>key_list_to_be_invalidated</code>를 넘겨 주었다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><code>get_invalidation_status</code><ul>
<li>이 메소드를 사용하면 무효화를 실행하고, 동시에 무효화 결과를 얻을 수 있다. <ul>
<li>무효화 결과는 response[&quot;Invalidation&quot;][&quot;Status&quot;]에 들어간다. 독스트링에도 추가해 놓았듯이 성공적으로 무효화가 실행되면 <code>response[&quot;Invalidation&quot;][&quot;Status&quot;]</code>는 &quot;Completed&quot;가 된다.</li>
<li>해당 메소드를 실제 운영 서버에 사용할 때는 &quot;Completed&quot;가 아니면 Exception을 raise 하도록 작성했다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="lambda-role-for-createinvalidation">Lambda Role for CreateInvalidation</h2>
<p>여기서 끝이 아니다. 
람다 환경에서 cloudfront invalidation이 일어나도록 만들기 위해서는 lambda 함수에 role을 생성해 주어야 한다. 
<a href="https://www.msp360.com/resources/blog/how-to-automatically-invalidate-dynamic-objects-in-amazon-cloudfront-using-aws-lambda/">여기</a>를 참고해 role을 생성했다.</p>
<pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
        &quot;Effect&quot;: &quot;Allow&quot;,
        &quot;Action&quot;: [
            &quot;cloudfront:CreateInvalidation&quot;
        ],
        &quot;Resource&quot;: [
            &quot;&lt;Your ARN&gt;&quot;
        ]
    }
  ]
}</code></pre>
<ul>
<li>cloudfront에 대한 <strong>CreateInvalidation</strong> 액션이 필요하다.</li>
<li>참고한 링크에서는 Resource를 &#39;*&#39;로 했는데, 운영 환경과 개발 환경을 분리해 두었기 때문에 운영 람다에 대해서는 운영 서버에서 사용하는 Cloudfront Distribution ID를, 개발용 람다에 대해서는 개발용 서버에서 사용할 Cloudfront Distribution ID를 이용해 ARN을 작성했다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Solutions Architect | Design High Performing Architecture(2) - FSx]]></title>
            <link>https://velog.io/@combi_jihoon/AWS-Solutions-Architect-Design-High-Performing-Architecture2</link>
            <guid>https://velog.io/@combi_jihoon/AWS-Solutions-Architect-Design-High-Performing-Architecture2</guid>
            <pubDate>Sun, 29 May 2022 03:44:53 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 Udemy의 AWS Certified Solutions Architect Associate Practice Exams를 공부하며 정리한 것입니다.</em></p>
<br>

<h2 id="fsx란">FSx란?</h2>
<ul>
<li>Amazon FSx는 <strong>Windows 파일 서버용</strong>으로 사용되며 완전 기본 Windows 파일 시스템이 지원하는 <strong>완전 관리형 Microsoft Windows 파일 서버를 제공</strong>한다.</li>
<li>FSx는 회사에서 사용하는 애플리케이션을 AWS 클라우드로 쉽게 옮기고 전환할 수 있는 기능, 성능 및 호환성을 갖추고 있다.</li>
<li>FSx는 Windows, Linux 및 macOS 컴퓨팅 인스턴스 및 장치에서 액세스할 수 있.<ul>
<li>또한, 수천 개의 컴퓨팅 인스턴스와 장치가 파일 시스템에 동시에 액세스할 수 있다는 이점도 있다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="언제-사용하는-것이-좋을까">언제 사용하는 것이 좋을까?</h3>
<p>아래와 같은 상황의 회사에서 사용하면 좋다.</p>
<blockquote>
<p>만약 어떤 회사가 Windows Server용 IIS(인터넷 정보 서비스)를 사용하는 웹 응용 프로그램을 가지고 있다고 하자. 이 회사는 파일 공유를 사용하는데 이는 회사 온프레미스 데이터 센터의 네트워크 연결 스토리지에 애플리케이션 데이터를 저장하는 데 사용된다. 그런데 이 회사는 고가용성을 위해 애플리케이션과 파일 공유를 AWS로 마이그레이션할 계획이다.</p>
</blockquote>
<p>이 때 사용할 수 있는 가장 알맞은 서비스는 <strong>FSx</strong>이다.</p>
<ul>
<li>위 상황에서는 기존 파일 공유 구성을 클라우드로 마이그레이션해야 한다.</li>
<li>파일 공유는 <strong>SMB 프로토콜</strong>을 통해 컴퓨팅 인스턴스에 액세스할 수 있는 폴더의 하위 폴더를 포함하는 파일 시스템의 특정 폴더를 의미한다. </li>
<li>온프레미스 파일 시스템에서 파일 공유 구성을 마이그레이션하려면 파일 공유 구성을 마이그레이션하기 전에 먼저 파일을 Amazon FSx로 마이그레이션해야 한다.</li>
</ul>
<br>

<p><em>SMB 프로토콜이란?</em></p>
<ul>
<li>SMB(서버 메시지 블록) 프로토콜은 컴퓨터의 애플리케이션에서 파일을 읽고 쓸 수 있으며 컴퓨터 네트워크상의 서버 프로그램에서 서비스를 요청할 수 있도록 지원하는 네트워크 파일 공유 프로토콜이다.</li>
</ul>
<br>

<h3 id="fsx와-유사하지만-다른-서비스들">FSx와 유사하지만 다른 서비스들</h3>
<h4 id="aws-storage-gateway">AWS Storage Gateway</h4>
<ul>
<li>Storage Gateway는 온프레미스 네트워크를 AWS에 연결하기 위해 사용된다.</li>
<li>그러나 Storage Gateway는 애플리메이션에 대한 마이그레이션 기능을 제공하지는 않는다.</li>
<li>만약 Storage Gatway 내의 파일 공유를 사용하게 되면 이 경우 온프레미스에서 AWS로 완전히 마이그레이션 하는 것이 아니라 여전히 온프레미스를 사용하고 있음을 의미한다.</li>
</ul>
<br>

<h4 id="efs">EFS</h4>
<ul>
<li>EFS는 파일을 추가하고 제거할 때 자동으로 확장되고 축소되어 관리 또는 프로비저닝이 필요하지 않은 파일 스토리지 제공 서비스이다.</li>
<li>EFS를 여러 인스턴스에서 실행하는 워크로드 및 애플리케이션에 대한 공통 데이터 소스로 사용할 수 있다.</li>
<li>EFS는 그러나 리눅스 워크로드만 지원하기 때문에 위의 예시처럼 Window를 사용하는 경우에는 적합하지 않다.</li>
</ul>
<br>

<h4 id="ebs">EBS</h4>
<ul>
<li>EBS는 주로 EC2 인스턴스에 대한 블록 스토리지로 사용되며 파일 공유 시스템으로써 사용되지는 않는다.</li>
<li>파일 공유는 파일 시스템에 속한 특정 폴더로써 SMB(Server Message Block) 프로토콜을 이용해 접근할 수 있다. </li>
<li>EBS는 SMB 프로토콜을 지원하지 않는다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Solutions Architect | DB Authentication to improve security
]]></title>
            <link>https://velog.io/@combi_jihoon/AWS-Solutions-Architect-DB-Authentication-to-improve-security</link>
            <guid>https://velog.io/@combi_jihoon/AWS-Solutions-Architect-DB-Authentication-to-improve-security</guid>
            <pubDate>Sun, 29 May 2022 03:12:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>만약 어떤 회사에서 고객의 기밀 데이터를 보호하기 위해 authentication token을 통해 EC2 인스턴스에 대해 특정한 프로필 자격 증명(credentials)을 사용해서만 RDS 데이터베이스에 액세스할 수 있도록 하도록 설정해야 한다고 하자. 만약 이 회사가 사용하는 RDS가 MySQL 또는 PostgreSQL이라면 어떤 방법을 이용해 기밀 정보를 보호할 수 있을까?</p>
</blockquote>
<br>

<h2 id="iam-db-authentication">IAM DB Authentication</h2>
<ul>
<li>DB 인스턴스에 대한 접속 인증을 위해 IAM을 사용할 수 있다.<ul>
<li>IAM 인증은 MySQL과 PostgreSQL에 대해서만 사용할 수 있다. <ul>
<li>이 인증 방법을 이용하면 DB 인스턴스에 접속하기 위해 인증 토큰을 이용하기 때문에 비밀번호를 사용하지 않아도 된다.</li>
</ul>
</li>
</ul>
</li>
<li>이 방법 말고도 전형적인 인증 방법인 public IP에 대해 password를 이용해 접속하는 것 역시 가능하긴 하다.</li>
<li>인증 토큰은 RDS 접속에 대한 요청이 있을 때만 생성이 되며 &#39;unique string of characters&#39;로 이루어져 있다.<ul>
<li>이 토큰은 15분 동안 유효하며 DB에 자격 증명(credentials)을 저장하지 않아도 된다. 왜냐하면 인증은 외부적으로 IAM을 통해 이루어지기 때문이다.</li>
</ul>
</li>
</ul>
<br>

<h2 id="iam-db-authentication-사용시-이점">IAM DB Authentication 사용시 이점</h2>
<ul>
<li>RDS로부터 오는/ RDS로 향하는 네트워크 트래픽이 암호화 된다.<ul>
<li>이 때 Secure Sockets Layer(SSL)을 이용하게 된다.</li>
</ul>
</li>
<li>IAM을 이용하면 DB 인스턴스에 대한 접속 관리를 개별적으로 하지 않고 중앙에서 IAM을 이용해 할 수 있기 때문에 편리하다.</li>
<li>만약 예를 들어 EC2와 같은 서비스를 통해 애플리케이션을 제공할 경우 EC2 인스턴스</li>
<li>가령 EC2에서 실행되는 애플리케이션의 경우 EC2 인스턴스와 관련된 프로필 자격 증명을 사용할 경우 데이터베이스에 접근하기 위해 암호 대신 보안이 더 강화된 방법으로 접근할 수 있게 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Solutions Architect | Design High Performing Architecture(1)]]></title>
            <link>https://velog.io/@combi_jihoon/AWS-Solutions-Architect-Design-High-Performing-Architecture1</link>
            <guid>https://velog.io/@combi_jihoon/AWS-Solutions-Architect-Design-High-Performing-Architecture1</guid>
            <pubDate>Sun, 22 May 2022 13:03:35 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 Udemy의 AWS Certified Solutions Architect Associate Practice Exams를 공부하며 정리한 것입니다.</em></p>
<h2 id="상황1">상황1</h2>
<p>어떤 회사에서 자주 접근하는 데이터를 S3에 저장하려고 하는 데 이 때 객체의 삭제 또는 생성이 일어날 경우 S3 버킷이 SQS에 알림을 보내도록 설정하고 싶다. 이 알림은 개발 팀 또는 운영 팀에게 객체의 상태에 대해 알려주는 알림이다.</p>
<h2 id="해결책">해결책</h2>
<h3 id="기능-분석">기능 분석</h3>
<h4 id="s3의-알림-기능">S3의 알림 기능</h4>
<ul>
<li>S3는 버킷에 특정 이벤트가 발생할 때 알림을 보내줄 수 있다.</li>
<li>이 기능은 S3 &gt; 속성 &gt; 이벤트 알림 탭에서 설정할 수 있다.</li>
<li>S3 알림이 이벤트를 게시할 수 있는 대상은 아래와 같이 3가지가 있다.<ul>
<li>SNS topic(주제)</li>
<li>SQS queue</li>
<li>lambda 함수
<img src="https://velog.velcdn.com/images/combi_jihoon/post/04564423-f0b6-4215-8d6f-32c172947607/image.png" alt=""></li>
</ul>
</li>
</ul>
<h4 id="sns의-특징">SNS의 특징</h4>
<ul>
<li>만약 SNS 주제가 복제 되어 여러 엔드포인트(ex) SQS, HTTPS(S), 람다 함수 등)로 전달 되었을 경우 여러 엔드포인트에서 동시에 비동기 프로세싱이 일어나게 된다.
<img src="https://velog.velcdn.com/images/combi_jihoon/post/1274a544-9f52-4845-8b5c-7190284e2e22/image.png" alt=""><ul>
<li>즉, 특정 SNS 주제에 구독한 SQS가 SNS로부터 동시에 알림을 받아 해당 알림을 분석하고 작업을 수행하는 등의 일을 할 수 있는 것이다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="답-찾기">답 찾기</h3>
<p>이제 해당 상황에 대한 답을 찾아 보면 다음과 같다.</p>
<p>우리는 fan out 패턴을 이용하기 위해 한 SNS 주제와 두 개의 SQS 큐를 생성할 수 있다. 이 때 두 SQS 큐는 각각 생성, 삭제에 관한 메시지를 받을 것이다. 만약 SNS가 이벤트 알림을 받으면 이 SNS는 각 구독자들에게 해당 메시지를 게시하게 된다.</p>
<p>주의해야 할 점은 S3 이벤트 알림 기능은 적어도 한 번 알림을 전송하며 한 번에 한 목적지로만 이동할 수 있다는 것이다. 즉, 여러 SNS 주제를 구독할 수는 없다.</p>
<p>따라서 이 상황에 대한 답을 정리하면, 
<strong>먼저 SNS 주제를 생성하고 두 개의 큐가 해당 주제를 구독하도록 한다. 그 다음 S3가 SNS로 알림을 보낼 수 있도록 permission을 생성하고 S3가 해당 SNS 주제를 이용할 수 있도록 설정하면 된다.</strong></p>
<br>

<p><strong>[확인해야 할 점]</strong></p>
<ul>
<li>만약 한 S3가 여러 SNS를 구독할 수 있도록 하는 내용의 보기가 나온다면 이 보기는 틀린 보기이다. </li>
<li>SQS는 SNS로부터 폴링을 할 수 없다. 단, 특정 서비스가 SQS로부터 폴링하는 것은 가능하다. SQS는 SNS 주제를 <strong>구독</strong>하는 것이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Two Scoops of Django | 단위 테스트 작성하기]]></title>
            <link>https://velog.io/@combi_jihoon/Two-Scoops-of-Django-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@combi_jihoon/Two-Scoops-of-Django-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 22 May 2022 12:30:50 GMT</pubDate>
            <description><![CDATA[<p><em>이 글은 모범 사례로 배우는 Django 테크닉에 관한 명서 &quot;Two Scoops of DJango&quot;(대니얼 로이 그린펠드, 오드리 로이 그린펠드 저, 2016)를 읽고 요약 정리하여 작성한 것입니다. 이 책을 읽고 새로웠던 것, 기존에 잘 알지 못했던 것을 위주로 작성했습니다.</em></p>
<br>
<br>
<br>

<p><strong>장고 프로젝트 테스팅에 유용한 라이브러리</strong></p>
<blockquote>
<p><code>coverage.py</code>는 코드의 어떤 부분이 테스트 되었고, 어떤 라인들이 테스트 되지 않았는지 명확히 나타내 준다.</p>
</blockquote>
<br>


<h2 id="단위-테스트-작성하기">단위 테스트 작성하기</h2>
<ul>
<li><p>각 테스트 메소드는 테스트를 한 가지씩 수행해야 한다.</p>
</li>
<li><p>테스트는 같거나 비슷한 코드를 여러 번 반복해도 된다.</p>
<ul>
<li>오히려, 복사 붙여넣기를 반복하는 것이 더 선호 된다.</li>
</ul>
</li>
<li><p>&quot;Mock&quot;을 이용해서 실제 데이터에 문제를 일으키지 않고 테스트할 수 있다.</p>
<ul>
<li><p>단위 테스트 중에는 외부 API에 대한 접속 혹은 이메일 수신, 웹훅을 비롯한 테스트 외적인 환경에 대한 액션이 취해져서는 안된다.</p>
<ul>
<li>만약 외부 환경에 대한 테스트가 필요하다면 단위 테스트를 통합 테스트로 변경 하거나 &quot;Mock&quot;을 이용해 외부 API에 대한 가짜 응답을 만드는 것(이를 &quot;멍키 패치&quot;라 한다)이 좋다.</li>
<li>아래의 예시로 멍키 패치를 살펴 보자. <strong>mock</strong> 데코레이터를 이용하면 된다.</li>
</ul>
<p><strong>&lt;예시 1&gt;</strong></p>
<pre><code class="language-python">    import mock
    import unittest

    import icreamapi

    from flavors.exceptions import CantListFlavors
    from flavors.utils import list_flavors_sorted

    class TestIceCreamSorting(unittest.TestCase):

        # &quot;icreamapi.get_flavors&quot; 멍키패치 세팅
        @mock.patch.object(icreamapi, &quot;get_flavors&quot;)
        def test_flavor를_정렬_할_수_있다(self, get_flavors):
            get_flavors.return_value = [&quot;a&quot;, &quot;c&quot;, &quot;b&quot;]  # 정렬되지 않은 리스트 생성

            flavors = list_flavors_sorted()

            self.asssertEqual(
                  flavors,
                [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
            )</code></pre>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="통합-테스트-하기">통합 테스트 하기</h2>
<ul>
<li>단위 테스트가 끝난 후 행하는 것이 가장 이상적이다.</li>
<li><code>coverage.py</code>를 이용해 테스트가 얼만큼 커버하는 지에 대한 정도를 퍼센트로 제공받을 수 있다.<ul>
<li>또한, 코드의 어떤 부분이 테스트 되었고 또 테스트 되지 않았는 지를 알 수 있다.</li>
</ul>
</li>
</ul>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[Cloudfront에 signed cookie로 access 하기]]></title>
            <link>https://velog.io/@combi_jihoon/Cloudfront%EC%97%90-signed-cookie%EB%A1%9C-access-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@combi_jihoon/Cloudfront%EC%97%90-signed-cookie%EB%A1%9C-access-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 16 May 2022 14:24:57 GMT</pubDate>
            <description><![CDATA[<p>Cloudfront에 signed cookie로 access 하기</p>
<h2 id="signed-cookie를-이용하게-된-배경">Signed cookie를 이용하게 된 배경</h2>
<p>Cloudfront에서 특정 유저만 url에 접근하도록 하고 싶은 경우 사용할 수 있는 방법은 크게 3가지가 있다.</p>
<ul>
<li>pre-signed url</li>
<li>signed url</li>
<li>signed cookie</li>
</ul>
<p>만약 프론트엔드가 url에 접근하려 한다면 <strong>signed url</strong>과 <strong>pre-signed url</strong>을 이용하는 방법이 있는데 <strong>pre-signed url</strong>을 이용할 경우 <strong>cognito</strong>를 이용해야 한다는 번거로움이 있다. 또한, S3의 특정 디렉토리에 있는 여러 개의 파일을 가져와야 하는데 이를 위해서는 <strong>signed cookie</strong>가 필요하다. 우리는 여러 개의 파일에 접근해야 하기 때문에 백엔드에서 signed cookie를 받아와 이를 프론트엔드에 전달해 주는 작업을 하려고 한다. 과정은 다음과 같다.</p>
<h3 id="목차">목차</h3>
<h4 id="aws-인프라-파트">AWS 인프라 파트</h4>
<ol>
<li>S3 버킷 생성</li>
<li>암호화 작업<ul>
<li>pem key 다운로드(퍼블릭 키 생성)</li>
<li>Cloudfront에 public key 추가하기<ul>
<li>key groups 생성</li>
</ul>
</li>
</ul>
</li>
<li>Cloudfront 배포 생성 전 <ul>
<li>원본 Access ID 생성</li>
</ul>
</li>
<li>Cloudfront 배포 생성<ul>
<li>S3를 origin으로 해야 함</li>
<li>위에서 생성한 key groups 연결</li>
<li>원본 Access ID에 대한 OAI 생성</li>
</ul>
</li>
</ol>
<h4 id="소스-코드-파트">소스 코드 파트</h4>
<ol>
<li>signed cookie를 가져오기 위한 코드 작업</li>
<li>테스트</li>
</ol>
<br>

<h2 id="aws-인프라-작업">AWS 인프라 작업</h2>
<h3 id="s3-버킷-생성">S3 버킷 생성</h3>
<ul>
<li>퍼블릭 액세스를 차단한 버킷을 새로 생성한다.</li>
</ul>
<h3 id="암호화-작업">암호화 작업</h3>
<h4 id="pem-key-다운로드">pem key 다운로드</h4>
<p>다음과 같은 방법으로 public key와 private key를 다운 받아 key pair를 생성할 수 있다(<a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html#private-content-creating-cloudfront-key-pairs">aws 공식 문서 참고</a>). </p>
<ul>
<li>openssl을 이용해 2048비트의 RSA 키 페어를 생성하고 <code>private_key.pem</code> 파일에 저장한다.<ul>
<li>이렇게 만들어진 파일은 퍼블릭 키와 프라이빗 키를 모두 포함하고 있다.<pre><code class="language-bash">openssl genrsa -out private_key.pem 2048</code></pre>
</li>
</ul>
</li>
<li>이제 <code>private_key.pem</code> 파일에서 <code>public_key.pem</code> 파일로 공개키를 추출한다.<pre><code class="language-bash">  openssl rsa -pubout -in private_key.pem -out public_key.pem</code></pre>
</li>
</ul>
<h4 id="cloudfront에-public-key-추가하기">Cloudfront에 public key 추가하기</h4>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/b8714514-fcbd-4ce5-b96d-50af27d46403/image.png" alt=""></p>
<ul>
<li><code>CloudFront &gt; Public key &gt; 퍼블릭 키 생성</code> 에서 public key를 등록한다.<ul>
<li>식별하기 쉬운 Name을 지정한 후(Description도 넣으면 좋다) Key에 <code>cat public_key.pem</code>으로 나온 내용을 복붙한다.</li>
</ul>
</li>
</ul>
<h4 id="cloudfront에-key-groups-등록하기">Cloudfront에 key groups 등록하기</h4>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/6849aadd-e645-4b49-95ec-02389b0c3016/image.png" alt=""></p>
<ul>
<li>원하는 이름을 추가한다.</li>
<li>public key에 위에서 생성한 public key를 선택한다.<ul>
<li>1 ~ 5개까지의 key를 한 그룹에 등록할 수 있다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="cloudfront-배포-생성-전-작업">Cloudfront 배포 생성 전 작업</h3>
<ul>
<li>배포를 생성하기 전에 <strong>원본 액세스 ID</strong>를 가지고 있어야 한다.</li>
<li>이는 S3에 액세스 하기 위해 Cloudfront에 지정할 액세스 ID이다.<ul>
<li>이를 S3 OAI 생성시 ID로 사용한다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="cloudfront-배포">Cloudfront 배포</h3>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/cb4d9ef0-4494-4a1a-8ca0-d96a4a9182a4/image.png" alt=""></p>
<ul>
<li>오리진은 위에서 생성한 S3로 선택한다.</li>
<li>주의해야 할 점은 OAI를 사용하도록 설정해야 한다는 것이다.<ul>
<li>배포 생성 전 만든 원본 액세스 ID를 선택하고, <strong>버킷 정책 업데이트</strong>를 선택하면 Cloudfront 배포 생성시 S3에서 정책이 알아서 업데이트 된다.</li>
</ul>
</li>
</ul>
<br>

<h2 id="소스코드-작업">소스코드 작업</h2>
<p>boto3 패키지에서 signed cookie 생성은 지원이 안된다.... 그렇기 때문에 직접 코드를 짜야 한다. 그러나 여러 사람들이 짜 놓은 코드들이 있었는데 이를 가져와서 조금의 변형을 거쳐 코드를 완성했다. 참고한 소스 코드 링크는 <a href="https://gist.github.com/mjohnsullivan/31064b04707923f82484c54981e4749e">여기</a> 있다.</p>
<p><strong>수정한 코드</strong></p>
<ul>
<li>signed cookies 생성에 사용되는 함수들을 클래스화 하여 어떤 함수를 호출해야 하는지 조금 더 명확히 하고자 했다.<ul>
<li>나중에 사용하려고 할 때 어떤 함수를 호출해야 하는지 헷갈릴 것 같아 클래스화 했다.<ul>
<li>또한, 타입 어노테이션을 추가해 파라미터 타입과 리턴 타입을 명시했다.</li>
</ul>
</li>
</ul>
</li>
<li>실제로 signed cookies를 생성할 때는 <code>get_signed_cookies</code> 함수만 호출하면 된다.<ul>
<li>signed cookies는 세 가지로 구성 된다.<ul>
<li><code>CloudFront-Policy</code>, <code>CloudFront-Signature</code>, <code>CloudFront-Key-Pair-Id</code></li>
</ul>
</li>
</ul>
</li>
<li>여기서 사용된 cloudfront id는 cloudfront의 public key id이다. <ul>
<li>위에서 Cloudfront에 public key를 추가할 때 생성된 public key id이다. 이 부분이 헷갈릴 것 같아 변수명을 조금 더 정확히 명시했다.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">class SignedCookieGenerator:
    &quot;&quot;&quot;
    클라이언트에서 cloudfront로부터 원하는 이미지를 가져오기 위해 필요한 signed cookie를 생성한다.
    &quot;&quot;&quot;

    @staticmethod
    def _replace_unsupported_chars(some_str: str) -&gt; str:
        &quot;&quot;&quot;Replace unsupported chars: &#39;+=/&#39; with &#39;-_~&#39;&quot;&quot;&quot;
        return some_str.replace(&quot;+&quot;, &quot;-&quot;).replace(&quot;=&quot;, &quot;_&quot;).replace(&quot;/&quot;, &quot;~&quot;)

    @staticmethod
    def _in_an_hour() -&gt; int:
        &quot;&quot;&quot;Returns a UTC POSIX timestamp for one hour in the future&quot;&quot;&quot;
        return int(time.time()) + (60 * 60)

    @staticmethod
    def rsa_signer(message: bytes, key: bytes) -&gt; bytes:
        &quot;&quot;&quot;
        Updated version
        Based on https://boto3.readthedocs.io/en/latest/reference/services/cloudfront.html#examples
        &quot;&quot;&quot;
        private_key = serialization.load_pem_private_key(
            key, password=None, backend=default_backend()
        )
        signature = private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
        return signature

    def generate_policy_cookie(self, url: int) -&gt; tuple:
        &quot;&quot;&quot;Returns a tuple: (policy json, policy base64)&quot;&quot;&quot;

        # expires in an hour
        policy_dict = {
            &quot;Statement&quot;: [
                {
                    &quot;Resource&quot;: url,
                    &quot;Condition&quot;: {
                        &quot;DateLessThan&quot;: {&quot;AWS:EpochTime&quot;: self._in_an_hour()}
                    },
                }
            ]
        }

        # Using separators=(&#39;,&#39;, &#39;:&#39;) removes seperator whitespace
        policy_json = json.dumps(policy_dict, separators=(&quot;,&quot;, &quot;:&quot;))

        policy_64 = str(base64.b64encode(policy_json.encode(&quot;utf-8&quot;)), &quot;utf-8&quot;)
        policy_64 = self._replace_unsupported_chars(policy_64)
        return policy_json, policy_64

    def generate_signature(self, policy: json, private_key: bytes):
        &quot;&quot;&quot;Creates a signature for the policy from the key, returning a string&quot;&quot;&quot;
        sig_bytes = self.rsa_signer(policy.encode(&quot;utf-8&quot;), private_key)
        sig_64 = self._replace_unsupported_chars(
            str(base64.b64encode(sig_bytes), &quot;utf-8&quot;)
        )
        return sig_64

    @staticmethod
    def generate_cookies(policy: json, signature: str, cloudfront_id: str) -&gt; dict:
        &quot;&quot;&quot;Returns a dictionary for cookie values in the form &#39;COOKIE NAME&#39;: &#39;COOKIE VALUE&#39;&quot;&quot;&quot;
        return {
            &quot;CloudFront-Policy&quot;: policy,
            &quot;CloudFront-Signature&quot;: signature,
            &quot;CloudFront-Key-Pair-Id&quot;: cloudfront_id,
        }

    def generate_signed_cookies(
        self, url: int, cloudfront_id: str, private_key: bytes
    ) -&gt; dict:
        policy_json, policy_64 = self.generate_policy_cookie(url)
        signature = self.generate_signature(policy_json, private_key)
        return self.generate_cookies(policy_64, signature, cloudfront_id)


def get_signed_cookies() -&gt; dict:
    &quot;&quot;&quot;
    How to get pem keys
    1. private_key with RSA
        $ openssl genrsa -out private_key.pem 2048
    2. public_key from private_key
        $ openssl rsa -pubout -in private_key.pem -out public_key.pem

    Signed Cookie consists of 3 keys
    &quot;&quot;&quot;

    CLOUDFRONT_URL = settings.CLOUDFRONT_URL
    CLOUDFRONT_PUBLIC_KEY_ID = os.getenv(&quot;CLOUDFRONT_PUBLIC_KEY_ID&quot;)

    signed_cookie_generator = SignedCookieGenerator()
    path_to_pem_key = os.path.join(settings.BASE_DIR, &quot;private_key.pem&quot;)

    with open(path_to_pem_key, &quot;rb&quot;) as f:
        cookies = signed_cookie_generator.generate_signed_cookies(
            url=CLOUDFRONT_URL,
            cloudfront_id=CLOUDFRONT_PUBLIC_KEY_ID,
            private_key=f.read(),
        )

    return cookies</code></pre>
<br>

<h3 id="테스트">테스트</h3>
<p>프론트엔드에 signed cookie를 전달해 주기 전에 실제로 원하는 url에 접근하려 할 때 signed cookie를 이용할 수 있는 지를 테스트 해 보았다. 
테스트 코드는 다음과 같다.</p>
<pre><code class="language-python">import requests

from utils.aws import get_signed_cookies  # 위에서 생성한 get_signed cookies 함수 임포트(utils/aws.py 파일에 생성함)


def images_request_test() -&gt; None:
    &quot;&quot;&quot;
    signed cookie를 이용해 cloudfront에 요청시 status code 200이 오는지 테스트 하기 위한 스크립트
    문제 발생시를 대비해 임시로 사용할 예정
    &quot;&quot;&quot;

    cloudfront_url = CLOUDFRONT_URL
    cookies = get_signed_cookies()
    print(&quot;signed cookie 정보&quot;, cookies)

    images = [
        image_id_1, image_id_2, ...
    ]
    try:
        print(&quot;요청을 시작합니다.&quot;)
        responses = []
        for image in images:
            key = f&quot;test/{image}.png&quot;
            url = f&quot;{cloudfront_url}/{key}&quot;
            response = requests.get(url, cookies=cookies)
            print(response.status_code)
            responses.append(response.status_code)
        print(responses)
    except Exception as e:
        print(e)
    print(&quot;끄읕&quot;)</code></pre>
]]></description>
        </item>
    </channel>
</rss>