<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ben_jh.log</title>
        <link>https://velog.io/</link>
        <description>Ben</description>
        <lastBuildDate>Mon, 04 Jul 2022 05:46:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. ben_jh.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ben_jh" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Like 쿼리 Search 개선
]]></title>
            <link>https://velog.io/@ben_jh/Like-%EC%BF%BC%EB%A6%AC-Search-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@ben_jh/Like-%EC%BF%BC%EB%A6%AC-Search-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Mon, 04 Jul 2022 05:46:12 GMT</pubDate>
            <description><![CDATA[<h2 id="like-target-와-like-target-의-차이점"><code>LIKE {target}%</code> 와 <code>LIKE %{target}%</code> 의 차이점</h2>
<ul>
<li>LIKE를 이용한 쿼리는 Full table search를 이용하는 경우 굉장히 느리다<ul>
<li>O(n)</li>
</ul>
</li>
<li>INDEX를 생성해야한다.<ul>
<li>그냥 INDEX를 생성하는 경우 btree기반 Index가 생성된다<ul>
<li><code>{target}%</code>를 쿼리하는 것은 빠르다.<ul>
<li>btree의 상단에서부터 분기점을 고를 수 있기 때문에</li>
<li>O(log(n))</li>
</ul>
</li>
<li><code>%{target}%</code>를 쿼리하는 것은 느리다<ul>
<li>btree의 상단에서부터 분기점을 고를 수 없으므로 결국 full table search와 같다</li>
<li>O(n)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="prefix가-정해지지-않은-target을-위한-index-">Prefix가 정해지지 않은 target을 위한 Index ?</h2>
<ul>
<li>Postgresql 에서는 <code>GiN</code>, <code>GiST</code>와 같은 텍스트 검색을 위한 인덱싱을 제공한다.</li>
</ul>
<h3 id="gin-gist-비교-docs"><code>GiN</code>, <code>GiST</code> 비교 <a href="https://www.postgresql.org/docs/9.4/textsearch-indexes.html">Docs</a></h3>
<ul>
<li>GIN index lookups are about three times faster than GiST</li>
<li>GIN indexes take about three times longer to build than GiST</li>
<li>GIN indexes are moderately slower to update than GiST indexes, but about 10 times slower if fast-update support was disabled (<a href="https://www.postgresql.org/docs/11/gin-implementation.html#GIN-FAST-UPDATE">docs</a>)</li>
<li>GIN indexes are two-to-three times larger than GiST indexes</li>
</ul>
<h3 id="gin-index-구현-설명"><code>GiN</code> Index 구현 설명</h3>
<ul>
<li>gabcgaga 문자열에서 {&#39;ga&#39;: [(0,1), (4,5), (6,7)]}과 같은 형태로 Index를 미리 생성</li>
</ul>
<h3 id="gin-적용방법"><code>GiN</code> 적용방법</h3>
<pre><code class="language-python">from django.contrib.postgres.search import SearchVectorField
# Model에 새로운 필드를 생성한다
from django.contrib.postgres.indexes import GinIndex
# Model Meta에 Index를 추가한다.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pythonic code]]></title>
            <link>https://velog.io/@ben_jh/Pythonic-code</link>
            <guid>https://velog.io/@ben_jh/Pythonic-code</guid>
            <pubDate>Tue, 21 Jun 2022 23:22:01 GMT</pubDate>
            <description><![CDATA[<h2 id="class의-pythonic-메서드">class의 pythonic 메서드</h2>
<h3 id="시퀀스-생성-메소드">시퀀스 생성 메소드</h3>
<ul>
<li><p>python에서는 list에 대해 <code>[::]</code>와 같은 형태로 인덱싱을 표현할 수 있다. </p>
</li>
<li><p>자체 시퀀스를 생성</p>
<ul>
<li><code>__getitem__</code>과 <code>__len__</code> 구현</li>
<li>만약 클래스에 대해 인덱싱이 필요한 경우 리스트나 이터러블 객체를 새로 생성하는 것은 비효율 적이고, 위의 매직 메소드를 구현해서 자체 시퀀스를 생성한다.</li>
<li><code>collections.UserList</code>를 상속할 수도 있다.</li>
</ul>
</li>
</ul>
<h3 id="컨텍스트-관리자">컨텍스트 관리자</h3>
<ul>
<li><p>컨텍스트 관리자는 특정 동작 전후에 작업을 실행하도록 하는 python의 유용한 패턴이다.</p>
<ul>
<li>e.g. http connection 연결 -&gt; 특정 작업 -&gt; 연결을 반환</li>
<li><code>with</code> 과 함께 사용한다.</li>
</ul>
</li>
<li><p><code>__exit__</code>, <code>__enter__</code> 메서드를 구현함으로써 클래스(객체)를 컨텍스트 관리자처럼 사용할 수 있다.</p>
</li>
</ul>
<h3 id="프로퍼티">프로퍼티</h3>
<ul>
<li>파이써닉한 게터세터 패턴이다</li>
<li>다른 언어처럼 get_{....}처럼 value를 들고오고 set하는 것이 아니라, 
프로퍼티 데코레이터를 사용함으로써 변수 자체에 직접 접근하는 효과를 가질 수 있다. {property 변수}.setter로 property 변수를 수정, 저장하는 과정을 보호할 수 있다.</li>
</ul>
<h3 id="이터러블-객체">이터러블 객체</h3>
<ul>
<li>python에서는 for ~ in ~ 와 같은 형태의 구문을 자주 사용한다.<ul>
<li>list, generator, set 등 대부분의 자료구조에 대해 사용할 수 있다.</li>
</ul>
</li>
<li>클래스에 <code>__next__</code>, <code>__iter__</code> 메소드 중 하나를 가지는 경우 클래스는 이터러블하며 for 루프에서 사용가능하다.</li>
</ul>
<h3 id="컨테이너-객체">컨테이너 객체</h3>
<ul>
<li>python에서 어떤 요소가 객체에 존재하는지 확인하는 <code>in</code>과 같은 문법은 직관성을 제공한다</li>
<li>클래스에 <code>__contains__</code> 메소드를 구현하면 클래스 객체를 이용해 코드를 직관적으로 작성할 수 있다.</li>
</ul>
<h2 id="디자인-원칙">디자인 원칙</h2>
<h3 id="계약에-의한-디자인">계약에 의한 디자인</h3>
<ul>
<li>함수의 입력 변수를 검증하는 작업은 항상 필요한데, 함수에 입력 변수를 넣기 전에 검증할 것인지, 입력된 변수에 대해 함수 안에서 검증할 것인지가 중요하다.</li>
<li>사전조건은 코드가 실행되기 전에 체크해야하는 것이고, 사후조건은 함수 리턴 값에 대해서 체크해야한다.</li>
<li>코드 전체에 대해 모두 사전조건을 따르게 하거나, 사후조건을 따르게 하는 등 통일 시키는 것이 좋고, 에러가 발생했을 때 책임 소재를 분명하게 할 수 있어 디버깅에 유리하다.<h3 id="방어적-프로그래밍">방어적 프로그래밍</h3>
</li>
<li>발생할 수 있는 에러에 대해서 처리하는 계획을 세우는 것이 중요하다.<ul>
<li>심각한 결함을 초래하는 경우 바로 프로그램을 중단해야하고, </li>
<li>일반적인 결함은 예외를 발생시키기보다 로깅하는 것이 유리하다.</li>
</ul>
</li>
<li>dict에 존재하지 않는 attribute에 직접 접근하는 경우 에러가 발생한다. 그러나 이 경우 dict.get({attribute}, default_value)를 이용해 어트리뷰트가 존재하는지 체크할 수 있다.</li>
<li>try-except블록은 자주 남용하거나 go-to를 구현하기 위해서 사용해서는 안되며 scope가 작아야 코드를 이해하기 쉽다.<h3 id="관심사의-분리-캡슐화">관심사의 분리: 캡슐화</h3>
</li>
<li>객체는 잘 정의되어야 하고 가능하면 한가지 일만 수행할 정도로 작은 것이 좋다.</li>
<li>객체 사이의 의존성이 낮은 것이 좋다.<h3 id="개발-지침">개발 지침</h3>
</li>
<li>DRY: 같은 코드가 여러 곳에 존재하는 것은 비효율적이며, 수정 한번에 모든 코드를 수정해야 하므로 바람직하지 않다.</li>
<li>YAGNI: 과잉 엔지니어링을 하지 않도록 한다.</li>
<li>EAFB/LBYL: EAFP는 허락보다 용서를 구하기 쉽다는 뜻으로 우선 코드를 실행하고 실제 동작하지 않는 경우 대응하는 방법이다. pythonic한 방법은 EAFB가 유리하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Readable Code: 함수명, 변수명]]></title>
            <link>https://velog.io/@ben_jh/%ED%95%A8%EC%88%98-%EB%B3%80%EC%88%98-%EC%9E%91%EB%AA%85%EB%B2%95</link>
            <guid>https://velog.io/@ben_jh/%ED%95%A8%EC%88%98-%EB%B3%80%EC%88%98-%EC%9E%91%EB%AA%85%EB%B2%95</guid>
            <pubDate>Tue, 21 Jun 2022 09:27:31 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>좋은 코드란 코드를 읽는 다른 사람이 별다른 수고를 들이지 않고 이해할 수 있는 코드이다. 빠르고 리소스를 적게 먹는 코드를 좋은 코드라고 할 수도 있지만, 작성한 사람만이 알 수 있는 코드라면 그 가치를 빠르게 잃을 수 밖에 없다. 현대 시대의 프로그램은 굉장히 많은 코드로 구성되고, 한명이 모든 프로그램을 설계하는 것이 아닌 굉장히 많은 사람들이 오랜 시간 유지보수하며 발전하는 과정을 가질 수 밖에 없기 때문이다. 협업 환경에서는 코드를 작성하는 시간보다 <strong>다른 사람의 코드를 읽고 이해하는데 대부분의 시간이 소요</strong>된다.</p>
<p>Readable Code가 좋은 코드라는 것은 알겠으나, 어떻게하면 누구나 읽기 쉽게 할 수 있을까? 
그 시작은 <strong>함수명과 변수명</strong>을 <strong>직관적</strong>으로 이해할 수 있도록 역할, 목적, 형태에 대한 <strong>정보를 담는 것</strong>이다. 코드 작성자 입장에서는 함수명과 변수명이 그다지 직관적이지 않더라도, 코드의 구조나 각 함수와 변수의 역할을 이해하고 있으므로 큰 문제가 되지 않을 수 있다. 그러나 코드를 처음 읽는 사람은 함수와 변수를 이해하기 위해 코드의 구조, 맥락을 파악하는데 수고로움이 든다. </p>
<h2 id="방법">방법</h2>
<h3 id="동사--목적어">동사 + 목적어</h3>
<ul>
<li>문장의 어순에 맞게 배열하는 것이 읽을 때에도 자연스럽게 읽힌다.<ul>
<li><code>get_something()</code>이 <code>something_get()</code>보다 자연스럽다.</li>
</ul>
</li>
<li>목적어는 명확해야 한다.<ul>
<li><code>get_fragged_cells()</code>가 <code>get_something()</code>보다 명확하다.</li>
</ul>
</li>
<li>상황에 맞는 뉘앙스의 동사를 선택하자.<ul>
<li><code>get</code>, <code>fetch</code>, <code>load</code>등 무언가 들고온다는 뜻은 같지만 사용되는 상황이 다르다.</li>
<li>사람들이 일반적으로 인식하는 <code>적절한 동사</code>에 대한 고민이 필요하고, 오픈 소스 코드를 많이 읽어보는 것이 중요하다.</li>
</ul>
</li>
</ul>
<h3 id="이름의-길이">이름의 길이</h3>
<ul>
<li>의미가 분명한 경우 짧은 이름이 긴 이름보다 직관적이다.</li>
<li>이해할 수 있다면 이름이 길어도 상관이 없다.<ul>
<li>요즘은 대부분 IDE를 이용하고 있고, IDE의 자동 완성 기능이 긴 이름의 단점을 보완해준다.</li>
</ul>
</li>
</ul>
<h3 id="동적-타입-언어는-변수의-형태를-추가해주자">동적 타입 언어는 변수의 형태를 추가해주자</h3>
<ul>
<li>동적 타입 언어는 코드를 작성한 사람이 아니라면 변수의 타입을 바로 알기가 쉽지 않다.</li>
<li>가능한 자세한 type을 변수명에 넣어주자<ul>
<li><code>article_list</code>보다 <code>article_queryset</code>이 변수의 type을 이해하기 쉽다.</li>
</ul>
</li>
</ul>
<h3 id="필요하다면-부사를-추가하자">필요하다면 부사를 추가하자</h3>
<ul>
<li>리스트에서 중복요소를 찾는 함수의 이름을 짓는 경우를 예시로 들어보자<ul>
<li><code>get_duplicate_list()</code>는 동사 + 목적어 + type정보를 가지고 있지만, <code>리스트에서</code> 중복요소를 찾는다는 것과는 거리가 멀다.</li>
<li><code>get_duplicates_in_list()</code>는 함수의 이름이 길어졌지만 의미가 더 명확해진다.</li>
</ul>
</li>
</ul>
<h3 id="의미-없는-단어축약어-피하기">의미 없는 단어/축약어 피하기</h3>
<ul>
<li>변수의 scope가 좁더라도, 일반적으로 통용되는 단어라도 풀어쓰려고 노력하자<ul>
<li><code>for obj in [...]</code> 보다 <code>for pattern_matched in [...]</code> 처럼 풀어쓰자.</li>
<li><code>postfix_position</code>처럼 쓰는 것이 귀찮더라도 <code>postfix_pos</code>보다 의미가 명확하다.</li>
<li><code>tmp</code>, <code>ret</code>, <code>retval</code>, <code>qs</code>와 같은 변수명보다 맥락에 맞는 변수를 쓰자.</li>
</ul>
</li>
</ul>
<h3 id="함수의-모든-목적과-동작을-포함해보자-링크">함수의 모든 목적과 동작을 포함해보자 <a href="https://www.digdeeproots.com/articles/on/naming-process/">링크</a></h3>
<ul>
<li>함수의 책임을 파악하고, 함수를 쪼개는 신호가 될 수 있다.</li>
<li>과정<ul>
<li>함수의 동작을 파악</li>
<li>동작에 따라 함수를 쪼개기</li>
<li>함수가 동작하는 맥락을 이해하고 포괄적인 이름 붙이기</li>
</ul>
</li>
<li>예시<ul>
<li><code>parse_XML_and_add_Flight_to_DB_and_local_cache_and_add_to_screen_if_visible()</code> 처럼 함수의 이름에 목적과 동작을 모두 추가함으로써 너무 많은 책임을 가졌다는 사실을 직관적으로 알 수 있다.</li>
<li>이 함수는 <code>parse_XML()</code>, <code>save_flight_to_DB()</code>, <code>show_flight_on_screen_if_visible</code>로 쪼갤 수 있다.</li>
<li>쪼개진 함수를 관찰해보고 목적에 따라 통합, 분리하자. 함수의 목적은 결국 비행 경로를 표시하고 DB에 저장하는 것이다. 따라서, <code>parse_XML()</code>은 새로운 클래스의 메소드로 분리하거나 util 함수로 분리한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Model Meta]]></title>
            <link>https://velog.io/@ben_jh/Django-Model-Meta</link>
            <guid>https://velog.io/@ben_jh/Django-Model-Meta</guid>
            <pubDate>Tue, 21 Jun 2022 09:26:30 GMT</pubDate>
            <description><![CDATA[<h2 id="indexing">indexing</h2>
<ul>
<li><p>개요 </p>
<ul>
<li>Model의 related field, primary key에 대해서는 자동으로 index가 생성된다.</li>
<li>이외에 model을 쿼리할 때 자주 사용되는 field인 경우 index 생성을 고려해보는 것이 좋다</li>
</ul>
</li>
<li><h2 id="class-meta-내에-modelsindex로-index를-생성하고자-하는-field와-field-조합에-대해-설정할-수-있다-db에-설정될-index의-이름도-설정할-수-있다"><code>class Meta</code> 내에 <code>models.Index</code>로 index를 생성하고자 하는 field와 field 조합에 대해 설정할 수 있다. DB에 설정될 index의 이름도 설정할 수 있다.</h2>
<pre><code class="language-python">class Customer(models.Model):
  first_name = models.CharField(max_length=100)
  last_name = models.CharField(max_length=100)

  class Meta:
      indexes = [
          models.Index(fields=[&#39;last_name&#39;, &#39;first_name&#39;]),
          models.Index(fields=[&#39;first_name&#39;], name=&#39;first_name_idx&#39;),
      ]</code></pre>
<ul>
<li>multi column index는 index 설정 순서를 따라야 index가 적용된다. <ul>
<li>e.g. `Customer.objects.filter(Q(first_name=<del>)&amp;Q(last_name=</del>))은 index가 적용되지 않는다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="unique_together">unique_together</h2>
<ul>
<li>field 하나에 대한 유일성이 아닌 field의 조합에 대해 유일성이 필요한 경우</li>
<li>하나의 게시물에 사용자당 한 번의 좋아요를 누를 수 있는 경우</li>
</ul>
<h2 id="ordering">ordering</h2>
<ul>
<li><code>Model.objects.first()</code>나 <code>Model.objects.all()</code>등에 배열될 object의 순서를 설정</li>
<li>ascending order: <code>[&#39;{field_name}&#39;]</code></li>
<li>descending order: <code>[&#39;-{field_name}&#39;]</code></li>
</ul>
<h2 id="abstarct">abstarct</h2>
<ul>
<li>추상 모델 클래스를 선언하는 경우 True를 설정해줘야 한다.</li>
<li>Abstract Model을 상속받은 경우 자동으로 False로 설정된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Model Field]]></title>
            <link>https://velog.io/@ben_jh/Django-Model-Field</link>
            <guid>https://velog.io/@ben_jh/Django-Model-Field</guid>
            <pubDate>Tue, 21 Jun 2022 09:26:03 GMT</pubDate>
            <description><![CDATA[<h2 id="주로-사용하는-model-field">주로 사용하는 Model Field</h2>
<h3 id="string-type">string type</h3>
<ul>
<li>CharField<ul>
<li>string의 최대 길이 값을 알 수 있을 때 사용</li>
<li>indexing이 필요한 경우</li>
<li>성별, 나이 등 선택이 제한되어 있는 경우 Choices를 이용하는 것이 좋다</li>
<li><code>max_length</code>를 지정해야한다</li>
</ul>
</li>
<li>TextField<ul>
<li>string의 최대 길이 값을 예측할 수 없을 때 (게시물 내용 등)</li>
</ul>
</li>
<li>유의 사항<ul>
<li><code>null=True</code>인 경우 DB단에 None과 빈 문자열이 모두 허용된다.</li>
</ul>
</li>
</ul>
<h3 id="integer-type">integer type</h3>
<ul>
<li>IntegerField<ul>
<li>정수 값의 저장</li>
</ul>
</li>
<li>PositiveIntegerField<ul>
<li>정수를 저장하지만, DB단에서 자연수와 0만을 허용하고 싶을 때</li>
</ul>
</li>
</ul>
<h3 id="float-type">float type</h3>
<ul>
<li>FloatField<ul>
<li>실수값 저장</li>
</ul>
</li>
</ul>
<h3 id="datetime-type">date(time) type</h3>
<ul>
<li>DateField<ul>
<li>python의 <code>datetime.date</code>형태를 저장</li>
</ul>
</li>
<li>DateTimeField<ul>
<li><code>datetime.datetime</code> 형태를 저장</li>
</ul>
</li>
</ul>
<h3 id="primary-key-type">primary key type</h3>
<ul>
<li>AutoField<ul>
<li>자동으로 1씩 증가하는 정수값을 생성하고 저장</li>
</ul>
</li>
<li>UUIDField<ul>
<li>uuid값을 저장하기 위함</li>
<li>option으로 uuid생성 함수를 callback 함수로 포함</li>
</ul>
</li>
</ul>
<h3 id="slug-url-">slug, url, ...</h3>
<ul>
<li>SlugField<ul>
<li>CharField이지만 slug 형태에 대한 validation이 적용됨</li>
</ul>
</li>
<li>URLField<ul>
<li>CharField이지만 email 형태에 대한 validation이 적용됨</li>
</ul>
</li>
</ul>
<h3 id="related-type">related type</h3>
<ul>
<li>ForeignKey<ul>
<li>Model의 여러 instance가 다른 Model의 한 instance를 참조하는 N:1관계에서 N에 해당하는 모델이 사용</li>
</ul>
</li>
<li>OneToOne<ul>
<li>A Model의 instance와 B Model의 instance가 1 대 1로 대응되어야 할 때</li>
<li>e.g. User 모델과 Profile모델은 1 대 1 대응 관계인 경우가 많다</li>
</ul>
</li>
<li>ManyToMany<ul>
<li>다대다 관계일 때 사용</li>
<li>e.g. 학생과 수강하고 있는 강의 사이의 관계</li>
</ul>
</li>
<li><code>on_delete</code>옵션으로 참조하는 모델이 삭제되는 경우 모델의 삭제, 수정, null 처리 등을 결정할 수 있다.</li>
</ul>
<h2 id="주로-사용하는-model-field-option">주로 사용하는 Model Field Option</h2>
<h3 id="null">null</h3>
<ul>
<li>DB 필드자체에 Null값이 허용되는지 여부</li>
</ul>
<h3 id="blank">blank</h3>
<ul>
<li>form, drf 등에서 Null값에 대한 validation이 적용되는지 여부</li>
</ul>
<h3 id="unique">unique</h3>
<ul>
<li>DB 칼럼에 대해 유일성을 보장</li>
<li>두개 이상의 칼럼 조합에 대한 유일성은 Meta의 option 이용</li>
</ul>
<h3 id="default">default</h3>
<ul>
<li>값이 입력되지 않았을 때 사용할 기본값</li>
<li>e.g. 기본 프로필 이미지</li>
</ul>
<h3 id="verbose_name">verbose_name</h3>
<ul>
<li>field에 사용될 label값</li>
</ul>
<h3 id="auto_now_add">auto_now_add</h3>
<ul>
<li>Date(Time)Field에서 레코드 생성시 현재 시간으로 자동 저장</li>
</ul>
<h3 id="related_name">related_name</h3>
<ul>
<li>related field에서 역참조하고자 하는 모델에서 바라보는 이름</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB 설계 방법론 기초]]></title>
            <link>https://velog.io/@ben_jh/DB-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95%EB%A1%A0-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@ben_jh/DB-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95%EB%A1%A0-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Tue, 21 Jun 2022 09:25:01 GMT</pubDate>
            <description><![CDATA[<h2 id="목적에-따른-db-설계-요구사항">목적에 따른 DB 설계 요구사항</h2>
<h3 id="안정적이고-영속적인-데이터-저장">안정적이고 영속적인 데이터 저장</h3>
<ul>
<li>ACID특성을 가져야 한다<ul>
<li>Atomicity<ul>
<li>모든 작업이 반영되거나 모두 롤백되는 특성</li>
</ul>
</li>
<li>Consistency<ul>
<li>데이터가 미리 정의된 규칙에 따라 일관적으로 수정되고 저장되어야 한다</li>
</ul>
</li>
<li>Isolation<ul>
<li>서로 다른 작업이 서로에게 영향을 주어서는 안된다</li>
</ul>
</li>
<li>Durability<ul>
<li>성공적으로 수행된 작업은 영원히 반영되어야 한다</li>
</ul>
</li>
</ul>
</li>
<li>저장된 데이터를 신뢰할 수 있고 보존되어야 한다.</li>
<li>OLTP를 위한 설계이다</li>
<li>RDB가 적합하다</li>
<li>정규화된 데이터 테이블이 권장된다<h3 id="분석을-위한-데이터">분석을 위한 데이터</h3>
</li>
<li>BI를 위한 데이터 분석 및 AI를 위한 추출 데이터를 저장하기 위함</li>
<li>테이블이 아닌 분석하고자 하는 정보를 단위로 저장한다</li>
<li>OLAP를 위한 설계이다<h3 id="빠른-read성능을-위한-db">빠른 Read성능을 위한 DB</h3>
</li>
<li>웹 서비스의 대부분은 DB에 대한 read작업이고, 부하를 분산하기 위한 차원이다.</li>
<li>안정적이고 신뢰할 수 있는 DB가 존재하는 경우 CQRS패턴에서 쿼리만을 위한 DB라고 할 수 있다.</li>
<li>NoSQL을 사용해도 괜찮고, Read Replica를 이용하거나, In-memory DB인 Redis를 이용해 설계할 수 있다.</li>
<li>기존의 정규화된 DB를 read하기 위해 과도한 join이 DB의 성능을 저하시키는 경우, 반정규화를 고려할 수 있다.</li>
</ul>
<blockquote>
<p>정규화 예시</p>
</blockquote>
<ul>
<li>테이블의 칼럼이 원자값을 가지도록 테이블을 분해한다.<ul>
<li>{이름: 추신수, 취미: 영화, 음악} -&gt; <code>[{이름: 추신수, 취미: 영화}, {이름: 추신수, 취미: 음악}]</code></li>
</ul>
</li>
<li>칼럼들의 부분 집합이 다른 칼럼을 결정해서는 안된다.<ul>
<li>{학생, 강의실, 강좌명}이 아니라 {학생, 강좌명}, {강좌명, 강의실}로 분리한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Migration]]></title>
            <link>https://velog.io/@ben_jh/Django-Migration</link>
            <guid>https://velog.io/@ben_jh/Django-Migration</guid>
            <pubDate>Tue, 21 Jun 2022 09:24:14 GMT</pubDate>
            <description><![CDATA[<h2 id="django-migration의-목적">Django Migration의 목적</h2>
<ul>
<li>Django Model에 정의된 field, option, meta정보를 데이터베이스 테이블의 schema와 동기화하고, model의 변경사항을 버전으로 관리</li>
</ul>
<h2 id="django-migration-workflow">Django Migration Workflow</h2>
<ul>
<li><p><code>makemigrations</code></p>
<ul>
<li>모델과 해당 DB table의 schema를 동기화하기 위한 (변경내역) 데이터 생성</li>
<li>DB table의 schema 버전은 DB의 migration table이 가르키고 있는 migrations file 버전을 통해 알 수 있다.</li>
<li>새로운 마이그레이션 파일은 dependency가 있는 migration 파일, table에 적용되어야 할 수정사항을 포함한다.</li>
<li>postfix로 app_name을 추가하면 특정 app의 model에 대해서만 migration 파일을 생성할 수 있다.</li>
</ul>
</li>
<li><p><code>migrate</code></p>
<ul>
<li>생성한 migration파일을 통해 sql 명령을 생성하고, DB에 대해 명령을 수행한다.<ul>
<li>생성되는 sql 명령은 <code>sqlmigrate</code>를 통해 확인할 수 있다.</li>
</ul>
</li>
<li>migrate할 app_name, migration 파일을 지정할 수 있다.<ul>
<li>최신 버전의 migration뿐만 아니라, 이전의 migration 버전으로 rollback도 가능하다.</li>
</ul>
</li>
</ul>
</li>
<li><p><code>showmigrations</code></p>
<ul>
<li>각 app에 대해 migration 파일 적용 상황을 확인할 수 있다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP Message의 구조]]></title>
            <link>https://velog.io/@ben_jh/HTTP-Message%EC%9D%98-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@ben_jh/HTTP-Message%EC%9D%98-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 21 Jun 2022 09:23:35 GMT</pubDate>
            <description><![CDATA[<h2 id="http-개요">HTTP 개요</h2>
<ul>
<li>HTTP는 서버와 클라이언트가 통신하기 위한 프로토콜이다.</li>
<li>ASCII로 인코딩된 텍스트로 데이터를 교환한다.</li>
<li>교환하는 데이터를 <code>HTTP Message</code>라고 한다<ul>
<li>목적에 따라 요청은 <code>HTTP Request</code>, 응답은 <code>HTTP Response</code>로 나눌 수 있다.</li>
<li>구조에 따라 <code>start line</code>, <code>HTTP Header</code>와 <code>HTTP Body</code>로 나눌 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="start-line">Start line</h2>
<ul>
<li><code>{HTTP Method} {URL} {HTTP_Version}</code></li>
<li><code>{HTTP_Version} {status_code} {msg}</code><h3 id="http-method">HTTP Method</h3>
</li>
<li><code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>HEAD</code>등 행동을 정의한다.</li>
</ul>
<h3 id="url">URL</h3>
<ul>
<li>요청을 수행할 목적지의 리소스를 의미한다.</li>
<li>ASCII로 인코딩된 텍스트 데이터이므로 ASCII가 아닌 유니코드 같은 경우 escape처리를 하도록 설계가 되어있다.<ul>
<li>한글을 url에 입력하는 경우 <code>%E8%20%7...</code>등 이스케이프 처리가 된다.</li>
</ul>
</li>
<li>스킴<ul>
<li>사용할 프로토콜을 의미</li>
<li>http나 https를 사용한다</li>
</ul>
</li>
<li>Host<ul>
<li>리소스를 가지고 있는 컴퓨터의 위치, 도메인 이름</li>
</ul>
</li>
<li>Path<ul>
<li>host에서 제공하는 리소스의 경로</li>
</ul>
</li>
<li>Query String<ul>
<li><code>?id=8&amp;page=7</code>과 같은 형태로 주어진다</li>
<li>동적 리소스를 식별하는 값으로 자주 사용</li>
</ul>
</li>
<li>fragment<ul>
<li><code>#URL</code> 등 가르키고 있는 HTML요소를 보여주도록 함</li>
<li>현재 url에 <code>#URL</code>을 끝에 추가하면 현재 문단으로 스크롤링 된 상태가 됨<h3 id="http-version">HTTP version</h3>
</li>
</ul>
</li>
<li>연결을 원하는 HTTP version을 표현<h3 id="status-code">status code</h3>
</li>
<li>Response에서 Request에 대한 응답으로 성공 혹은 상태 변화 상태, 실패 상태 등을 전달</li>
</ul>
<h2 id="http-header">HTTP Header</h2>
<ul>
<li>Client나 서버에 대한 신상 정보라고 할 수 있다. (다른 나라를 여행하기 위한 여권, 비자라고 이해하는 것이 편하다.)</li>
<li>접속 프로그램 정보, 허용하는 컨텐츠 타입, 사용하는 언어, 허용하는 압축 알고리즘, 세션정보, HTTP Body의 컨텐츠 타입, length등을 저장한다.</li>
<li>보안, 인증, 주고받는 데이터 타입을 식별하는데 쓰인다.</li>
</ul>
<h2 id="http-body">HTTP Body</h2>
<ul>
<li>실제로 주고받는 데이터 값</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DRF Serializer 설정]]></title>
            <link>https://velog.io/@ben_jh/DRF-Serializer-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@ben_jh/DRF-Serializer-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 19 Jun 2022 23:50:08 GMT</pubDate>
            <description><![CDATA[<p> Serializer는 Client로부터 전달 받은 input을 parsing, normalize하고 검증하여 Django Model까지 전달하는 역할, 그리고 역순으로 Client까지 전달하는 역할도 담당한다. DRF에서는 Serializer를 쉽게 구성할 수 있도록 연관되는 Django Model의 필드를 mapping하여 Serializer를 구성할 수 있다. 대부분의 Django 프로젝트에서는 ModelSerializer나 ViewSet을 설정함으로써 쉽게 REST API를 구성할 수 있을 것이다. 그러나, 비즈니스 로직상 Serializer의 <code>field option</code> , <code>data type</code>, <code>related field 처리</code> 등이 Django Model과 달라질 수 있다. </p>
<h2 id="field-option">Field Option</h2>
<p>Serializer에서 설정할 수 있는 Field option은 다음과 같다. Django Model과 Field mapping이 되는 경우 추가로 기재해두었다.</p>
<ul>
<li><code>read_only</code><ul>
<li>Read에만 필요한 필드</li>
<li>Model field 옵션으로 <code>editabl</code> True로 설정 시 True</li>
</ul>
</li>
<li><code>write_only</code><ul>
<li>Update와 Create시에는 필요하지만, Model 표현에는 포함되지 않는 필드</li>
</ul>
</li>
<li><code>required</code><ul>
<li>Deserializing 과정에서 필요하지 않은 필드</li>
<li>Model field 옵션으로 <code>blank</code>, <code>null</code>, <code>default</code>설정시 False</li>
</ul>
</li>
<li><code>default</code><ul>
<li>blank or null 입력 시 대체되는 값</li>
<li>Model field 옵션으로 <code>default</code>지정 시에 동일한 default 값 혹은 callback으로 설정</li>
</ul>
</li>
<li><code>allow_null</code><ul>
<li>Null값 허용 여부</li>
<li>Model field 옵션으로 <code>null</code>설정 시 True</li>
</ul>
</li>
<li><code>source</code><ul>
<li>Model의 필드에는 존재하지 않지만, serializing과정에서 필요한 모델의 property 혹은 method</li>
</ul>
</li>
<li><code>validators</code><ul>
<li>validators 지정 시에 동일한 validator</li>
</ul>
</li>
<li><code>error_messages</code><ul>
<li>에러코드 딕셔너리</li>
</ul>
</li>
<li><code>label</code><ul>
<li>verbose_name지정 시에 설정 됨.</li>
</ul>
</li>
<li><code>help_text</code><ul>
<li>필드 설명</li>
</ul>
</li>
<li><code>initial</code><ul>
<li>default값이 아닌 입력으로 사용될 값 생성</li>
</ul>
</li>
<li><code>style</code><ul>
<li>serializing과정 중 필드를 rendering할 스타일</li>
</ul>
</li>
</ul>
<h2 id="related-field">Related Field</h2>
<p> DRF에서는 Django Model field중 연관된 model이 존재하는 경우 <code>PrimaryKeyRelatedField</code>로 매핑을 하고, related model의 pk를 (De)Serializing한다. 기본적으로 read-write에 모두 사용할 수 있다.</p>
<p>Related Field를 serializing하는 옵션</p>
<ul>
<li><code>PrimaryKeyRelatedField</code>: pk값</li>
<li><code>StringRelatedField</code>: 연관된 모델의 <code>__str__</code> 메소드</li>
<li><code>HyperlinkedRelatedField</code>: hyperlink</li>
<li><code>SlugRelatedField</code>: slug</li>
<li>연관된 모델의 serializer를 정의: 연관된 모델의 필드를 선택해서 직렬화가 필요한 경우</li>
</ul>
<p>유의해야할 점은 Serializer자체에는 연관된 모델을 조회할 때, django ORM의 <code>prefetch_related</code>나 <code>select_related</code>와 같이 쿼리를 최적화하는 기능은 없다는 것이다. 이 경우, Serializer에 queryset에 최적화된 ORM 형태로 주어야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Rest Framework 개요]]></title>
            <link>https://velog.io/@ben_jh/Django-Rest-Framework-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@ben_jh/Django-Rest-Framework-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Fri, 17 Jun 2022 09:10:24 GMT</pubDate>
            <description><![CDATA[<h1 id="drf의-등장배경">DRF의 등장배경</h1>
<h2 id="요약-django를-이용해-rest-api-server를-쉽게-만들어줌">요약: Django를 이용해 REST API Server를 쉽게 만들어줌</h2>
<h2 id="클라이언트의-접속-방법이-다양해짐">클라이언트의 접속 방법이 다양해짐</h2>
<ul>
<li>전통적으로 클라이언트는 웹 브라우저만을 이용해서 웹 서비스를 이용했다.<ul>
<li>백엔드는 html, js기반의 템플릿을 이용해 웹 서비스를 제공</li>
</ul>
</li>
<li>모바일 디바이스가 보급되면서, <code>모바일앱</code>을 이용한 웹 서비스 이용이 보편화되었다.<ul>
<li>모바일 앱마다 서로 다른 화면 구성을 위한 새로운 백엔드 서버가 필요해짐.</li>
</ul>
</li>
</ul>
<h2 id="csr의-등장">CSR의 등장</h2>
<ul>
<li>안드로이드, 아이폰, 아이패드, 웹브라우저 각각에 대응되는 뷰를 제공하는 것은 비효율적이다.</li>
<li>클라이언트 기기의 성능이 이전에 비해 좋아졌다.</li>
<li>그래서, 서버는 화면 구성을 위해 필요한 데이터만 제공하고 클라이언트 기기에서 화면을 구성하는 것이 트렌드가 됨.</li>
</ul>
<h2 id="rest-api">REST API</h2>
<ul>
<li>HTTP를 그대로 이용한 방법</li>
<li>클라이언트가 서버로부터 리소스를 받아오는 일종의 규약 및 방법론<ul>
<li>URL을 통해 자원을 명시</li>
<li>HTTP Method로 CRUD를 요청</li>
</ul>
</li>
<li>클라이언트는 서버가 제공하는 API Endpoint를 통해 쉽게 정보를 받을 수 있게 됨.</li>
</ul>
<h2 id="django와-rest-api">Django와 REST API</h2>
<ul>
<li>REST API는 XML, JSON과 같은 구조화된 텍스트 형식으로 데이터를 전송함</li>
<li>Model의 정보를 들고와서 구조화된 형태, 자료형에 맞게 <code>직렬화</code>를 할 필요성이 생김</li>
<li>Django 에서 (De)serializing을 지원하기는 하지만, REST API를 위해 추상화된 View, 웹 브라우저 API, 인증 기능등을 제공하지는 않았다.</li>
</ul>
<h1 id="drf의-기능">DRF의 기능</h1>
<h2 id="serializer">Serializer</h2>
<h3 id="serializing-model---dict">Serializing (Model -&gt; Dict)</h3>
<ul>
<li><p>직렬화하는 기능</p>
<ul>
<li>Serializer의 필드 정의 없이 모델의 필드를 참조해서 직렬화도 가능(<code>ModelSerializer</code>)</li>
</ul>
</li>
<li><p>필요한 필드만 선택 가능</p>
<ul>
<li><code>fields=[...]</code></li>
<li>write_only인 필드(e.g. password)는 제외하고 serializing을 할 수 있도록 함.</li>
<li>Serializer instance를 선언하고 data property에 접근하면 <code>to_representation</code>메서드가 실질적으로 직렬화해주는 기능을 한다.</li>
</ul>
</li>
</ul>
<ul>
<li><p>List에 대한 직렬화 지원</p>
<ul>
<li>다양한 Pagination 기능도 제공한다.</li>
</ul>
</li>
<li><p>Nested 형태로 직렬화 지원</p>
<ul>
<li>참조하는 다른 object의 serializer이용</li>
</ul>
</li>
<li><p>custom field 생성 가능</p>
<ul>
<li>Model의 <code>property</code>나 메소드를 이용</li>
</ul>
</li>
</ul>
<h3 id="deserializing-dict---model">Deserializing (Dict -&gt; Model)</h3>
<ul>
<li><p>Model object에 필요한 데이터만 뽑아낼 수 있음</p>
</li>
<li><p>입력 데이터를 검증(validate)할 수 있음</p>
<ul>
<li>Model field에 맞게, 사용자가 원하는 기준에 맞게 </li>
</ul>
</li>
<li><p>검증한 데이터를 이용해서 Model instance(s)에 대해 CRUD할 수 있음.</p>
</li>
</ul>
<h2 id="view">View</h2>
<h3 id="api-endpoint-핸들링">API Endpoint 핸들링</h3>
<ul>
<li>REST API를 위해 추상화된 View를 제공</li>
<li>View와 URL을 라우팅<h3 id="response-표준화">Response 표준화</h3>
</li>
<li>일관된 상태 코드와 상태 메시지를 제공할 수 있도록 함.</li>
</ul>
<h2 id="authentication--permission">Authentication / Permission</h2>
<ul>
<li>인증/권한 부여를 캡슐화<ul>
<li>URL/ Method마다 서로 다른 인증 권한 부여 로직을 적용할 수 있도록 함.</li>
</ul>
</li>
</ul>
<h2 id="testing">Testing</h2>
<ul>
<li>APIRequestFactory 제공</li>
<li>Authentication, CSRF mocking</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Middleware 역할]]></title>
            <link>https://velog.io/@ben_jh/Django-Middleware-%EC%97%AD%ED%95%A0</link>
            <guid>https://velog.io/@ben_jh/Django-Middleware-%EC%97%AD%ED%95%A0</guid>
            <pubDate>Fri, 17 Jun 2022 02:39:33 GMT</pubDate>
            <description><![CDATA[<h2 id="middleware-역할-http-헤더-정보-관리">Middleware 역할: HTTP 헤더 정보 관리</h2>
<h3 id="security-middleware">Security Middleware</h3>
<ul>
<li>Response에 HTTP Strict Transport Security(HSTS) 헤더 추가<ul>
<li>HTTPS 연결을 강제하기 위함</li>
</ul>
</li>
<li>Response에 Referer-Policy 헤더 추가<ul>
<li>client가 referer를 보내도록 하기 위함</li>
<li>CSRF middleware에서 referer가 필요</li>
</ul>
</li>
<li>Response에 Cross-Origin-Opener-Policy 추가</li>
<li>Response에 X-Content-Type-Option: nosniff 헤더 추가<ul>
<li>서버 공격 스크립트를 업로드하는 것을 방지</li>
</ul>
</li>
<li>SSL Redirect<ul>
<li>HTTP 연결에 301 상태 코드를 보내고 HTTPS로 redirect하도록 함</li>
</ul>
</li>
</ul>
<h3 id="session-middleware">Session Middleware</h3>
<ul>
<li>Session에서 Client에 대한 정보 관리<ul>
<li>Cookie, Session연결시간 등</li>
</ul>
</li>
</ul>
<h3 id="csrfviewmiddleware">CSRFViewMiddleware</h3>
<ul>
<li>CSRF 공격 방어</li>
<li>Response에 CSRFcookie를 통해 random secret 값 추가</li>
<li>Safe Method(GET, HEAD, OPTIONS, TRACE)가 아닌 Request에 CSRFCookie가 없거나 적절하지 않으면 403 error반환</li>
</ul>
<h3 id="xframeoptionsmiddleware">XFrameOptionsMiddleware</h3>
<ul>
<li>Clickjacking 공격 방어</li>
<li>Response에 <code>X-Frame-Options</code> 헤더 추가<ul>
<li>iframe 리소스가 sameorigin이어야 실행할 수 있도록 브라우저가 지원</li>
</ul>
</li>
</ul>
<h3 id="commonmiddleware">CommonMiddleware</h3>
<ul>
<li>user agents 필터링</li>
<li><code>APPEND_SLASH</code>, <code>PREPEND_WWW</code>세팅에 따라 URL path 재구성</li>
<li>Response에 Content-Length가 필요한 경우 추가</li>
</ul>
<h3 id="authenticationmiddleware">AuthenticationMiddleware</h3>
<ul>
<li>Request를 보낸 사용자가 인증된 사용자인지 확인하고 <code>user</code> attribute를 추가</li>
</ul>
<h3 id="gzipmiddleware">GzipMiddleware</h3>
<ul>
<li>HTTP 헤더에 인코딩 정보 추가 (<code>GzipMiddleware</code>)<ul>
<li>HTTP body의 Gzip 압축도 동시에</li>
</ul>
</li>
</ul>
<h2 id="middleware의-순서">Middleware의 순서</h2>
<ul>
<li>Middleware는 Setting에 나열된 middleware의 순서에 따라 request를 처리하고, 역순으로 response를 처리한다.</li>
<li>일반적인 순서는 다음과 같다<ul>
<li>SecurityMiddleware</li>
<li>GZipMiddleware</li>
<li>SessionMiddleware</li>
<li>CommonMiddleware</li>
<li>CSRFMiddleware</li>
<li>AuthenticationMiddleware</li>
</ul>
</li>
<li>필요한 Middleware가 있는 경우 middleware를 생성하고 적절한 위치에 추가한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Serializing 하는 이유]]></title>
            <link>https://velog.io/@ben_jh/Serializing-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@ben_jh/Serializing-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 16 Jun 2022 08:07:12 GMT</pubDate>
            <description><![CDATA[<h1 id="직렬화">직렬화</h1>
<ul>
<li>프로그래밍 언어 내의 object는 직렬화를 거친 형태로 다른 컴퓨터/네트워크와 통신한다.<ul>
<li>HTTP</li>
<li>DB</li>
<li>HTP ..<h2 id="object-개요">Object 개요</h2>
</li>
<li>Attribute를 가지는 구조화된 데이터이다. (class, struct, ...)</li>
<li>Object와 Attribute는 데이터 그 자체가 아니라 데이터를 저장하고 있는 메모리의 주소값이다.</li>
</ul>
</li>
</ul>
<h2 id="object를-네트워크로-전송할-때-문제점">Object를 네트워크로 전송할 때 문제점</h2>
<p>Object를 구성하는 값을 그대로 네트워크를 통해 전송한다면 문제점이 발생한다.</p>
<ul>
<li>데이터를 전달받은 컴퓨터는 전달받은 메모리 주소값을 통해 데이터에 접근하더라도, 메모리 주소값이 가르키는 데이터와 같을리가 없다.</li>
<li>데이터를 전달받더라도 양쪽 컴퓨터에서 약속한 데이터의 구조가 명시되어 있지 않으므로, 원하는 데이터가 무엇인지, 위치가 어딘지 알 수가 없다.</li>
</ul>
<h2 id="직렬화로-문제점-해결">직렬화로 문제점 해결</h2>
<p>이 때 필요한 것이 <code>직렬화</code>이다.</p>
<ul>
<li>Object의 메모리 주소 값을 메모리 주소가 가르키고 있는 데이터로 변환해준다. (<code>마샬링</code>)</li>
<li>약속한 구조화된 텍스트 형태로 변환한다. (<code>JSON</code> or <code>XML</code>)</li>
</ul>
<p>전달받은 JSON, XML파일은 <code>역직렬화</code>를 통해 상대방이 원하는 프로그래밍 언어의 객체로 변환된다. </p>
<h2 id="http-header-metadata로의-직렬화">HTTP header, metadata로의 직렬화</h2>
<ul>
<li>JSON, XML으로의 변환은 HTTP body에 필요한 bytestream형태로 바꾸는 과정에서 필요한 직렬화이다.</li>
<li>WebFramework에서 생성한 metadata, header값은 cgi 변수를 통해 웹서버에서 직렬화된다. (Python WebFramework는 WSGI 변수를 통해 HTTP 메시지(바이트 형태로 변환된다.)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hierarchical SQL in Django: Django-treebeard]]></title>
            <link>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Django-treebeard</link>
            <guid>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Django-treebeard</guid>
            <pubDate>Thu, 16 Jun 2022 02:21:40 GMT</pubDate>
            <description><![CDATA[<h2 id="django-treebeard-소개"><code>django-treebeard</code> 소개</h2>
<p>계층 구조를 표현하는 방법은 크게 네가지가 있다.</p>
<ul>
<li>Adjacency List without CTE</li>
<li>Adjacency List with CTE</li>
<li>Materialized Path</li>
<li>Nested Set</li>
</ul>
<p>앞선 Post에서 각각의 방법에 대한 Django Model, 부모/자식 노드 판별, SubTree를 구하는 예시에 대해 설명했다.
하지만, production에서 사용하기 위해서 상황마다 노드를 추가/삭제하는 방법에 대해 고민해야하고, <code>Tree Manager</code> class를 구현해서 사용하기 쉬운 메소드를 제공해야한다. 이는 쉬운 일이 아니고, 계층 구조를 프로젝트에 적용하기 어렵다.</p>
<p><code>django-treebeard</code>는 <code>Adjaceny List without CTE</code>, <code>Materialized Path</code>, <code>Nested Set</code>을 쉽게 사용할 수 있도록, 각각의 Node Model과 <code>NodeManager</code>, <code>TreeManager</code>를 제공한다. <code>wagtail</code>이나 <code>django-cms</code>같은 django기반 오픈 소스들에서 계층 구조를 표현하기 위한 라이브러리로 <code>django-treebeard</code>를 사용하고 있다.</p>
<p><a href="https://django-treebeard.readthedocs.io/en/latest/">django-treebeard</a></p>
<h2 id="적절한-hierarchical-model-선택">적절한 hierarchical model 선택</h2>
<ul>
<li><p>계층 구조를 설계하는 가장 기본은 <code>Adjaceny List</code>이다. 제공되는 모델 중에서 유일하게 정규화된 형태이고, 데이터의 정합성이 구조적으로 보장되기 때문이다. Depth 정보, Sibling Node의 순서, Traversing 성능이 중요한 경우 다른 모델을 고려해보는 것이 좋다.</p>
</li>
<li><p>Depth정보, Sibling Node의 순서, 간단한 쿼리 작성이 필요한 경우 <code>Materialized Path</code>모델을 선택하는 것이 좋다. 특히 Descendants Node를 조회하는 쿼리는 Leaf Node의 depth를 모르더라도, Self Join이 필요없는 <code>Sargable Query</code>라는 점이 특징적이다. 그러나 CHAR형태인<code>path</code>로 노드 사이의 관계를 정의하므로 Node의 갯수에 제한이 있다. 또한, Ascendants를 조회하는 쿼리는 <code>IN</code> operator를 요구하고 sargable하지는 않다.</p>
</li>
<li><p>변경이 자주 없고 Traversing 성능이 중요한 경우 <code>Nested Set</code> 모델이 유리하다. descendant, ascendant 모두 간단한 sargable query로 조회할 수 있고, 노드의 갯수에 제한이 없다는 것이 장점이다. 그러나, 노드의 추가 삭제가 발생하는 경우 전체 노드에 대해 조회하고 수정하는 경우가 생기므로 cost가 크다.</p>
</li>
</ul>
<h2 id="django-treebeard-제공-api"><code>django-treebeard</code> 제공 API</h2>
<ul>
<li><code>add_root(self: Node,**kwargs) -&gt; None</code></li>
<li><code>add_child(self: Node,**kwargs) -&gt; None</code></li>
<li><code>add_sibling(self: Node, pos: int=None, **kwargs) -&gt; None</code></li>
<li><code>move(target: Node, pos: int=None) -&gt; None</code><ul>
<li>subtree를 한번에 다른 노드 밑으로 이동시킬 때 사용</li>
</ul>
</li>
<li><code>get_tree(self: Node, parent: Node=None) -&gt; QuerySet[Node]</code><ul>
<li>subtree에 대해 DFS를 통해 QuerySet 반환</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hierarchical SQL in Django: Nested Set]]></title>
            <link>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Nested-Set</link>
            <guid>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Nested-Set</guid>
            <pubDate>Thu, 16 Jun 2022 01:56:11 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<ul>
<li>노드마다 숫자의 범위를 저장하고 숫자 범위가 다른 노드에 포함되는지 여부로 ascendant, descendant를 결정한다.</li>
<li>트리에 노드 추가/삭제시 모든 ascendant에 대해 값 수정이 필요하다.</li>
<li>하지만 트리에 대해 traversing하거나 조회하는 성능이 뛰어나다</li>
</ul>
<h2 id="django-model-example">Django Model Example</h2>
<pre><code class="language-python">class NSNode(models.Model):
    left_number = models.PostiveIntegerField(db_index=True)
    right_number = models.PositiveIntegerField(db_index=True)
    ...</code></pre>
<h2 id="orm-example">ORM Example</h2>
<pre><code class="language-python">def get_descendants(node: NSNode) -&gt; QuerySet:
    range_of_descendants = node.left + 1, node.right - 1
    descendant_queryset = NSNode.objects.filter(
        left__range = range_of_descendants
    )

    return descendant_queryset</code></pre>
<pre><code class="language-python">def get_ascendants(node: NSNode) -&gt; QuerySet:
    ascendant_queryset = NSNode.objects.filter(
        Q(left__lt=node.left) &amp;
        Q(right_gt=node.right)
    )

    return ascendant_queryset</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hierarchical SQL in Django: Materialized Path]]></title>
            <link>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Materialized-Path</link>
            <guid>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Materialized-Path</guid>
            <pubDate>Wed, 15 Jun 2022 09:38:07 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Materialized Path에서는 Foreign Key 없이 path 정보만으로 Tree구조에서 두 노드 사이의 관계를 알 수 있다.</li>
</ul>
<h2 id="materialized-path-원리">Materialized Path 원리</h2>
<ul>
<li>자식 노드의 path = 부모 노드의 path + <code>일정한 길이의 string</code><ul>
<li>path는 알파벳 대문자 + 숫자로 구성된다.<ul>
<li>대부분의 DB에서 정렬기준이 동일한 charset들이다.</li>
</ul>
</li>
<li>path_length_per_depth(steplen): depth가 하나씩 늘 때마다 path에 추가되는 string 크기</li>
</ul>
</li>
<li>부모 - 자식 노드 판별<ul>
<li><code>child_path[:-path_length_per_depth] == parent_path</code></li>
<li>자식 노드의 path는 부모 노드의 path를 포함한 형태로 저장하기 때문이다.</li>
</ul>
</li>
<li>Ascendants, Descendants 노드 판별<ul>
<li><code>descendants_path[:depth_difference * path_length_per_depth] == ascendants_path</code> </li>
<li>descendant이면 ascendant의 path를 항상 포함하고 있기 때문이다.</li>
</ul>
</li>
</ul>
<h2 id="materialized-path를-이용한-계층-구조-예시">Materialized Path를 이용한 계층 구조 예시</h2>
<ul>
<li>파일 디렉토리
<img src="https://velog.velcdn.com/images/ben_jh/post/f821aab5-0ced-4a37-b301-295a5389acde/image.png" alt=""><ul>
<li>e.g. 가장 하단의 <code>ee.jpg</code>의 file path<ul>
<li>path에 부모의 path를 prefix로 가진다</li>
<li><code>/opt/aaa/window/ee.jpg</code></li>
</ul>
</li>
<li><code>/opt/aaa</code> 에 해당하는 폴더, 파일들<ul>
<li>검색하고자 하는 폴더 path를 prefix로 가진 path를 모두 검색하면 된다.</li>
<li><code>/opt/aaa/aa.txt</code>, <code>/opt/aaa/linux</code>, <code>/opt/aaa/window/ddd</code>, <code>/opt/aaa/window/ee.jpg</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="django-model-example">Django Model Example</h2>
<pre><code class="language-python">class MPNode(models.Model):
    path_length_per_depth = 1
    alphabet = [ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]
    path = models.CharField(
        &quot;materialized_path&quot;,
        max_lenghth=256,
        editable=False
    )
    ...

    class Meta:
        indexes = [
            models.Index(
                fields=[&#39;path&#39;],
                name=&#39;materialized_path_index&#39;
            )
        ]</code></pre>
<h2 id="ormsql-example">ORM/SQL Example</h2>
<h3 id="descendants를-조회하는-orm">Descendants를 조회하는 ORM</h3>
<pre><code class="language-python">def get_descendants(node: MPNode) -&gt; QuerySet:
    descendants_query_set = MPNode.objects.filter(
        path__startswith=node.path
    )
    descendants_query_set = descendants_query_set.exclude(
        path=node.path
    )

    return descendants_query_set

# Descendants of node which is &#39;AAF&#39;
node_AAF = MPNode.objects.get(path=&#39;AAF&#39;)
descendants_of_node_AAF = get_descendants(node_AAF)
</code></pre>
<h3 id="ascendants를-조회하는-orm">Ascendants를 조회하는 ORM</h3>
<pre><code class="language-python">def get_ascendants(node: MPNode) -&gt; QuerySet:
    ascendants_path_list = [
        node.path[:depth * path_lenghth_per_depth] 
        for depth in range(1, len(node.path))
    ]
    ascendants_query_set = MPNode.objects.filter(
        path__in=ascendants_path_list
    )

    return ascendants_query_set</code></pre>
<h3 id="descendants를-조회하는-raw-query">Descendants를 조회하는 Raw Query</h3>
<pre><code class="language-sql">SELECT * FROM MPNode WHERE path LIKE &#39;AAF&#39; AND NOT PATH = &#39;AAF&#39;</code></pre>
<h2 id="참고">참고</h2>
<p>Joe Celko&#39;s Trees and Hierarchies in SQL for Smarties</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hierarchical SQL in Django: Adjacent List]]></title>
            <link>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Adjacent-List</link>
            <guid>https://velog.io/@ben_jh/Hierarchical-SQL-in-Django-Adjacent-List</guid>
            <pubDate>Wed, 15 Jun 2022 03:08:47 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>RDB에서 계층 구조를 나타내기에 가장 일반적이고 자연스러운 방법</p>
</li>
<li><p>Foreign Key로 자식 노드, 부모 노드를 가르킨다.</p>
</li>
<li><p>FK가 가르키고 있는 부모, 자식 노드를 찾기 위해 Recursive한 메소드가 필요하다.</p>
<ul>
<li>ORM 단계에서 Recursive하게 호출하거나</li>
<li>단일 쿼리에서 Self Join, CTE를 이용해 부모, 자식 노드를 재귀적으로 호출</li>
</ul>
</li>
<li><p>간단한 계층 구조에서 적합한 방법이지만 복잡한 계층 구조에서는 쿼리 작성 난이도 상승, Join으로 인한 DB 성능 저하가 발생할 수 있다.</p>
</li>
</ul>
<h2 id="django-model-example">Django Model Example</h2>
<pre><code class="language-python">class Node(models.Model):
        parent = models.ForeignKey(
                &quot;self&quot;,
                on_delete=models.CASCADE,
                related_name=&#39;child&#39;
        )

        ...</code></pre>
<h2 id="ormsql-example">ORM/SQL Example</h2>
<h3 id="descendants를-조회하는-orm">Descendants를 조회하는 ORM</h3>
<pre><code class="language-python">from itertools import chain
from typing import Iterable

def get_descendants(node: Node) -&gt; Iterable[Node]:
    queryset = Node.objects.filter(parent=node)
    results = chain(queryset)
    for child in queryset:
            results = chain(results, get_descendants(child))
    return results

descendants_of_node_7 = list(get_descendants(Node.objects.get(pk=7)))
# [4, 10, 6, 5, 11]</code></pre>
<h3 id="ascendants를-조회하는-orm">Ascendants를 조회하는 ORM</h3>
<pre><code class="language-python">def get_ascendants(node: Node) -&gt; Iterable[Node]:
    if node.parent:
        return chain([node.parent], get_ancestors(node.parent))

    return chain()

ascendants_of_node_11 = get_ascendants(Node.objects.get(pk=11))
# [6, 7, 2]
</code></pre>
<ul>
<li>간단한 계층 구조에서 descendant, ascendant 노드들을 쉽게 가져올 수 있다.</li>
<li>하지만 복잡한 구조에서는 비효율적인 쿼리(N+1 쿼리)로 인한 성능저하를 겪을 수 있다.<ul>
<li>조회하고자 하는 Sub Tree의 모든 Node에 대해 추가적으로 쿼리를 수행하기 때문이다.</li>
</ul>
</li>
</ul>
<h3 id="self-join을-이용한-raw-query">Self Join을 이용한 Raw Query</h3>
<pre><code class="language-sql"># descendants_of_node_7 = list(get_descendants(Node.objects.get(pk=7)))
SELECT t1.id, t2.id, t3.id, t4.id
FROM Node as t1
LEFT JOIN Node as t2 ON t2.parent_id=t1.id
LEFT JOIN Node as t3 ON t3.parent_id=t2.id
LEFT JOIN Node as t4 ON t4.parent_id=t3.id
WHERE t1.id = 7</code></pre>
<ul>
<li>테이블 Join을 이용해 추가 쿼리 없이 한번에 쿼리할 수 있다.</li>
<li>조회하고자 하는 sub tree의 depth가 얕고, depth가 정해진 경우 적절한 방법이다.</li>
</ul>
<h3 id="recursive-cte-참고">Recursive CTE <a href="https://www.postgresql.org/docs/9.6/queries-with.html">참고</a></h3>
<pre><code class="language-sql">WITH RECURSIVE descendants 
    (id, name, depth, parent_id)
AS (
    SELECT 
        id, 
        name, 
        0, 
        parent_id, 
    FROM Node
    WHERE parent_id is NULL

    UNION ALL

    SELECT
        N1.id,
        N1.name,
        T1.depth + 1,
        T1.id,
    FROM Node N1, descendants T1
    WHERE N1.parent_id = T1.id
)
SELECT * FROM descendants
WHERE depth &gt; 0
ORDER BY depth, parent_id;</code></pre>
<ul>
<li>sub tree의 depth 제한 없이 모든 child를 가져올 수 있다.</li>
<li><code>postgresql</code>의 경우 <code>WITH</code> clause를 이용한 recursive cte는 parent query로부터 fetch하며 쿼리를 반복한다.<ul>
<li><code>postgresql</code> docs에서는 production level에서 사용하지 않기를 권하고, 분석용으로 사용하기를 권한다. (DB 엔진마다 작동방식이 달라 결과가 달라질 수 있기 때문에)</li>
<li>query optimizer의 적용이 되지 않으므로 성능상 손해가 발생할 수 있다.</li>
</ul>
</li>
<li><a href="https://github.com/dimagi/django-cte">django-cte</a> 라이브러리를 이용해서 CTE query를 raw query가 아닌 python code로 관리할 수 있다는 장점이 있다.</li>
</ul>
<h2 id="참고">참고</h2>
<p><a href="https://brownbears.tistory.com/426">itertools.chain()을 이용한 쿼리셋 합치기</a></p>
<p><a href="https://towardsdatascience.com/recursive-sql-queries-with-postgresql-87e2a453f1b">Recursive SQL Queries with PostgreSQLRecursive SQL Queries with PostgreSQL</a></p>
<p><a href="https://learnsql.com/blog/do-it-in-sql-recursive-tree-traversal/">Do it in sql recursive tree traversal</a></p>
<p><a href="https://www.postgresql.org/docs/9.6/queries-with.html">postgresql docs - WITH</a></p>
<p>Joe Celko&#39;s Trees and Hierarchies in SQL for Smarties</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lambda에 웹서버 배포 시 문제점 및 해결과정]]></title>
            <link>https://velog.io/@ben_jh/Lambda%EC%97%90-%EC%9B%B9%EC%84%9C%EB%B2%84-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-%EB%AC%B8%EC%A0%9C%EC%A0%90-%EB%B0%8F-%ED%95%B4%EA%B2%B0%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@ben_jh/Lambda%EC%97%90-%EC%9B%B9%EC%84%9C%EB%B2%84-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-%EB%AC%B8%EC%A0%9C%EC%A0%90-%EB%B0%8F-%ED%95%B4%EA%B2%B0%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Tue, 14 Jun 2022 07:19:52 GMT</pubDate>
            <description><![CDATA[<p>서버리스로 웹서버를 배포하는 경우, 개발자 입장에서 인프라를 크게 관리하지 않아도 된다.  또한, 상시적으로 실행되어야 하는 서버가 없으므로 적게 사용할 경우 비용적인 장점이 있다. Lambda에 간단하게 웹서버를 배포할 때 참고할 수 있는 레퍼런스도 많고, 과정을 간소화해주는 <code>Zappa</code>와 같은 라이브러리도 있어서 구현하기도 쉽다.</p>
<p>그러나, 서버리스 배포가 기존의 EC2, ECS기반 웹서버 배포에 비해 장점만 가지고 있는 것은 아니다. 실제로 서비스를 하는 경우 겪을 수 있는 문제점들이 있는데, 이에 대해 소개하고 해결할 수 있는 방법을 다뤄보려고 한다.</p>
<h2 id="code의-업로드-크기-제한">code의 업로드 크기 제한</h2>
<ul>
<li>압축했을 때, code는 그 크기가 50MB보다 작아야 한다.</li>
<li>서비스를 위해서는 굉장히 다양한 라이브러리가 필요하고, 비즈니스 로직이 추가되면서 code 크기가 50MB보다 커질 수 있다.</li>
<li>해결 방법<ul>
<li>ECR을 통해 코드와 container를 함께 업로드 <ul>
<li>ECR 레포지토리에 필요한 docker container(10GB 이내)를 업로드한다. </li>
<li>ECR에 저장된 docker container로 lambda를 생성하도록 한다.</li>
<li><a href="https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/images-create.html#images-upload">관련 AWS 문서</a></li>
</ul>
</li>
<li>S3에 파일을 업로드하고 lambda에서 <code>\tmp</code>디렉토리를 이용해 다운 받는다.<ul>
<li>S3에서 다운받는 시간이 느리므로 coldstart timeout 시간이 길어질 수 있다.</li>
<li>zappa의 simple handler 기능으로 쉽게 적용할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="lambda의-coldstart-문제">Lambda의 coldstart 문제</h2>
<ul>
<li><p>coldstart 유형</p>
<p><img src="https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2021/04/12/performance1.png" alt=""></p>
<p>Lambda 입장에서는 runtime 구성시에 발생하는 cold start만 cold start라고 하지만, 웹서버 입장에서는 request를 처리하기 위해 웹서버가 메모리에 로딩되는 과정 까지도 cold start라고 해야한다.</p>
<ul>
<li><p>lambda runtime 구성에서 발생하는 cold start</p>
<ul>
<li>Python은 1ms이내의 시간만 걸린다.</li>
</ul>
</li>
<li><p>Code를 compile하는 과정에서 발생하는 cold start</p>
<ul>
<li>위의 그림에서 <code>Execute Initialization Code</code>에 해당하는 부분이다.</li>
<li>JAVA의 경우 이 과정이 오래 걸리지만, Python은 인터프리터 언어라 의미 없다</li>
</ul>
</li>
<li><p>Code가 메모리에 로딩되는 과정에서 발생하는 cold start</p>
<ul>
<li><code>Execute handler code</code> 부분에 해당한다.</li>
<li>Python의 경우 여기서 대부분의 coldstart time이 발생한다.</li>
</ul>
</li>
</ul>
</li>
<li><p>해결 방법</p>
<ul>
<li><p>runtime 구성 + 코드 컴파일 과정시에 생기는 coldstart</p>
<ul>
<li><p>provisioned concurreny + application auto scaling 기능을 이용하면 해결할 수 있다.</p>
<p><img src="https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2021/04/12/performance3.png" alt=""></p>
<p><img src="https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2021/04/12/performance4.png" alt=""></p>
</li>
</ul>
<ul>
<li>다만 비용이 비싸고 Python 기반 웹 프레임워크의 경우 메모리에 로딩되는데 시간이 걸려 생기는 coldstart는 해결하지 못한다.</li>
</ul>
</li>
<li><p>웹서버 로딩으로 인한 coldstart</p>
<p>lambda는 hadler를 수행한 후 바로 꺼지는 것이 아니라, 5분간은 유지된다.</p>
<ul>
<li><p><code>Zappa</code>에서 <code>keep_warm</code> 세팅을 통해 빈 request를 일정한 주기(5분 이내)로 보내준다</p>
<ul>
<li>서비스에 API 요청이 없더라도 적어도 1개의 lambda는 warm상태(웹서버가 로딩된 상태)임을 보장한다</li>
</ul>
</li>
<li><p><code>n</code>개의 동시성이 필요하다면 <code>n</code>개의 lambda를 4분에서 5분마다 호출한다.</p>
<ul>
<li><code>n</code>개를 순차적으로 호출한다면, 한 개의 lambda가 순차적으로 request를 처리하는 경우가 생긴다.</li>
</ul>
<p><img src="https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2021/04/12/performance6.png" alt=""></p>
<ul>
<li>원하는 concurrency만큼 비동기적으로 호출하고, 호출된 lambda가 response를 보내고 다른 request를 처리하지 않도록 <code>time.sleep()</code>메서드를 이용한다.</li>
<li><a href="https://pypy.dev/serverless/zappa-increase-unprovisioned-concurrency/#%EA%BC%BC%EC%88%98">참고 링크 및 관련 코드</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="rds-connection-leak">RDS Connection Leak</h2>
<ul>
<li>lambda가 여러개 띄워지고 모두 RDS에 접근하는 경우 connection에 leak 발생할 수 있다</li>
<li>그러나 lambda의  concurrent execution이 1000으로 제한되어 있고, 가장 낮은 인스턴스 사양의 RDS도 이정도는 감당가능하므로 딱히 고려하지 않아도 된다.</li>
</ul>
<h2 id="참고">참고</h2>
<p><a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/">https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/</a></p>
<p><a href="https://pypy.dev/serverless/zappa-increase-unprovisioned-concurrency/#%EA%BC%BC%EC%88%98">https://pypy.dev/serverless/zappa-increase-unprovisioned-concurrency/#%EA%BC%BC%EC%88%98</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zappa로 서버리스 배포]]></title>
            <link>https://velog.io/@ben_jh/Zappa%EB%A1%9C-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@ben_jh/Zappa%EB%A1%9C-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Tue, 14 Jun 2022 05:38:31 GMT</pubDate>
            <description><![CDATA[<h2 id="zappa-개요"><code>Zappa</code> 개요</h2>
<ul>
<li>Python Web Framework의 AWS Lambda + API Gateway 기반 서버리스 웹서버 배포 자동화</li>
</ul>
<h2 id="zappa-역할"><code>Zappa</code> 역할</h2>
<h3 id="배포의-자동화">배포의 자동화</h3>
<ul>
<li>명령어 한 줄로 lambda에 코드를 배포하기 위한 모든 과정을 진행한다.<ul>
<li>lambda 생성, 코드 압축 및 업로드, EventBridge, API Gateway 설정 등 필요한 AWS 리소스를 자동으로 생성하고 설정한다.</li>
</ul>
</li>
<li>기능 출시, 테스트 등 코드 수정이 잦은 상황에 매우 유용하다.</li>
</ul>
<h3 id="lambda-as-python-was">Lambda as Python WAS</h3>
<p>Lambda가 웹서버로 작동하기 위한 모든 과정을 포함하고 있다.</p>
<ul>
<li>API Gateway 연결<ul>
<li>API Gateway와 lambda의 연결이 마치 웹서버와의 연결인 것 처럼 설정(WSGI)</li>
<li>CORS, MIME Type등 HTTP 관련 설정 기능을 제공</li>
</ul>
</li>
<li>EventBridge 연결<ul>
<li>cronjob을 위한 lambda 호출</li>
<li>event 생성 시 lambda 호출</li>
</ul>
</li>
<li>DNS 관련 기능<ul>
<li>DNS 설정</li>
<li>SSL 설정</li>
</ul>
</li>
<li>그 외 웹서버 구성에 유용한 AWS 서비스들을 연결<ul>
<li>S3 (Static 파일 저장)</li>
<li>Integration(SNS, SQS)</li>
<li>KMS 설정</li>
</ul>
</li>
</ul>
<h3 id="관리-기능">관리 기능</h3>
<ul>
<li><p>Stage 기반으로 AWS 인프라를 개발환경으로 관리</p>
<ul>
<li><code>zappa deploy &lt;환경이름&gt;</code></li>
<li><code>zappa update &lt;환경이름&gt;</code></li>
</ul>
</li>
<li><p>django remote manage</p>
<ul>
<li><code>zappa manage &lt;환경이름&gt;</code></li>
</ul>
</li>
<li><p><code>tail</code></p>
<ul>
<li>오류 메시지 제공(from cloudwatch)</li>
</ul>
</li>
<li><p><code>status</code></p>
<ul>
<li>Lambda의 Metric 제공</li>
</ul>
</li>
</ul>
<h2 id="zappa-세팅"><code>Zappa</code> 세팅</h2>
<p>API Gateway + lambda + RDS 환경 가정</p>
<h3 id="기본-설정">기본 설정</h3>
<ul>
<li><p>AWS credential</p>
</li>
<li><p>Docker 설정</p>
<ul>
<li>컨테이너 이미지 선택 및 zappa 환경 빌드<ul>
<li>amazon linux 2 및 lambda 지원 도커 컨테이너를 선택한다</li>
<li>사용자의 cpu 아키텍처에 따라 오류가 발생할 수 있으므로 arm, x86 동시 지원 이미지를 선택하자.<ul>
<li>Python 3.8 (ARM &amp; x86) (<a href="https://hub.docker.com/r/mlupin/docker-lambda/tags">mlupin/docker-lambda:python3.8-build</a>)</li>
<li>Python 3.9 (ARM &amp; x86) (<a href="https://hub.docker.com/r/mlupin/docker-lambda/tags">mlupin/docker-lambda:python3.9-build</a>)</li>
</ul>
</li>
<li>dockerfile 구성<ul>
<li>가상환경 생성 및 구성</li>
<li>AWS credential 불러오기</li>
<li>..</li>
</ul>
</li>
<li>그런데 귀찮으니 위 세팅과정을 자동화한 <code>zappadock</code>을 이용한다<ul>
<li><a href="https://github.com/dickermoshe/zappadock">https://github.com/dickermoshe/zappadock</a></li>
</ul>
</li>
</ul>
</li>
<li>PIP 세팅<ul>
<li>프로젝트에 필요했던 패키지를 <code>pip freeze &gt; requirements.txt</code> 로 문서화한다.</li>
<li>도커상에서 <code>pip install -r &#39;requirements.txt&#39;</code> 로 설치한다.</li>
<li><code>psycopg2-binary</code><ul>
<li>Amazon Linux 2에는 psycopg2 필요 모듈인 <code>libpq</code> 가 없어서 바이너리로 설치해야된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>VPC 설정</p>
<ul>
<li>RDS가 필요한 경우 VPC의 AZ는 2개 이상으로 구성되어야 한다.</li>
<li>Private Subnet의 리소스가 인터넷 연결이 필요한 경우, 고정적인 EIP가 필요한 경우 NAT Gateway도 함께 생성한다.</li>
<li>S3 연결을 위해 S3 Gateway도 함께 생성해준다.</li>
<li>Management console의 creat vpc wizard 이용<ul>
<li>VPC, subnet, 라우트테이블, NATgw, igw, vpce 설정을 GUI로 가능하도록 지원</li>
</ul>
</li>
</ul>
</li>
<li><p>RDS 설정</p>
<ul>
<li><code>zappa</code>에서는 RDS를 설정할 수 없으므로 미리 RDS를 AWS에 생성해주자.</li>
</ul>
</li>
<li><p>기본 설정 자동화 :question:</p>
<ul>
<li>VPC, RDS와 같은 설정은 한번 설정해두면 프로젝트/stage에 따라 변경할 필요가 없이 계속 사용된다. 따라서 <code>IaC</code>로 자동화할 필요는 없는 것 같다.</li>
<li><code>Zappa</code>에서 소개하는 자동화 라이브러리는 있지만 업데이트가 되지 않는다.<ul>
<li><a href="https://github.com/dpetzold/terraform-aws-zappa">https://github.com/dpetzold/terraform-aws-zappa</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="zappa-세부-설정"><code>Zappa</code> 세부 설정</h3>
<ul>
<li><p>vpc_config</p>
<pre><code class="language-json">&quot;vpc_config&quot;: {    
        // 아키텍처상 lambda가 놓여야 할 subnet id
        // 여러 subnet에 배치할 수 있다.
    &quot;SubnetIds&quot;: [ &quot;subnet-12345678&quot; ],
        // 다른 리소스에 접근하기 위해 설정해둔 보안그룹
    &quot;SecurityGroupIds&quot;: [ &quot;sg-12345678&quot; ]
},</code></pre>
</li>
<li><p>events</p>
<pre><code class="language-JSON">&quot;events&quot;: [
{ 
        &quot;function&quot;: &quot;your_module.your_recurring_function&quot;, 
        &quot;expression&quot;: &quot;rate(1 minute)&quot; 
},
{
        &quot;function&quot;: &quot;your_module.your_reactive_function&quot;, 
        &quot;event_source&quot;: {
                &quot;arn&quot;:  &quot;arn:aws:s3:::my-bucket&quot;, 
                &quot;events&quot;: [
                        &quot;s3:ObjectCreated:*&quot; 
                ]
        }
}
],</code></pre>
<ul>
<li>사용 이유<ul>
<li>주기적인 작업</li>
<li>다른 리소스에서 이벤트가 발생한 경우 람다가 호출되기 위해서</li>
</ul>
</li>
</ul>
</li>
<li><p>keep_warm</p>
<pre><code class="language-JSON">&quot;events&quot;: [
{ 
        &quot;function&quot;: &quot;your_module.your_recurring_function&quot;, 
        &quot;expression&quot;: &quot;rate(1 minute)&quot; 
},
{
        &quot;function&quot;: &quot;your_module.your_reactive_function&quot;, 
        &quot;event_source&quot;: {
                &quot;arn&quot;:  &quot;arn:aws:s3:::my-bucket&quot;, 
                &quot;events&quot;: [
                        &quot;s3:ObjectCreated:*&quot; 
                ]
        }
}
],</code></pre>
<ul>
<li>lambda의 cold start 방지<ul>
<li>lambda가 갑자기 호출되면 provisioning을 하기 위해 지연시간이 발생한다.<ul>
<li>cold start + 잡다한 연결 + RDS 등등 지연시간이 1초가 넘어갈 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lambda as Web Server]]></title>
            <link>https://velog.io/@ben_jh/Lambda-as-Web-Server</link>
            <guid>https://velog.io/@ben_jh/Lambda-as-Web-Server</guid>
            <pubDate>Wed, 01 Jun 2022 12:21:02 GMT</pubDate>
            <description><![CDATA[<h1 id="lambda-개요"><code>Lambda</code> 개요</h1>
<h2 id="서버리스-컴퓨팅-서비스">서버리스 컴퓨팅 서비스</h2>
<ul>
<li><code>Lambda</code>는 AWS의 서버리스 함수 실행 서비스이다.<ul>
<li>Azure는 <code>Functions</code>, GCP의 <code>Google Cloud Functions</code>와 유사</li>
</ul>
</li>
<li>사용자가 서버를 프로비저닝하거나 관리할 필요가 없음</li>
<li>코드를 업로드하면 바로 실행할 수 있는 <code>Runtime</code> 환경을 제공</li>
<li><code>파일 처리</code>, <code>스트림 처리</code>, <code>간단한 모바일 백엔드</code>, <code>IOT 백엔드</code> 서비스 구성에 유용<h2 id="pay-as-you-go"><code>Pay as you go</code></h2>
</li>
<li>기존 <code>VM</code>, <code>Container</code> 형태의 컴퓨팅 서비스는 항상 가동시간을 기준으로 1초 단위로 요금을 계산</li>
<li><code>Runtime</code>이 제공되는 <code>Lambda</code>는 ms 단위로 함수 실행 시간을 요금으로 계산</li>
<li>웹 서비스의 경우, 대부분의 트래픽 및 컴퓨팅은 클라이언트가 서버에게 정보를 요청할 때<ul>
<li><code>VM</code>, <code>Container</code>와 달리 <code>runtime</code> 환경은 클라이언트에게 <code>동적 파일</code>을 서빙하는 경우에만 작동하도록 할 수 있고, Idle 상태를 피할 수 있음.</li>
</ul>
</li>
<li>Idle 상태를 최소한으로 유지한 채 ms 단위의 과금체계를 가질 수 있으므로 더욱 유연한 billing이 가능</li>
</ul>
<h2 id="스타트업에-유리">스타트업에 유리</h2>
<ul>
<li>서버 인프라를 관리할 인력이 따로 없음</li>
<li>서비스 출시/변경이 빠르게 반영되어야 함</li>
<li>트래픽이 적고 예측이 불가능해 유연한 billing 시스템이 필요함</li>
</ul>
<h1 id="lambda를-web-server로"><code>Lambda</code>를 Web Server로</h1>
<p>만약 <code>Lambda</code>로 <code>Web Server</code>를 구현하고 안정적인 서비스를 운영하고자 한다면 고려해야할 점이 한 두가지가 아님.</p>
<ul>
<li>기존의 웹서버 구조는 웹서버(Apache, Nginx) - WAS(Apach Tomcat + Web Framework) - DB와 같은 형태로 구성</li>
<li><code>Lambda</code>를 쓰기로 한 이상, 웹서버가 제공해주었던 기능을 코드나 다른 서비스를 이용해 구현해야 함.</li>
</ul>
<h2 id="web-server구성을-위해-고려해야할-점">Web Server구성을 위해 고려해야할 점</h2>
<ul>
<li>클라이언트와 <code>Lambda</code>와의 연결 방법</li>
<li>로드밸런싱의 구현</li>
<li>정적 파일의 서빙</li>
<li>SSL 인증</li>
<li>해커들의 공격에 대한 방어</li>
<li>BI툴 연결</li>
<li>스토리지와 RDB 연결</li>
<li>Cron Job의 구현</li>
</ul>
<h2 id="web-server-구성을-위해-필요한-서비스">Web Server 구성을 위해 필요한 서비스</h2>
<table>
<thead>
<tr>
<th>기능</th>
<th>대체 방법</th>
</tr>
</thead>
<tbody><tr>
<td>API 호출</td>
<td>AWS API Gateway 이용</td>
</tr>
<tr>
<td>정적파일 서빙</td>
<td>CDN 서비스<br />- AWS CloudFront와 API Gateway를 연결<br />- CDN 서비스를 이용해서 프레임워크에서 정적파일을 서빙하도록 구조를 변경 (e.g. <code>whitenoise</code>)</td>
</tr>
<tr>
<td>DDoS 방어</td>
<td>AWS Web Application Firewall<br />AWS CloudFront</td>
</tr>
<tr>
<td>로드밸런싱</td>
<td>AWS CloudFront + API Gateway</td>
</tr>
<tr>
<td>인터넷 연결</td>
<td>Internet Gateway</td>
</tr>
<tr>
<td>리소스 연결</td>
<td>Endpoint Gateway(for S3, DynamoDB)</td>
</tr>
<tr>
<td>Cron Job</td>
<td>AWS EventBridge</td>
</tr>
<tr>
<td>고정 IP</td>
<td>NAT Gateway + Elstic IP</td>
</tr>
<tr>
<td>SSL 인증</td>
<td>CloudFront나 API Gateway에 SSL 인증서 설치</td>
</tr>
</tbody></table>
<h2 id="구조">구조</h2>
<p><img src="https://velog.velcdn.com/images/combi_jihoon/post/f1029afd-6be5-4869-801d-68e9d57062d1/serverless%20django.png" alt="https://velog.velcdn.com/images/combi_jihoon/post/f1029afd-6be5-4869-801d-68e9d57062d1/serverless%20django.png"></p>
<ul>
<li>여기에 cron job을 위한 <code>EventBridge</code> 와 다른 서비스와 결합을 위한 <code>SNS</code> , <code>SQS</code> 까지 추가하면 서비스를 위한 구조</li>
<li>테스트 서버의 경우, <code>Route53</code> 과 <code>CloudFront</code> 는 꼭 필요하지는 않음</li>
</ul>
<h2 id="lambda의-network-세팅">Lambda의 Network 세팅</h2>
<h3 id="lambda의-위치">Lambda의 위치</h3>
<ul>
<li>Lambda 관리용 VPC</li>
<li>개인 VPC<ul>
<li>public subnet</li>
<li>private subnet</li>
</ul>
</li>
</ul>
<h3 id="lambda-outbound-고정-ip-할당">Lambda outbound 고정 IP 할당</h3>
<ul>
<li>사용 이유<ul>
<li>inbound traffic에 대해 ip 인증을 요구하는 리소스에 연결</li>
</ul>
</li>
<li>NAT Gateway를 연결하고 EIP를 부여</li>
</ul>
<h3 id="lambda에서-다른-리소스를-연결">Lambda에서 다른 리소스를 연결</h3>
<ul>
<li><p>VPC endpoints</p>
<ul>
<li>권장하는 방법</li>
<li>외부망을 타지 않고 곧장 AWS 내부망을 이용하므로 더 빠르고 비용도 절약</li>
</ul>
</li>
<li><p>Internet Gateway</p>
<ul>
<li>연결하는 리소스의 보안 규칙 요구사항에 따라 (IP로 인증을 하는 경우) 고정 IP를 요구할 수도 있음.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>