<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dj-yang.log</title>
        <link>https://velog.io/</link>
        <description>비전공자가 고통받으며 개발합니다</description>
        <lastBuildDate>Sat, 23 Mar 2024 12:46:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. dj-yang.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dj-yang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[GIL이란]]></title>
            <link>https://velog.io/@dj-yang/GIL%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@dj-yang/GIL%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Sat, 23 Mar 2024 12:46:36 GMT</pubDate>
            <description><![CDATA[<p>파이썬 설계적 결함.</p>
<ol>
<li>Global Interpreter Lock (GIL)</li>
</ol>
<ul>
<li><p>등장 배경 : 레퍼런스 카운팅(생성된 객체가 가르키는 참조의 수) -&gt; 이 참조 객체가 0이되면 메모리 가비지 컬렉터에 의해 해제된다. 이와 같은 방식을 파이썬에서는 사용하고 있는데, 두 개 이상의 쓰레드에서 같은 객체에 접근을 해 업데이트 등과 같은 작업을 진행할 때 레이스 컨디션이 일어날 수 있다.(메모리 누수가 발생하거나 참조하고 있는 객체가 존재했는데 해체를 하고자 한다거나. 이와 같은 메모리 이슈의 안전성을 보장하기 위한 디자인 선택</p>
</li>
<li><p>단점 : 스레드의 병목 현상이 일어나게 된다면 병렬 프로그램임에도 불구하고 싱글 스레드로 실행한 것보다 오랜 작업시간이 걸릴 수 있다. 이는 CPU bound한 프로그램에서 많이 발생한다. 오히려 I/O Bound한 프로그램에서는 이득이 될 수 있을 가능성이 높다.</p>
</li>
</ul>
<p>(2024-11-27 추가) I/O 작업 요청을 해서 다른 장치가 해당 작업을 처리하는 동안 Lock이 풀려 다른 스레드에서 인터프리터에 접근할 수 있게 변하는 것 같다.</p>
<ul>
<li><p>해결법</p>
<ol>
<li>멀티프로세싱(9장)</li>
<li>Numpy(6장)</li>
<li>Numexpr(6장)</li>
<li>사이썬(7장)</li>
</ol>
</li>
<li><p>현재 상황 : pep 703 (C 파이썬에서 GIL을 선택사항으로 두자)가 제안을 승인하는 방향으로 방향성을 잡고 있음. 그 방식은 아래와 같음</p>
<ol>
<li>불멸화 : None과 같은 객체는 할당을 해체할 필요 없으므로 참조 카운트 추적이 필요하지 않음.</li>
<li>편향 참조 가운팅 : 멀티 스레드에서 접근하는 객체의 카운팅과 싱글 스레드에서 접근하는 카운팅을 다르게 함.</li>
<li>스레드 안전 메모리 할당 : 가바지 수집기에서 조금 더 쉽게 객체를 추적하는 방식으로 진행.</li>
<li>지연된 참조 카운트 : 모듈의 최상위 함수와 같은 일부 객체에 대한 참조 카운트는 안전한 지연이 가능하다. -&gt; 아마 여러 곳에서 사용되는 만큼 해체될 일이 없기 때문에 그런듯 보임.</li>
<li>수정된 가비지 수집기 : C 파이썬 가비지 수집기는 둘 이상의 객체가 상호 참조를 갖는 순환 객체 참조를 정리한다. No-GIL 빌드에서는 객체 추적을 위한 “세대” 시스템이 제거되는 등 가비지 수집기의 많은 부분이 변경된다.</li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 맛보기 - Docker with local DB]]></title>
            <link>https://velog.io/@dj-yang/CICD-%EB%A7%9B%EB%B3%B4%EA%B8%B0-Docker-with-local-DB</link>
            <guid>https://velog.io/@dj-yang/CICD-%EB%A7%9B%EB%B3%B4%EA%B8%B0-Docker-with-local-DB</guid>
            <pubDate>Mon, 26 Feb 2024 08:59:07 GMT</pubDate>
            <description><![CDATA[<p>이번에는 로컬 서버를 도커로 동작 시킬 생각이다. 이와 같은 생각을 했을 때 걱정되는 점은 사실 하나였다.</p>
<p>내가 개인적으로 사용하는 MySQL 서버가 Local에서 돌고 있는데, 이 데이터 베이스에 연결을 해서 어플리케이션을 동작 시키고 싶었다.</p>
<hr>
<h3 id="docker">Docker</h3>
<p>도커가 무엇인지에 대해서 검색해보면 아래와 같은 내용을 확인할 수 있었다.</p>
<blockquote>
<p>도커는 리눅스의 응용 프로그램들을 프로세스 격리 기술들을 사용해 컨테이너로 실행하고 관리하는 오픈 소스 프로젝트이다. 도커 웹 페이지의 기능을 인용하면 다음과 같다: 도커 컨테이너는 일종의 소프트웨어를 소프트웨어의 실행에 필요한 모든 것을 포함하는 완전한 파일 시스템 안에 감싼다.</p>
</blockquote>
<p>위의 내용 중에 중요한 부분은 격리 기술과 컨테이너가 아닐까 싶다. 도커는 조금 더 멋있는 설명이 있겠지만 내가 봤을 때는 이미지를 가져와 컨테이너처럼 사용할 수 있다.</p>
<p>장점은 내가 기존에 동작하는 서버와 같은 환경의 새로운 컴퓨터를 동작시킬 때, 굳이 새로운 설정이 필요 없다는 점이다. 그냥 기존에 있던 이미지를 불러와서 구동만 시키면 된다.</p>
<p>또, 패키지들의 디펜던시 관리 또한 용의하다고 판단하고 있다. 기존의 가상환경이랑 같은 역할을 하는 것처럼 느껴졌다.</p>
<p>Docker에 대해서는 아주 잘 정리 되어있으니 별도로 따로 다루지는 않겠다.</p>
<hr>
<h3 id="fastapi">FastAPI</h3>
<p>우리 팀의 사이드 프로젝트에서 서버는 fastAPI를 공부 겸 사용하기로 했다. 해당 프레임워크에서 Docker에 대한 기본 세팅 명령어를 제공해주고 있어, 우선 가져와봤다.</p>
<pre><code class="language-Docker">출처 - https://fastapi.tiangolo.com/deployment/docker/#dockerfile

FROM python:3.9

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD [&quot;uvicorn&quot;, &quot;app.main:app&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;, &quot;--port&quot;, &quot;80&quot;]</code></pre>
<p>이미 어느 정도 기능을 구현한 프로젝트이기 때문에 역시 정상적으로 동작하지는 않았다.
따라서 인터넷을 검색해서 나에게 맞게 몇가지를 수정했다. 수정한 사항은 아래와 같다.</p>
<ul>
<li>Python 버전을 3.11로 올렸다.</li>
<li>사용하고 있는 패키지 관리 툴이 Poetry이기 때문에 Poetry를 명시적으로 인스톨 해줬고, 그것을 Docker에도 전달했다.</li>
<li>uvicorn을 Poetry에서 관리하고 있음에도 불구하고 실행이 안되길래 찾아보니 명시적으로 선언을 해줘야한다고 해서 uvicorn 패키지를 별도로 인스톨해줬다.</li>
<li>mysql을 제대로 사용하기 위해서 mysql client를 설치했다.</li>
</ul>
<pre><code class="language-Docker">FROM python:3.11

WORKDIR /code

# Install poetry
RUN pip install poetry

# Install MySQL client
RUN apt-get update &amp;&amp; apt-get install -y default-mysql-client

# Copy pyproject.toml and poetry.lock
COPY pyproject.toml poetry.lock /code/

# Install dependencies using poetry
RUN poetry config virtualenvs.create false \
    &amp;&amp; poetry install --no-root --no-dev

# Install uvicorn (if not included in pyproject.toml)
RUN pip install uvicorn

# Copy the rest of the application code
COPY . /code/

# Command to run the application
CMD [&quot;uvicorn&quot;, &quot;app.main:app&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;, &quot;--port&quot;, &quot;80&quot;]</code></pre>
<p>이렇게 Dockerfile을 구성해서 Docker run을 시켰지만 환경 변수 부분을 인식을 못해, 서버가 제대로 켜지지 않고 꺼지고 있었다.</p>
<p>로그를 확인해보니 환경변수가 동작하지 않아 문제가 발생하고 있어서 검색해보니 Docker Run Command를 줄 때 -e 옵션을 통해 환경 변수를 줄 수 있다고 해서 Command를 아래와 변경했다.</p>
<pre><code class="language-cmd">docker run -d --name mycontainer --network host \
-e MYSQL_USER={{MYSQL_USER}} \
-e MYSQL_PASSWORD={{MYSQL_PASSWORD}} \
-e MYSQL_HOST={{MYSQL_HOST}} \
-e MYSQL_DATABASE={{MYSQL_DATABASE}} \
-p 80:80 {{IMAGE_NAME}}</code></pre>
<p>하지만, 정상적으로 동작하지 않았고 계속해서 같은 오류가 발생했다. </p>
<p>원인을 파악하기 위해 환경 변수가 docker에게 넘어가는 것이 아닌지 아니면 Value는 제대로 넘어갔지만 어플리케이션단까지 넘어가지 않고 있는 건지 체크가 필요했다.</p>
<p>main.py의 코드를 가장 베이스 코드로 변경하고 docker를 구동시키고 같은 명령어로 docker container를 실행했다.</p>
<pre><code class="language-cmd">docker exec -it  {{CONTAINER ID}} /bin/bash</code></pre>
<p><img src="https://velog.velcdn.com/images/dj-yang/post/8272a0d5-6135-4b40-8a03-2e3ddd5b9392/image.png" alt=""></p>
<p>위와 같이 동작하면 이제 제대로 된 환경변수가 적용되어 있는 지 아래와 같은 방식으로 확인해보면 된다.</p>
<pre><code class="language-cmd">echo $MYSQL_USER</code></pre>
<p><img src="https://velog.velcdn.com/images/dj-yang/post/47a34820-65e7-4f65-9e46-49ad4ab0fc59/image.png" alt="">
내가 주입한 환경 변수가 제대로 나오는 것을 확인했지만 아직도 원인을 몰랐었다.</p>
<p>그러던 중 같이 문제를 해결해주던 지인이 --network option에서 의문을 제기했고, docker docs를 확인해본 결과...</p>
<blockquote>
<p>...
The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.
...
출처 - <a href="https://docs.docker.com/network/drivers/host/">https://docs.docker.com/network/drivers/host/</a></p>
</blockquote>
<p>linux 환경에서만 host 모드를 지원한다는 글을 발견하여, network option을 제거하고 실행했더니 원하던대로 local db와 연결된 docker container가 열렸다.</p>
<hr>
<h3 id="network-host-mode">network host mode</h3>
<p>이때 host 모드에 대해서 조금 궁금해서 찾아봤는데. 내가 이해한 바로는 docker container는 별도의 네트워크 환경에서 동작하기 때문에 접속할 수 있는 포트를 연결해야하는데(위의 command에서 80:80 부분이 해당 역할을 하고 있다.) network 옵션을 host로 설정할 별도의 네트워크 설정 없이 내 서버 == 컨테이너에서 운영하는 서버가 되어 도커 내에서도 localhost라는 주소로 내 서버에 접속할 수 있는 것이라 생각한다.</p>
<p>참고로 우리는 host 모드가 아니기 때문에 도커에서 내 로컬에 접속해야한다면 <em>host.docker.internal</em>를 호스트로 설정하면 된다...!</p>
<hr>
<p>다음은 도커 기반으로 자동 배포를 진행하는 방법을 진행해보려고 한다. 가능하다면 terraform이나 aws에서 제공하는 기능을 이용해서 환경변수 또한 안전하게 관리하는 것 또한 진행해볼까 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 맛보기 - Github Action]]></title>
            <link>https://velog.io/@dj-yang/CICD-%EB%A7%9B%EB%B3%B4%EA%B8%B0-Github-Action</link>
            <guid>https://velog.io/@dj-yang/CICD-%EB%A7%9B%EB%B3%B4%EA%B8%B0-Github-Action</guid>
            <pubDate>Fri, 23 Feb 2024 08:14:17 GMT</pubDate>
            <description><![CDATA[<p>요즘 내가 가고 싶은 회사들은 백엔드 개발자에게 구조에 대한 요구사항이 계속 있는 것 같다. API 공장이나 기획자로서의 롤을 주로 경험했던 전 회사를 다닌 나로서는 상당히 아쉽고 시간 있을 때 공부를 안한 것이 후회되기도 했다.</p>
<p>그래도 이미 지난 일인데 뭐.. 이제부터 열심히 해볼 생각이다. 그래서 사이드를 시작하기도 했다.</p>
<hr>
<p>CI/CD의 정의를 보면 아래와 같다.</p>
<blockquote>
<p>CI/CD는 지속적 통합(Continuous Integration) 및 지속적 제공/배포(Continuous Delivery/Deployment)를 의미하며, 소프트웨어 개발 라이프사이클을 간소화하고 가속화하는 것을 목표로 합니다.
지속적 통합(CI)은 코드 변경 사항을 공유 소스 코드 리포지토리에 자동으로 자주 통합하는 사례를 나타냅니다. 지속적 제공 및/또는 배포(CD)는 코드 변경 사항의 통합, 테스트, 제공을 나타내는 프로세스로, 두 가지 부분으로 구성됩니다. 지속적 제공에는 자동 프로덕션 배포 기능이 없는 반면, 지속적 배포는 업데이트를 프로덕션 환경에 자동으로 릴리스합니다.</p>
</blockquote>
<p>간단하게 말하면 예전에는 휴먼 리소스가 들어갔던 부분인 배포 과정과 테스트 과정을 자동화 시키는 것이라고 이해했다.</p>
<p><em>예전에 CI/CD 파이프 라인이라고 쓴 포스팅은 제대로 된 것이 아니였네..</em></p>
<p>아무것도 모르는 나로써는 우선 자동 배포화를 시도해볼 생각으로 인터넷 서칭을 시작했다.</p>
<h2 id="github-action">Github Action</h2>
<p>먼저, 가장 쉽게 접근할 수 있는 Github에서 제공해주는 Action에 관심이 갔다. 예전에 간단하게 사용을 해봤었고 내가 생각하는 규모에서는 이 정도면 충분하다고 생각했다.</p>
<p>github action에서는 원하는 이벤트 트리거(pr, merge) 등을 이용해 원하는 동작을 사전에 지시할 수 있었고, 내가 선택한 방식은 github이 직접 내가 구성한 서버에 ssh를 이용한 접속을 진행하여 변경된 코드 사항을 pull하고 웹 서버와 미들웨어를 재시작하는 방법이다.</p>
<p>github action은 내가 push하는 코드에 <strong>.github/workflows</strong> 디렉토리에 yml로 되는 스크립트를 작성하면 github 컴퓨터가 해당 스크립트를 그대로 실행시키는 방식을 사용했다.</p>
<p>구성했던 스크립트는 아래와 같다.</p>
<pre><code class="language-yml">name: dev branch auto ci process script

on: # 아래 job을 실행시킬 상황
  push:
    branches: [ develop ]

jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-20.04

    steps:
      - name: Get Github Actions IP
        id: ip
        uses: haythem/public-ip@v1.2

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Add Github Actions IP to Security group
        run: |
          aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

      - name: excuting remote ssh commands
        uses: appleboy/ssh-action@v0.1.6 # ssh 접속하는 오픈소스
        with:
          host: ${{ secrets.REMOTE_IP }} # 인스턴스 IP
          username: ${{ secrets.REMOTE_USER }} # 우분투 아이디
          key: ${{ secrets.REMOTE_PRIVATE_KEY }} # ec2 instance pem key
          port: ${{ secrets.REMOTE_SSH_PORT }} # 접속포트
          script: | # 실행할 스크립트
              {{app directory로 이동}}
            git pull origin develop
            poetry shell
            alembic upgrade head
            sudo service nginx restart
            sudo systemctl restart {{service file name}}

      - name: Remove Github Actions IP From Security Group
        run: |
          aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
</code></pre>
<p><strong>on</strong> : 이벤트를 트리거 시킬 상황을 뜻한다. 나는 push 이벤트가 발생할 때 스크립트를 실행시켜달라고 했는데, 그 중에서 develop branch에 push를 할 경우에만 실행할 수 있도록 선언했다.</p>
<p><strong>job</strong> : on에서 설정한 조건이 충족될 때 진행해야하는 작업을 뜻한다. step option을 통해 여러 개의 동작을 한번 지시할 수 있다.</p>
<p><strong>runs-on</strong> : 해당 명령어를 실행시킬 서버의 환경을 뜻한다. 나는 제일 익숙한 ubuntu로 선택했다.</p>
<p><strong>step</strong> : 작업의 절차, 실제로 절차대로 작성한 스크립트를 위에서부터 차례대로 실행한다.</p>
<p>작성한 스텝을 순서대로 설명하면 아래와 같다.</p>
<ol>
<li>github action의 컴퓨터 ip 확인 : 내 서버에 접속을 하기 위해서는 보안 그룹에 해당 컴퓨터가 접속할 수 있게 만들어줘야하고 적절한 컴퓨터인지 확인을 ip를 통해서 진행한다.</li>
<li>AWS 관련 권한 확보 : AWS에 설정을 변경하거나 그외 필요한 작업을 하기 위해 권한을 얻었다.</li>
<li>보안 그룹에 IP 추가 : 1번에서 확보한 IP를 내 서버 보안그룹에 추가했다.</li>
<li>AWS SERVER에 접속 : SSH를 통해 내가 원하는 서버에 접속을 했고, Script 옵션을 통해 명령어를 실행했다.</li>
<li>보안 그룹에서 IP 제거 : 필요한 작업을 진행한 후 IP를 제거해 보안의 안전성을 높혔다.</li>
</ol>
<hr>
<h2 id="한계점-및-피드백">한계점 및 피드백</h2>
<p>위와 같이 구성했지만 몇 가지 한계점과 바로 팀원이 피드백이 들어왔다.</p>
<ul>
<li><p>배포 주체 관련 피드백
<img src="https://velog.velcdn.com/images/dj-yang/post/728c7acd-c509-4dd5-8b44-002dbe4c77ac/image.png" alt="">개인적으로 이야기를 듣고 일리가 있는 말이라고 여겼다. AWS를 서버로 사용하기 때문에 AWS 자체에서 문제가 발생하는 부분에 대해서는 어쩔 수 없지만, 적어도 Github에서 문제가 있을 때 Dependency를 최소화하는 방향성이 맞다고 생각했고, 자동 배포 중에 Github Action으로 비중을 최소화 시키는 것이 올바른 방향성이라고 생각했다.</p>
</li>
<li><p>오류 대응
스크립트를 보면 디비 변경 또한 진행하고 있다. 하지만 만약, 디비가 변경 후에 코드에 문제가 생겼다면 DB 자체의 변경은 이루어졌지만 나머지 환경이나 코드가 최신화가 안되어 제대로 된 서비스가 제공되지 않을 수 있다고 판단했다. 깃헙 액션은 정말 시킨 것에 대해 수행만 할 뿐 문제가 발생했을 때 해결법에 대한 부분이 존재하지 않았다.</p>
</li>
</ul>
<p>지금 당장에 생각나는 방법으로는 1번 배포 주체를 AWS 서비스 로 변경한다. 팀원이 Code Deploy를 추천해줬고 찾아보니 적절한 서비스라고 판단했다. 배포 실행 자체는 Github Action 진행하더라도 배포 과정 자체는  Code Deploy의 프로세스를 따르는 방식으로 수정하는 것이 좋다고 판단했다.</p>
<p>오류에 대한 대응을 위해 version을 넣는 것이 좋겠다고 판단했고, versioning을 하려면 몇 가지 고민 사항이 있었다.</p>
<ol>
<li>DB 문제 : versioning을 효율적으로 하기 위해 서버 컴퓨터 자체를 새로 파는 형식을 사용하는 것으로 알고 있다. 이와 같은 방식으로 진행해야 서비스가 중간에 끊기는 현상 없이 사용자 입장에서 24시간 서비스가 구동되는 것으로 보이기 때문이다. 하지만, 우리는 돈의 절약을 위해 EC2 인스턴스에 직접 디비를 설치하여 사용하고 있었다. (좋지 못한 방법이라는 건 알지만..가난한 백수의 삶이다) 따라서 서버가 변경된다면 디비 내의 데이터도 같이 옮겨줘야 하는 것도 고려해줘야 했다.</li>
<li>환경 설정 : 새로운 서버가 생성될 때마다 환경세팅을 해주는 부담도 있었다. 단순하게 같은 서버를 만들면 된다면 상관없었지만 개발 환경에 따라 이마저도 요구사항이 달라질 수 있다고 판단했다. 그래서 docker와 같은 container를 사용하는 것이 맞다고 생각했다.</li>
</ol>
<p>위의 고려 사항을 생각해보고 우선 local에서 docker를 사용한 이미지를 생성하는 방식으로 변경을 해보는 포스팅을 진행해보려고 한다.</p>
<p>fin.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[퇴사한 백수의 회고]]></title>
            <link>https://velog.io/@dj-yang/%ED%87%B4%EC%82%AC%ED%95%9C-%EB%B0%B1%EC%88%98%EC%9D%98-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@dj-yang/%ED%87%B4%EC%82%AC%ED%95%9C-%EB%B0%B1%EC%88%98%EC%9D%98-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 23 Feb 2024 07:16:09 GMT</pubDate>
            <description><![CDATA[<h3 id="퇴사">퇴사</h3>
<p>작년 10월 퇴사를 했다...</p>
<p>매일 API 개발만 진행했고, 결과 없는 개발... 만들고 사용자가 없어 새로운 기능을 만들고, 요구사항이 들어왔지만 해결을 해도 피드백이 없었다. 사용자 중심의 서비스를 개발하며 회사가 성장하는 짜릿한 경험을 하고 싶었던 나에게 개인적으로 힘든 환경이라고 생각한다. 내가 원하는 이상은 이루기 어려운 것일까? 내가 더 적극적으로 트라이 했어야하지 않았을까?...</p>
<p>개발자 수만 몇 십배가 차이나는 회사의 서비스를 보며 똑같이만 만들면 되는데 왜 못하냐는 이야기도 들었다.</p>
<p>물론, 당시에는 짜증났지만 지금은 이해가 간다. 개발을 모르시는 분이고 그것을 설득하는 것도 내가 해야되는 일이라고 생각한다.</p>
<h3 id="수면">수면</h3>
<p>처음 퇴사하고는 진짜 잠만 잤다. 그렇게 잠만 잘 수 있냐 생각될 정도로 잠을 많이 잤다. 퇴사하고 며칠 간은 평소 일어나던 시간에 알람이 없어도 눈이 떠졌다. 일주일 정도 지나자 이제 푹 잘 수 있게 되었다.</p>
<h3 id="여행">여행</h3>
<p>회사를 다닐 때 여행을 가고 싶은 욕망이 강했어서 퇴사를 하고 나서 국내 3 지역, 해외 1지역을 여행다녀왔다. (퇴직금 개꿀 ㅎ)
부모님도 모시고 다녀왔는데, 힘들지만 재밌는 경험이었고 또 가고 싶다고 하신다. (죄송해요 이제 돈이 없어여...) 국내에도 이쁜 지역이 정말 많이 있다고 생각하는 좋은 경험이었다. 종종 가고 싶은데, 슬슬 돈을 아끼긴 해야할 것 같다. 꿈을 위해..</p>
<h3 id="사이드-프로젝트">사이드 프로젝트</h3>
<p>시간이 많은 백수이기에 여러 가지 프로젝트를 벌렸다. 예전과 다른 것은 내가 계속 열정적으로 하니 예전에는 시작할 때 혼자서 했던 프로젝트도 이제 주변에 사람이 점점 붙는다. 지금 개인적으로 프로젝트라고 할 만한 행동을 3가지 정도하는데, 한 가지를 빼고는 나머지는 5명 이상의 팀과 함께 진행 중이다. 내가 사람을 대할 때 하는 행동과 내가 보여줬던 모습이 잘못되지 않았다는 생각을 했다.</p>
<p>그와 더불어 책임감도 더 커지기도 했다. 사주를 보러 갔을 때 물어보기도 했는데, 잘 안된다고 해서 마음 속으로는 좀 서운했다.</p>
<p>프로젝트 하나는 시장 조사 중인데, 사람들의 반응이 생각보다 애매해서? 고민 중이다. 인증 단계가 있는데, 그 부분에서 허들을 좀 느끼는 것 같다. 하지만 서비스를 제공하는데 있어서 필수적인 부분이라고 생각해서 거부감을 줄일 수 있도록 고민을 지속하고 있는 상황이다.</p>
<p>또 하나의 프로젝트는 내가 해보고 싶었던 것을 도전하는 의미에서 진행한 것이고 사실 돈도 없어서 다들 무일푼으로 참여해달라고 했는데, 스스럼없이 참여해주는 모습에 감동했던 것 같다. 이 부분에 대해서도 내가 할 수 있는 한 최선을 다해서 유의미한 결과를 낼 수 있었으면 좋겠다고 생각한다.</p>
<h3 id="고민">고민</h3>
<p>요즘 정말 다양한 고민을 한다. 내가 제대로 된 길을 가고 있는 건지, 이게 내가 정말 하고 싶었던 일이었는지 또 만약 계속 가려고 한다고 해도 chatGPT와 같은 AI에게 금방 대체가 되지 않을까에 대해서 고민하고 있다.</p>
<p>사이드 프로젝트를 하는 것도 이러한 고민에 대해서 조금 확신을 얻고자 하는 의미에서 내 스스로 방어기재로 계속 여러 가지를 시도하는 것인지에 대한 고민도 있다.</p>
<p>보통 2~3년 차에 많은 번 아웃을 겪는다고 하는데, 그것을 버티고 회사를 지속적으로 다니는 사람이 있고 퇴사를 하는 사람이 있다고 한다.</p>
<p>내 개인적으로 판단했을 때 회사를 계속해서 다닌다면 내 커리어적인 부분이나 정신적이 부분에서도 성장을 크게 하기 어려울 것이라고 생각했기 때문에 퇴사에 대해서 후회는 하지 않지만 다음 스텝이 정해지지 않아서 그런 지 몰라도 고민이 끊이질 않는다...</p>
<p>내가 정말 하고 싶었던 일이 무엇이었는 지, 내가 추구하는 가치가 무엇인지.. 나는 아직 철이 없는데 사회에서 요구하는 것은 어른으로서의 내가 아닌지..</p>
<p>결과가 나지 않기 때문에 우선은 하나씩 하나씩 우선 앞에 있는 해야할 일부터 처리하는 것이 맞는 것 같다.</p>
<h3 id="독서">독서</h3>
<p>개인적으로 힘들었을 때 나에게 가장 위로가 되었던 것은 책이었다. 어렸을 때부터 &#39;남자는 약한소리 하면 안된다&#39;, &#39;남자는 울면 안된다&#39; 라는 이야기를 너무 많이 들으면서 커왔기 때문에 남한테 약한 소리하는 것이 아직도 어렵다. 그럴 때 책은 나에게 많은 경험과 위로를 줬다.</p>
<p>처음 읽기 시작했을 때가 22년 11월이고 한달에 1권씩만 읽자는 목표를 가지고 있었는데, 16개월 동안 약 35권 정도의 책을 읽은 것 같다. 많은 양은 아니지만 내가 소소하게 한 목표를 이루니 기분이 좋기도 하다.</p>
<p>그 안에서 많은 것을 배웠고, 실제로 그것을 토대로 대화했을 때 상대방이 나에게 큰 영감을 얻었다고 연락이 오기도 했다. 그 분이랑은 아직도 때때로 서로가 생각날 때 대화를 하는 편이다.</p>
<p>나 자신이 책 읽는 습관은 계속 가지고 있었으면 좋겠다.</p>
<hr>
<p>앞으로 내가 어떤 길을 걷게될 지에 대한 확신은 아직도 없다. 하지만, 지금 내 마음의 소리를 기울여 봤을 때는 내가 만든 서비스에 사람들이 들어와 선한 영향력을, 유의미한 일을 할 수 있는 장소를 제공해줄 수 있으면 좋겠다고 한다.</p>
<p>내가 가진 꿈은 이루기 어렵다고 생각한다. 하지만, 꿈은 원래 이루기 어려운 것을 고르는 게 아닌가 싶다. 한발자국씩 한발자국씩 잘 걷는 법을 배우면 언젠가는 뛰는 법을 깨닫게 되고, 또 그 후에는 자전거를 타거나, 비행기를 탈 수 있는 방법을 배우지 않을까 싶다.</p>
<p>그래서 꿈을 위해 계속해서 노력해보고 싶다.</p>
<p>fin.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rate limiting vs Throttling]]></title>
            <link>https://velog.io/@dj-yang/Rate-limiting-vs-Throttling</link>
            <guid>https://velog.io/@dj-yang/Rate-limiting-vs-Throttling</guid>
            <pubDate>Sat, 15 Jan 2022 14:18:05 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>팀 내에서 진행하는 토론에 ratelimit, throttling이라는 용어가 나왔지만.. 재밌게도 난 둘 다 제대로 알지는 못 하는 개념이기 때문에 조용히 있을 수 밖에 없었다.</p>
<p>어떤 개념인지를 파악하기 위해 검색을 진행하고 그 내용을 여기에 정리했다.</p>
<hr>
<h1 id="본론">본론</h1>
<h2 id="rate-limiting">Rate Limiting</h2>
<p>ratelimit에 대해서 인터넷 검색을 진행했을 때 아래와 같은 글들이 있었다.</p>
<blockquote>
<p>Rate Limiting is a client side response to the maximum capacity of a channel. If a channel has capacity to consume requests at a given rate/sec then a client should be prepared to limit their request rate</p>
</blockquote>
<p>위의 글 내용을 확인해보면 &#39;client side의 채널 이용에 제한을 두는 방식&#39;으로 귀결되는 것 같다. 즉 client가 request를 보낼 수 있는 횟수를 제한하는 방식인 것으로 이해했다. 이와 같은 방식을 사용하는 이유에 대해서 찾아봤는데</p>
<blockquote>
<p>In computer networks, rate limiting is used to control the rate of requests sent or received by a network interface controller. It can be used to prevent DoS attacks and limit web scraping.</p>
</blockquote>
<p>출처: <a href="https://en.wikipedia.org/wiki/Rate_limiting">https://en.wikipedia.org/wiki/Rate_limiting</a></p>
<p>즉 Dos나 web scraping을 방지하는 하기 위해서라고 한다.</p>
<p>정리하자면 Rate Limiting을 적용하는 목적을 대표적으로 뽑아보면 아래와 같은 것 같다.</p>
<ol>
<li>즉 Dos나 web scraping을 방비</li>
<li>서비스의 가용성(API레벨, 네트워크 레벨, 컨테이너 레벨, CPU 레벨 등) 유지</li>
</ol>
<p>Rate Limiting을 적용하기 위한 5가지 알고리즘이 있다.</p>
<h3 id="1-leaky-bucket">1. Leaky Bucket</h3>
<p>네트워크로의 데이터 주입 속도의 상한을 정해 제어하고 네트워크에서 트래픽 체증을 일정하게 유지한다. 일정한 유출 속도(유출 속도는 고정된 값)를 제한하여 버스트 유입 속도를 부드럽게 한다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/96dacaf1-e4d2-45ae-aff0-abe6611a6fe6/image.png" alt=""></p>
<ul>
<li>고정 용량의 버킷에 다양한 유량의 물이 들어오면 버킷에 담기고 그 담긴물은 일정량 비율로 떨어진다.</li>
<li>들어오는 물의 양이 많아 버킷의 용량을 초과하게 되면 그 물은 버린다.</li>
<li>입력 속도가 출력 속도보다 크면 버킷에서 누적이 발생하고 누적이 버킷 용량보다 큰 경우 오버플로가 발생하여 데이터 패킷 손실이 발생할 수 있다.</li>
</ul>
<h3 id="2-token-bucket">2. Token Bucket</h3>
<p>일시적으로 많은 트래픽이 와도 토큰이 있다면 처리가 가능하면서 토큰 손실 처리를 통해 평균 처리 속도를 제한할 수 있다. 즉, 평균 유입 속도를 제한하고 처리 패킷 손실없이 특정 수준의 버스트 요청 허용할 수 있다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/84f132b3-e15a-4c8f-9b15-cc5dfb8eae5a/image.png" alt=""></p>
<ul>
<li>토큰은 정해진 비율로 토큰 버킷에 넣는다.</li>
<li>버킷은 최대 n개의 토큰을 저장하며, 버킷이 가득차면 새로 추가된 토큰은 삭제되거나 거부된다.</li>
<li>요청이 들어오면 큐에 들어가며 요청을 처리하기 전에 토큰 버킷의 토큰을 획득해야 하며, 토큰을 보유한 후에 요청이 처리되며 처리된 후에는 토큰을 삭제한다.</li>
<li>토큰 버킷은 토큰이 배치되는 속도를 기반으로 액세스 속도를 제어한다.</li>
<li>전송 횟수를 누적할 수 있으며, 버킷이 가득차면 패킷 손실 없이 토큰이 손실된다.</li>
</ul>
<h3 id="3-fixed-window-counter">3. Fixed Window Counter</h3>
<p>정해진 시간 단위로 window가 만들어지고 요청 건수가 기록되어 해당 window의 요청 건수가 정해진 건수보다 크면 해당 요청은 처리가 거부된다. 이 알고리즘을 사용하면 경계의 시간대(12:59, 13:01초에 몰리면)에 요청이 오면 두배의 부하를 받게 된다. 즉, 구현은 쉽지만, 기간 경계의 트래픽 편향 문제가 발생된다.
<img src="https://images.velog.io/images/dj-yang/post/8c4afc6f-d5fe-4b31-ba7d-e1cfc8d299ad/image.png" alt=""></p>
<ul>
<li>버킷에는 정해진 시간 단위의 window(window 1번은 12:00 - 13:00, window 2번은 13:00 - 14:00)가 존재하고 시간 단위의 각 window는 요청이 오면 요청 건수가 기록된다.</li>
<li>시간당 정해진 요청 건수가 초과(여기서는 분당 4건이 상한)되는 9번의 요청은 거부된다.</li>
</ul>
<h3 id="4-sliding-window-log">4. Sliding Window Log</h3>
<p>Fixed window counter의 단점인 기간 경계의 편향에 대응하기 위한 알고리즘이다. 하지만, window의 요청건에 대한 로그를 관리해야하기 때문에 구현과 메모리 비용이 높은 문제점이 있다.
<img src="https://images.velog.io/images/dj-yang/post/641b89b3-b462-45d5-bc78-bc2c54d399a4/image.png" alt=""></p>
<ul>
<li>분당 2건의 요청을 처리한다면 12초와 24초에 온 요청은 허용이 되고 36초에 온 요청 분당 2건처리 원칙에 의해 거부되고</li>
<li>1분 25초의 요청이 들어오면 12초와 14초에 온 요청 로그를 pop해서 꺼내 없애고 남은 건 바로 전 요청인 36초에 온거 하나만 있어서 1분 25초에 들어온 요청은 처리가 된다.</li>
</ul>
<h3 id="5-sliding-window-counter">5. Sliding Window Counter</h3>
<p>Fixed window counter의 경계 문제와 Sliding window log의 로그 보관 비용 등의 문제점을 보완할 수 있는 알고리즘이다.
<img src="https://images.velog.io/images/dj-yang/post/1f7716e9-ce07-4cb6-a043-99b7ac59062f/image.png" alt=""></p>
<ul>
<li>분당 10건 처리한다면 1분안에 9건의 요청이 오고 1분과 2분 사이에는 5건이 요청온다고 가정.</li>
<li>1분 15초에 요청이 왔는데 1분 15초 지점은 1분과 2분 사이에서 25% 지점, 이전 기간은 1 - 0.25 = 75% 비율로 계산해서 9 * 0.75 + 5 = 14.75 &gt; 10으로 한도를 초과했기 때문에 요청은 거부된다. 즉, 이전 window와 현재 window의 비율값으로 계산된 합이 처리 건수를 초과하면 거부된다.</li>
<li>1분 30초 시점에 요청이 온다면 이전 기간은 50%, 9 * 0.5 + 5 = 9.5 &lt; 10이므로 해당 요청은 처리된다.</li>
</ul>
<blockquote>
<p>Sliding Window Counter는 window의 비율이 소수점이 나오게 되면 정확성이 떨어질 수는 있으나, Fixed window counter의 경계 문제와 Sliding window log의 로그 보관 비용 등의 문제점을 개선하게 된다.</p>
</blockquote>
<h3 id="rate-limiting-적용-시-고려-사항">Rate Limiting 적용 시 고려 사항</h3>
<ul>
<li>Rate Limit 알고리즘은 트래픽 패턴을 잘 분석한 다음 적합한 알고리즘을 선택해야 한다. 유료 서비스가 트래픽 체증에 민감해하지 않다면(관대한) Token Bucket 알고리즘을 선택하고 그 외에는 Fixed Wondow나 Sliding Window 알고리즘을 선택한다.</li>
<li>기본적으로 서비스 인프라 트래픽을 수용할 수 없는 시점에 도달했을 때 Rate Limit을 적용해야하며, 외부 서비스에 영향을 최소화하는 노력(Common한 API는 Rate Limit에 걸리지 않을 정도로 상한값을 높게 잡음 등)을 한 다음 Rate Limit을 적용하는게 좋다.</li>
<li>외부 개발자들에게 Rate Limit 정보를 명확하게 알려야하고, API 응답에도 요청 정보와 남은 정보 등 트래픽이 초과했을때 오류값 등을 명확히 정의해야 한다.</li>
</ul>
<h2 id="throttling">Throttling</h2>
<blockquote>
<p>PC, 노트북, 모바일 기기의 CPU, GPU 등이 지나치게 과열될때 기기의 손상을 막고자 클럭과 전압을 강제적으로 낮추거나 강제로 전원을 꺼서 발열을 줄이는 기능이다.</p>
</blockquote>
<p>출처: <a href="https://12bme.tistory.com/504">https://12bme.tistory.com/504</a> [길은 가면, 뒤에 있다.]</p>
<p>말 그대로 특정 하드웨어(우리에게는 서버)의 성능을 넘어서는 작업이 진행될 때 해당 부분을 다양한 방식으로 조정해 서비스 가용성을 유지하는 것을 뜻하는 것 같다.</p>
<h2 id="결론">결론</h2>
<p>나는 2가지에 대해서 이야기할 때 어떤 것이 우리 서비스에 맞을까 생각을 진행했는데 멍청한 고민이었던 것 같다. 그 이유는 아래 글을 확인하면 이해할 수 있다.</p>
<blockquote>
<p>“Throttling, also sometimes called Rate Limiting, is a technique that allows a service to control the consumption of resources used by an instance of an application, an individual tenant, or an entire service”</p>
</blockquote>
<p>출처 : <a href="https://beabetterdev.com/2020/12/12/what-is-api-throttling-and-rate-limiting/">https://beabetterdev.com/2020/12/12/what-is-api-throttling-and-rate-limiting/</a></p>
<p>때때로 Rate Limiting이라고 불리는 Throttling은..</p>
<p>결국 같은 것을 다르게 표현하는 것 같고 개인적으로 쓸데 없는 첨언을 하자면 Throttling이라는 개념이 조금 더 넓은 의미에서 사용하는 개념이 아닌가 싶다.</p>
<p>예를 들어 &quot;우리는 특정 부분에서 서버의 가용성을 떨어지는 것을 발견했고 Throttling이라는 개념을 적용할 필요가 있는데 그 부분에 적용할 방식으로는 Rate Limiting이 적절하다&quot; 라고 표현하면 가장 알맞은 표현인 것 같다.</p>
<p>마지막으로 위의 인용문을 가져온 페이지에서 눈에 띈 문장으로 끝내려고 한다.</p>
<h4 id="throttling-is-a-policy-that-the-server-enforces-and-the-client-respects"><strong><em>&quot;Throttling is a policy that the Server enforces and the Client respects.&quot;</em></strong></h4>
<h4 id="throttling은-서버에서-강제하고-클라이언트에서-준수하는-정책이다"><em>&quot;Throttling은 서버에서 강제하고 클라이언트에서 준수하는 정책이다.&quot;</em></h4>
<hr>
<p>Rate Limiting 파트에서 사용하는 이미지 및 텍스트는 <a href="https://www.progress.com/blogs/how-to-rate-limit-an-api-query-throttling-made-easy">https://www.progress.com/blogs/how-to-rate-limit-an-api-query-throttling-made-easy</a> 에서 가져왔습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[INTJ 성향 신입 개발자의 회사 적응기]]></title>
            <link>https://velog.io/@dj-yang/INTJ-%EC%84%B1%ED%96%A5-%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%9A%8C%EC%82%AC%EC%A0%81%EC%9D%91%EA%B8%B0</link>
            <guid>https://velog.io/@dj-yang/INTJ-%EC%84%B1%ED%96%A5-%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%9A%8C%EC%82%AC%EC%A0%81%EC%9D%91%EA%B8%B0</guid>
            <pubDate>Mon, 10 Jan 2022 06:05:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dj-yang/post/4526c13b-b104-4d33-993b-8a731a262e98/image.png" alt="">
나는 INTJ이다. 그에 대한 특징을 살펴보면 아래와 같다</p>
<ul>
<li>기본적으로 히키코모리</li>
<li>호불호가 확실하지만 불호의 기준이 높다.</li>
<li>혼자 밥먹는 것 좋아한다.</li>
<li>기타 등등등..</li>
</ul>
<blockquote>
<p>기타 여러 가지 특징이 있지만 간단하게 말해서 인간관계에 용의한 타입은 아닌 것 같다. MBTI특징을 설명하는 글을 보다가 &#39;이런 사람이 사회생활이 가능한가?&#39;라는 생각이 들면 대부분 INTJ를 설명하는 글이었다.</p>
</blockquote>
<p>이런 내가 처음 도전한 창업이라는 길에서 정말 많은 것을 배웠고 즐거웠다. 하지만 그와 반대로 나에 대한 한계점을 확실하게 느꼈던 것 같다. 나는 더 많은 것을 배우고 싶었고 스타트업에 들어가야겠다고 생각했다. 내가 회사를 선택할 때는 몇 가지 기준이 있었다.</p>
<ul>
<li>기술적인 리드가 가능한 CTO의 존재</li>
<li>개발팀의 존재(최소 3명 이상)</li>
<li>SI가 아닌 자사의 서비스가 존재</li>
<li>일정 규모 이상의 투자를 받은 회사(성장하고 있는 회사)</li>
</ul>
<p>이런 조건과 일치하는 회사들의 문을 두드렸고 가장 먼저 대답했던 회사가 지금 다니고 있는 회사였다. 참고로 우리 회사의 인턴기간은 3달이고, 그 후 재계약 여부가 결정된다.</p>
<h3 id="한-달-차-적응기">한 달 차-적응기</h3>
<p>처음 입사를 하고 나서의 내 모습을 상상하면 이 이미지가 떠오른다.</p>
<table>
<thead>
<tr>
<th><img src="https://images.velog.io/images/dj-yang/post/fe9fd290-7209-49b7-8f42-b13f011380ac/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>인싸들의 관심에 고통받는 아싸</td>
</tr>
</tbody></table>
<p>다들 관심을 가져주시고 말도 많이 걸어주셨지만 나 같은 아싸는 관심이 부담스러웠다…</p>
<p>회사 내에 있는 규칙들(PR, commit 등)도 하나씩 배워갔고 대망의 첫 PR을 올리게 되었다!</p>
<table>
<thead>
<tr>
<th><img src="https://images.velog.io/images/dj-yang/post/aefe79e4-ceaa-4018-b996-cb8f9eb14549/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>첫 PR에 달린 Comment</td>
</tr>
</tbody></table>
<p>PR을 올리고 뿌듯한 마음으로 화장실을 다녀왔는데 Slack Bot이 신나게 알림을 보내주고 있었다. 내가 올린 PR에 달린 Comment 들에 대한 알림이었다.</p>
<p>&#39;누군가가 내가 올린 코드를 진지하게 봐주고 있구나&#39;라는 기쁨 vs &#39;내가 코딩을 정말 못 하는 구나&#39;라는 자괴감 간의 싸움이 내면에서 일어났었다..</p>
<table>
<thead>
<tr>
<th><img src="https://images.velog.io/images/dj-yang/post/7b72f6fc-25c1-4e7e-947d-c94fdf4ea508/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>저도 가요?….</td>
</tr>
</tbody></table>
<p>그렇게 무한한 관심 속에 올 것이 오고 말았다… 워크숍이 예정되어있었고 비행기를 타고 미국 본사로 가야 하는 상황이 발생했다. (신입은 가고 싶은 사람만 가도 되는 행사였는데.. 신입이니 눈치 보다가 그냥 갔다..)</p>
<p>워크숍은 비행기 멀미 + 아싸의 힘듬이 있었지만 개발팀의 도움으로 무사히 귀국했다..</p>
<p>그 후에는 코드 리딩과 배정받은 업무 처리를 하느라 시간이 빠르게 지나갔던 것 같다.</p>
<p>첫 번째 달 PR 수 : 8개</p>
<h3 id="두-달-차-적응기">두 달 차-적응기</h3>
<p>첫 달에는 코딩할 때 이미 가이드라인이 지정되어있고 나는 그 방향에 맞춰 추가 및 삭제하는 업무만 배정되었다면 두 달 차부터는 내가 코드 가이드라인을 지정하는 역할이 되었다.</p>
<p>다른 사람이 적은 로직을 보고 이해하는 것도 힘들다고 생각했지만 다른 사람들이 앞으로 내가 정한 가이드라인을 따라오게 되는 로직을 만드는 것이 더욱더 어렵다는 것을 깨달았다.</p>
<p>관련 논문(필요한 부분만..)도 찾아보고 인터넷에서 다른 사람이 작성한 코드도 찾아보기도 했다. 또 개발팀은 CTO님과 매주 1 on 1을 진행했는데 그때마다 투정 부리면서 많이 귀찮게 한 것 같다.</p>
<table>
<thead>
<tr>
<th><img src="https://images.velog.io/images/dj-yang/post/785f5058-6c8c-413f-88ee-8fdf0e0d9ea9/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>내가 보는 CTO님</td>
</tr>
</tbody></table>
<p>다행히 싫은 내색 없이 도움이 필요한 부분에 대해서 지속해서 케어를 해주셨고 아무것도 모르는 신입 개발자가 두 달 차를 버틸 수 있었던 원동력이었던 것 같다. (물론 동료 개발자분들도 많은 도움과 조언을 주셨다.)</p>
<p>지금 생각해보면 두 달 차는 정말 여러 모로 스트레스 속에서 보냈던 것 같다.</p>
<p>두 번째 달 PR 수 : 14개</p>
<h3 id="세-달-차-적응기">세 달 차-적응기</h3>
<p>두 달 차에 시작한 업무를 계속 진행했다. 내가 진행하는 부분에 대해서 문서를 작성하고 개발팀과 공유했지만 많은 부분에서 부족함을 보였던 것 같다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/f49950ef-b138-4793-8fa9-539f3394f737/image.png" alt=""></p>
<p>두 달 차에 진행하던 것이 크게 봤을 때 총 2개였고 하나는 방향성이 잡히고 개발도 일정 수준까지 진행했지만, 나머지 하나는 그때는 계속 제자리걸음인 것처럼 느껴졌다. 제자리걸음인 업무가 내가 하고 싶다고 할 수 있다고 해서 진행한 업무이기 때문에 부담감이 컸던 것 같다. 하고 싶다고 주장했던 것을 후회했다.</p>
<p>조급한 마음이 변화된 것은 우연히 나에게 생각을 할 수 있는 시간이 생겼을 때였다. 일을 하지 않고 생각을 하며 나에 대한 객관화(?)를 한 번 더 진행했던 것 같고 현재 내 한계를 확인할 수 있었다.</p>
<p>한계를 확인하자 현재의 내 능력에서 최선을 다하자로 마음가짐이 변했다. 완벽하게 하는 것은 욕심이고 현재의 내가 할 수 있는 최선을 다하고 그 다음 단계는 미래의 나에게 맡기기로 했다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/258441eb-845b-4405-8251-94366a150783/image.png" alt=""></p>
<p>거짓말처럼 마음이 편해졌고 속도도 붙었다.</p>
<p>분명히 내가 개발한 것이 완벽한 코드가 아닐지 모른다. 하지만 그 안에서 분명히 유의미한 발전이 있었고 지속해서 발전할 수 있다는 자신감을 얻게 된 것 같다. (단점은 그와 비례해서 다른 욕심도 커졌다..)</p>
<p>세 번째 달 PR 수 : 12개</p>
<hr>
<p>인턴 기간 동안 개인적으로 잘했던 점과 못했던 점을 나눠보자면</p>
<p>잘 했던 점</p>
<ul>
<li>내가 하고 싶은 것을 말했던 것</li>
<li>업무에 있어서 포기하지 않았던 것</li>
<li>주인의식을 가지고 서비스 개발 및 피드백에 대응했던 것</li>
</ul>
<p>못 했던 점</p>
<ul>
<li>개발팀 외 직원분들과 더 친해질 수 있었던 것 같은데 못 했던 점</li>
<li>내가 하고 싶은 것과 할 수 있는 것을 구분하지 못 했던 점</li>
</ul>
<hr>
<p>나는 아직도 <strong>적응 중</strong>이고 발전을 위해 발버둥 치는 중이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(3) - elastic beanstalk]]></title>
            <link>https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-elastic-beanstalk</link>
            <guid>https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-elastic-beanstalk</guid>
            <pubDate>Wed, 28 Jul 2021 08:37:34 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>가장 기초적인 github-actions를 이용한 CI/CD 파이프 라인 구축하기 마지막 시리즈이다.
다른 시리즈를 아직 못 봤다면 아래를 확인!</p>
<p>시리즈
<a href="https://velog.io/@dj-yang/Django-Gitgub-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-AWS-SETTING">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(1) - AWS SETTING</a>
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-github-actions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(2) - github actions 사용하기</a></p>
<hr>
<h1 id="본론">본론</h1>
<h2 id="ebextensions">.ebextensions</h2>
<p>AWS Elastic Beanstalk는 .ebextensions에서 setting 값을 받아온다.</p>
<p>이전에 생겼던 오류는 해당 설정을 해주지 않았기 때문에 발생한 오류이다.
.github 폴더를 만든 위치에 <strong>.ebextensions</strong> 폴더를 생성한다.</p>
<pre><code class="language-config"># ../cicdproject/.ebextensions/django.config

option_settings:
  aws:elasticbeanstalk:container:python:
    WSGIPath: cicdproject.wsgi:application
  aws:elasticbeanstalk:application:environment:
    DJANGO_SETTINGS_MODULE: cicdproject.settings
    &quot;PYTHONPATH&quot;: &quot;/var/app/current:$PYTHONPATH&quot;
  aws:elasticbeanstalk:environment:proxy:staticfiles:
    /static: static</code></pre>
<p>AWS Elastic Beanstalk에서 Django앱을 사용하려면 NGINX와 WSGI를 이용하기 때문에 위와 같은 세팅을 통해 wsgi 설정 파일을 작성해줬다.</p>
<p>이 상태로 커밋 후 푸시를 진행한다.</p>
<h2 id="allowed_host">ALLOWED_HOST</h2>
<p>다시 <strong>환경으로 이동</strong> 버튼을 클릭하면 allowed_host 오류가 나타난다.
이는 django app에 elastic beanstalk가 접근을 하려고 했지만 이를 거절했기 때문에 발생한 오류다.</p>
<p>아마 다른 방식으로 배포를 진행하려고 했다면 자주 봤을 오류기 때문에 큰 설명을 안하고 바로 진행하겠다.</p>
<p><strong>환경으로 이동</strong>버튼을 클릭하면 URL(xxxxxx.ap-northeast-2.elasticbeanstalk.com/)로 이동하는데 해당 url을 django settings에 있는 ALLOWED_HOST에 추가해주면 된다.</p>
<pre><code class="language-python"># settings.py
...
ALLOWED_HOSTS = [&#39;xxxxxx.ap-northeast-2.elasticbeanstalk.com&#39;]
...</code></pre>
<p>설정을 해야하는 것이 전부 끝났다. 이제 다시 커밋 후 push를 진행해보자.</p>
<pre><code class="language-bash">git add ../cicdproject/settings.py
git commit -m &quot;add allowed_host for aws eb&quot;
git push</code></pre>
<p>일정 시간 후 다시 <strong>환경으로 이동</strong> 버튼을 클릭하게 된다면 성공적으로 로켓이 발사되는 것을 확인할 수 있다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/a06c5867-519c-498c-92e6-b68ccb088290/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-28%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.31.58.png" alt=""></p>
<p>이로써 아주 간단한 Github-actions를 통한 CI/CD 파이프라인 구축을 완료되었다!</p>
<p>시리즈
<a href="https://velog.io/@dj-yang/Django-Gitgub-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-AWS-SETTING">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(1) - AWS SETTING</a>
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-github-actions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(2) - github actions 사용하기</a></p>
<hr>
<h1 id="고찰">고찰</h1>
<p>이번 파이프라인 구축은 사실 AWS Elastic Beanstalk나 Github-Actions에 대해서 정확하게 알고 구현한 것이 아닌 인터넷에 있는 한 포스팅을 확인하고 DOCS를 조금씩 확인하면서 진행했다.</p>
<p>그래서 그런 지 처음에 오류가 발생해도 이 오류가 왜 발생했는 지 한번에 확인이 어려웠다.</p>
<p>사실 이 글을 쓰는 예제를 실행하기 전에 MySQL을 DB로 사용해서 배포를 진행해보았는데, Elastic Beanstalk에서 성능 저하 오류를 보였다.</p>
<p>아직 정확하게 알고 사용하는 것이 아니라는 것을 확실하게 보여주는 예가 아닌가 싶다..ㅎㅎ</p>
<p>AWS Elastic Beanstalk는 초반 설정만 제대로 해놓으면 정말 편리하게 어플리케이션을 배포할 수 있는 서비스이다.</p>
<p>기회가 된다면 이번에는 내가 실제로 제작하고 있는 서비스를 EB를 통해 배포하는 과정 또한 진행해볼 생각이다. 재밌다..</p>
<hr>
<h1 id="참고">참고</h1>
<ul>
<li>Django full CI-CD flow to AWS with GitHub Actions and S3 - <a href="https://dev.to/vlntsolo/django-full-ci-cd-flow-to-aws-with-github-actions-and-s3-2enp">https://dev.to/vlntsolo/django-full-ci-cd-flow-to-aws-with-github-actions-and-s3-2enp</a></li>
<li>aws docs - <a href="https://docs.aws.amazon.com">https://docs.aws.amazon.com</a></li>
<li>github actions docs - <a href="https://docs.github.com/en/actions">https://docs.github.com/en/actions</a></li>
<li>aws elastic beanstalk extensions - <a href="https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/platforms-linux-extend.html">https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/platforms-linux-extend.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(2) - github actions 사용하기]]></title>
            <link>https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-github-actions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-github-actions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 27 Jul 2021 04:21:41 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>전에 작성한 글에 이어서 실제로 github-action을 사용해보자!</p>
<p>시리즈
<a href="https://velog.io/@dj-yang/Django-Gitgub-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-AWS-SETTING">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(1) - AWS SETTING</a>
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-elastic-beanstalk">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(3) - elastic beanstalk</a></p>
<h1 id="본론">본론</h1>
<h2 id="장고-프로젝트-생성">장고 프로젝트 생성</h2>
<p>장고의 별다른 기능을 사용하는 것이 아닌 로켓만 띄울 생각이니 기본적인 프로젝트 명령어가 필요하다.</p>
<pre><code class="language-bash"># 가상환경 생성
&gt; python3 -m venv myvenv
# 가상환경 실행
&gt; source myvenv/bin/activate
# django 설치
&gt; pip install django

# 장고 프로젝트 생성 및 실생
&gt; django-admin startproject cicdproject
&gt; cd cicdproject
/cicdproject &gt; python3 manage.py runserver</code></pre>
<p><img src="https://images.velog.io/images/dj-yang/post/5623cbcb-f0cd-46cb-91c3-b3f8443093ef/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-27%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.50.24.png" alt=""></p>
<p>장고 프로젝트 생성에 성공했다.</p>
<p>우선 패키지 관리를 위해 pip freeze를 사용하자.</p>
<pre><code class="language-bash">bash: pip freeze &gt; requirements.txt</code></pre>
<h2 id="github-repository-생성">github repository 생성</h2>
<p>github-actions를 사용하려는 생각을 가지신 분이면.. 이미 github을 사용 중일테니 각자 진행하자..</p>
<p>repositoty가 생성되었다면 <strong>settings -&gt; Secrets -&gt; New repository secret</strong>에 들어가서 AWS IAM을 통해 만들었던 Access Key 들을 입력한다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/4d25079c-c305-4916-a0ba-ef589561600b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-27%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2012.10.31.png" alt=""></p>
<p><strong>aws_secret_access_key</strong>도 같은 방식으로 설정해준다.</p>
<h2 id="github-actions">github-actions</h2>
<p>github은 커밋 내용 중에 <strong><em>.github/workflows</em></strong>에 있는 파일을 자동으로 실행한다. 따라서 해당 폴더를 장고 프로젝트 내에 만들어야 한다.</p>
<pre><code class="language-yml"># ../cicdproject/.gitgub/workflows/custom_config.yml
name: CI-CD pipeline to AWS
env:
  EB_S3_BUCKET_NAME: [&quot;만들었던 S3 bucket 이름&quot;]
  EB_APPLICATION_NAME: [&quot;만들었던 Application 이름&quot;]
  EB_ENVIRONMENT_NAME: [&quot;만들었던 환경 이름&quot;] # 보통 앱 이름에 -env 형태로 자동 생성
  DEPLOY_PACKAGE_NAME: &quot;django-app-${{ github.sha }}.zip&quot;
  AWS_REGION_NAME: [&quot;Application이 위치하는 지역&quot;] # ex) ap-northeast-2

on:
  push:
    branches:
        - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Git clone on our repo
        uses: actions/checkout@v2

      - name: Create zip deployment package
        run: zip -r ${{ env.DEPLOY_PACKAGE_NAME }} ./ -x *.git*

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.aws_access_key_id }}
          aws-secret-access-key: ${{ secrets.aws_secret_access_key }}
          aws-region: ${{ env.AWS_REGION_NAME }}

      - name: Copying file to S3
        run: aws s3 cp ${{ env.DEPLOY_PACKAGE_NAME }} s3://${{ env.EB_S3_BUCKET_NAME }}/

      - name: Print message on success finish
        run: echo &quot;CI part finished successfully&quot;

  deploy:
    runs-on: ubuntu-latest
    needs: [build]
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.aws_access_key_id }}
          aws-secret-access-key: ${{ secrets.aws_secret_access_key }}
          aws-region: ${{ env.AWS_REGION_NAME }}

      - name: Create new EBL app ver
        run: |
          aws elasticbeanstalk create-application-version \
          --application-name ${{ env.EB_APPLICATION_NAME }} \
          --source-bundle S3Bucket=&quot;${{ env.EB_S3_BUCKET_NAME }}&quot;,S3Key=&quot;${{ env.DEPLOY_PACKAGE_NAME }}&quot; \
          --version-label &quot;${{ github.sha }}&quot;

      - name: Deploy new app
        run: aws elasticbeanstalk update-environment --environment-name ${{ env.EB_ENVIRONMENT_NAME }} --version-label &quot;${{ github.sha }}&quot;
      - name: Print message on success finish
        run: echo &quot;CD part finished successfully&quot;</code></pre>
<p>어플리케이션을 Elastic Beanstalk에서 사용할 수 있게 압축파일로 만들고 해당 압축 파일을 EB에게 보내는 github-actions 명령어들이다.</p>
<p>우선 이 상태로 github에 push를 해서 내 repositoty가 해당 파일을 잘 참조하고 있는 지 확인해보자.</p>
<blockquote>
<p>내 repository -&gt; Actions에서 확인할 수 있다.</p>
</blockquote>
<p>아마 위에서 자신이 직접 설정해야하는 부분에서만 실수를 안했다면 정상적으로 github이 actions 를 실행한 것을 확인할 수 있다.</p>
<p>이제 AWS Elastic Beanstalk에서 다시 한번 <strong>내 환경으로 가기</strong>를 클릭해보겠다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/73f452f2-a28a-4956-bbef-7e1e5f94bd4b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-27%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.18.50.png" alt=""></p>
<p>... 처음 Elastic Beanstalk에서 만들어준 화면과 다르지만 에러가 발생한다..</p>
<p>직접 EC2와 같은 인스턴스를 이용해서 배포를 해보셨다면 자주 보였을 화면이 뜨는 이유는 아직 Elastic Beanstalk에 대한 부분을 설정하지 않았기 때문이다.</p>
<p>우리는 현재 github actions가 정상적으로 실행될 수 있는 부분까지만 설정했다.</p>
<p>다음 글에서는 이제 배포 환경에서도 정상적으로 어플리케이션이 동작해서 로켓을 띄울 수 있도록 해볼 생각이다.</p>
<p>시리즈
<a href="https://velog.io/@dj-yang/Django-Gitgub-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-AWS-SETTING">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(1) - AWS SETTING</a>
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-elastic-beanstalk">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(3) - elastic beanstalk</a></p>
<hr>
<h1 id="참고">참고</h1>
<ul>
<li>Django full CI-CD flow to AWS with GitHub Actions and S3 - <a href="https://dev.to/vlntsolo/django-full-ci-cd-flow-to-aws-with-github-actions-and-s3-2enp">https://dev.to/vlntsolo/django-full-ci-cd-flow-to-aws-with-github-actions-and-s3-2enp</a></li>
<li>aws docs - <a href="https://docs.aws.amazon.com">https://docs.aws.amazon.com</a></li>
<li>github actions docs - <a href="https://docs.github.com/en/actions">https://docs.github.com/en/actions</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(1) - AWS SETTING]]></title>
            <link>https://velog.io/@dj-yang/Django-Gitgub-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-AWS-SETTING</link>
            <guid>https://velog.io/@dj-yang/Django-Gitgub-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-AWS-SETTING</guid>
            <pubDate>Mon, 26 Jul 2021 05:05:42 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>개발을 하면서 자주 들었던 부분이 바로 CI/CD이다. 서비스의 규모가 커지면 자동으로 서버가 테스트를 해주고 배포서버에 내 프로젝트를 업데이트 시킨다고?.. 생각만해도 기분이 짜릿했다.</p>
<p>그래서 구현을 해보고 싶었다.</p>
<p>이 글은 비전공자라 기본적인 cs 지식에서도 많이 부족한 사람이 무작정 파이프라인을 구축하는 내용을 담고 있다.</p>
<p>시리즈
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-github-actions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(2) - github actions 사용하기</a>
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-elastic-beanstalk">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(3) - elastic beanstalk</a></p>
<h1 id="본론">본론</h1>
<p>이번 구축을 위해 나는 aws에서 제공하는 Elastic Beanstalk를 이용하기로 했다. 해당 기능을 이용하기로 한 이유는 다음과 같다.</p>
<ol>
<li>CI/CD 파이프라인에 집중하고 싶다.</li>
<li>아직 기초적인 단계로 추가적인 개발을 하기에는 부담스럽다.</li>
</ol>
<h2 id="아키텍쳐">아키텍쳐</h2>
<h3 id="elastic-beanstalk">Elastic Beanstalk</h3>
<p><img src="https://images.velog.io/images/dj-yang/post/c89b2654-2b26-47ed-9139-7542f12b31f5/elastic.png" alt=""></p>
<p>Elastic Beanstalk를 사용하면 애플리케이션을 실행하는 인프라에 대해 자세히 알지 못해도 AWS 클라우드에서 애플리케이션을 신속하게 배포하고 관리할 수 있습니다. Elastic Beanstalk를 사용하면 선택 또는 제어에 대한 제한 없이 관리 복잡성을 줄일 수 있습니다. 애플리케이션을 업로드하기만 하면 Elastic Beanstalk에서 용량 프로비저닝, 로드 밸런싱, 조정, 애플리케이션 상태 모니터링에 대한 세부 정보를 자동으로 처리합니다.
<a href="https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/Welcome.html">출처 :  AWS Elastic Beanstalk란 무엇입니까?</a> </p>
<p><img src="https://images.velog.io/images/dj-yang/post/5bb5aa9e-7a1f-4244-b1dd-04de257f6242/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-26%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.19.23.png" alt=""></p>
<p>대략적으로 살펴보면 이와 같은 기능들을 한번에 그리고 손 쉽게 설정할 수 있도록 도와주니 내 입장에서는 너무 훌륭한 서비스일 수 밖에 없다.</p>
<h3 id="최종-아키텍쳐">최종 아키텍쳐</h3>
<p>결국 내가 구현하려는 것은 간단하게 압축될 수 있었다.
<img src="https://images.velog.io/images/dj-yang/post/d985d29e-b949-42af-aca0-2d953db5c2ba/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-26%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.23.15.png" alt=""></p>
<h2 id="aws-setting">AWS setting</h2>
<h3 id="elastic-beanstalk---create-application">Elastic Beanstalk - Create Application</h3>
<p>우선 해당 기능을 사용하기 위해 aws setting을 진행하려고 한다.</p>
<ol>
<li><a href="https://ap-northeast-2.console.aws.amazon.com/elasticbeanstalk/home?region=ap-northeast-2#/welcome">aws elastic beanstalk</a> 서비스에 들어가 <strong>Create Application</strong> 클릭</li>
<li><strong>어플리케이션</strong> 이름 작성</li>
<li>플랫폼 탭으로 넘어가서 플랫폼으로 <strong>Python</strong> 선택</li>
<li>플랫폼 브랜치에 자신과 맞는 버전의 파이썬 선택</li>
<li>플랫폼 버전은 <strong>Recommended</strong> 상태로 유지</li>
<li><strong>어플리케이션 생성</strong> 버튼 클릭</li>
</ol>
<p>위의 과정을 진행하면 몇 분정도의 시간이 흐른 후 Elastic Beanstalk에서 그에 맞는 환경을 구축한다.</p>
<p>환경이 구축되고 왼쪽 사이드바에 환경으로 이동을 클릭했을 때 다음과 같은 이미지가 나온다.
<img src="https://images.velog.io/images/dj-yang/post/a70ae80a-2235-45ce-8c3a-30838951fb30/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-26%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.42.20.png" alt=""></p>
<p>끝났다.. 정말 짜릿하다. 원래 EC2 인스턴스에 Nginx - uWSGI - Django 하나하나 설치해가며 시간을 소모 시켰던 것에 비하면 정말.... </p>
<p>개인적으로 생각하기에 elastic beanstalk의 단점은 오류 해결이 어려운 것 같다. 정해진 동작을 실행시키는 등에서 발생하는 특정한 오류 외의 것은 기본적으로 제공해주지 않는 것 같고 해당 인스턴스에 접속하기도 힘들었다.</p>
<h3 id="s3---create-bucket">S3 - Create Bucket</h3>
<p>다음은 S3를 버킷을 만들 생각이다. Elastic Beanstalk는 S3에다가 우리가 올리는 장고를 버전 형식으로 관리한다. 예를 들어 서비스 업데이트를 총 5번 했다면 해당 버킷에는 총 5개의 압축 파일이 존재한다.</p>
<p>또한, 별도로 Elastic Beanstalk에서 사용하는 리소스 등을 저장하는 버킷도 자동으로 생성된다.</p>
<ol>
<li><a href="https://s3.console.aws.amazon.com/s3/home?region=ap-northeast-2">AWS S3</a>에 접속 -&gt; <strong>버킷 만들기</strong> 클릭</li>
<li><strong>버킷 이름</strong> 설정</li>
<li>버킷 리전은 Elastic Beanstalk 앱이 있는 위치와 동일하게 설정(환경으로 이동을 눌렀을 때, 주소 창에 리전이 포함되어 있다.)</li>
<li><strong>모든 퍼블릭 액세스 차단</strong> 상태 유지</li>
<li><strong>버킷 만들기</strong> 클릭</li>
</ol>
<p>이제 우리 장고 앱의 버전을 저장할 버킷 또한 만들었다.</p>
<h3 id="iam---create-user">IAM - Create User</h3>
<p>우리가 직접 인스턴스에 접속하는 것이면 perm 파일 등을 이용해서 해당 인스턴스에 접속 후 일련의 과정을 진행하면 되겠지만, 우리는 github에게 배포를 대신 맡길 생각이다.</p>
<p>따라서 github에서 AWS로 접속할 수 있는 Access Key와 Password가 필요하다.</p>
<ol>
<li><a href="https://console.aws.amazon.com/iamv2/home?#/users">AWS IAM</a>에 접속 -&gt; <strong>사용자 추가</strong> 클릭</li>
<li>사용자 이름에 <em>github-actions</em> (본인이 원하는 이름으로 만들어도 상관없다.)</li>
<li>AWS 액세스 유형 선택에 <strong>프로그래밍 방식 액세스</strong> 체크</li>
<li><strong>다음</strong> 클릭</li>
<li><strong>기존 정책 직접 연결</strong> 클릭</li>
<li><strong>AdministratorAccess-AWSElasticBeanstalk</strong> , <strong>AmazonS3FullAccess</strong> 체크</li>
<li>계속 <strong>다음</strong> 클릭 후 <strong>사용자 만들기</strong> 클릭</li>
<li>Access Key와 Password    저장(기억)해두기</li>
</ol>
<p>IAM을 마지막으로 우리가 사용할 서비스의 구축이 전부 완료되었다.</p>
<p>다음 글에서는 장고 프로젝트를 만들어 로켓을 로컬에서 띄워보고 github 액션을 통해 elastic beanstalk에 배포하는 내용을 작성해볼 생각이다.</p>
<p>시리즈
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-github-actions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(2) - github actions 사용하기</a>
<a href="https://velog.io/@dj-yang/Django-Github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84-%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-elastic-beanstalk">[Django] Github-actions를 이용한 CI/CD 파이프 라인 구축하기(3) - elastic beanstalk</a></p>
<hr>
<h1 id="참고">참고</h1>
<ul>
<li>Django full CI-CD flow to AWS with GitHub Actions and S3 - <a href="https://dev.to/vlntsolo/django-full-ci-cd-flow-to-aws-with-github-actions-and-s3-2enp">https://dev.to/vlntsolo/django-full-ci-cd-flow-to-aws-with-github-actions-and-s3-2enp</a></li>
<li>aws docs - <a href="https://docs.aws.amazon.com/">https://docs.aws.amazon.com/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] AWS-SES를 이용하여 메일 발송하기(2)]]></title>
            <link>https://velog.io/@dj-yang/Django-AWS-SES%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B02-AWS-SES-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@dj-yang/Django-AWS-SES%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B02-AWS-SES-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sat, 24 Jul 2021 02:51:34 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>해당 글은 시리즈 2번째 글입니다. 아직 첫 번째 게시물을 확인 안하시고 오셨다면 확인하고 오셔주세요!</p>
<p><a href="https://velog.io/@dj-yang/Django-AWS-SES%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B0">[Django] AWS-SES를 이용하여 메일 발송하기(1) - AWS-SES 설정</a></p>
<p>이메일 발송 설정하기..</p>
<hr>
<h1 id="장고-앱-설정하기">장고 앱 설정하기</h1>
<h2 id="엔드포인트-설정">엔드포인트 설정</h2>
<p>엔드포인트를 먼저 설정해줘야 해야 한다. 저는 엔드 포인트를 <strong>입구</strong>라고 생각하고 있어요.
메일 발송 프로세스가 1. 장고 앱에서 메일발송 요청 -&gt; 2. aws-ses에서 자체 smtp 서버를 통해 메일 발송 형태로 진행될텐데, 이때 1과 2를 연결해주는 것이라고 생각하면 편하다!</p>
<p>장고에게 우리가 사용할 엔드포인트 설정을 해줘야 한다.</p>
<pre><code class="language-python"># settins.py
EMAIL_BACKEND = &#39;django.core.mail.backends.smtp.EmailBackend&#39;
EMAIL_HOST = &#39;[AWS Simple Email Service -&gt; SMTP settings -&gt; Server_Name]&#39;
EMAIL_HOST_USER = &#39;[이전 글 SMTP 자격증명 설정에서 얻은 Access Key ID]&#39;
EMAIL_HOST_PASSWORD = &#39;[이전 글 SMTP 자격증명 설정에서 얻은 Access Key PW]&#39;
EMAIL_PORT = 587
EMAIL_USE_TLS = True</code></pre>
<blockquote>
<p>Access Key ID나 Access Key PW처럼 서비스에 접속 가능한 값들은 github public 레포 같이 공개된 곳에 올리면 안됩니다!(비밀키 관리하는 방법을 배우세요)</p>
</blockquote>
<p>이제 모든 세팅이 끝났습니다. 메일을 보내봅시다.</p>
<h2 id="메일-보내기">메일 보내기</h2>
<p>메일을 발송할 때는 장고에서 제공하는 mail 시스템을 사용할 생각이다. - <a href="https://docs.djangoproject.com/en/3.2/topics/email/">장고 메일 시스템</a></p>
<p>장고에서 제공하는 기능을 이용하면 별도의 url을 사용할 필요 없이 자신이 가지고 있던 로직 내에서 메일 발송을 사용할 수 있다는 점 같다.</p>
<p>메일을 발송하기 위해 기존에 있는 앱에 views를 추가 한다. </p>
<pre><code class="language-python">from django.core.mail import send_mail

def foo(request):
    ...
    send_mail(
    &#39;제목&#39;,
    &#39;내용&#39;,
    &#39;발신자(aws 등록한 도메인만 사용 가능)&#39;,
    [&#39;수신자&#39;],
    fail_silently=False,
    )
    ...</code></pre>
<p>이때, 만약 내가 sandbox 상태라면 <em><a href="mailto:to@example.com">to@example.com</a></em> 자리에는 확인된 수신자만 사용할 수 있다.</p>
<blockquote>
<p>확인된 수신자 설정 :  AWS Simple Email Service -&gt; Email Addresses -&gt; Verify a New Email Adresses</p>
</blockquote>
<p>해당 기능을 실행하시면 정상적으로 메일이 발송되는 것을 볼 수 있다.</p>
<hr>
<h1 id="추가">추가</h1>
<h2 id="html로-이메일-보내기">HTML로 이메일 보내기</h2>
<p>메일 발송 기능을 구현했더니 이번에는 디자인이 너무 별로인 것 같다고 생각이 들었다.
벨로그 가입할 때 인증 메일이 생각났다..
<img src="https://images.velog.io/images/dj-yang/post/836f8228-03cf-46f7-8ba8-582a3a9487b7/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-24%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.41.19.png" alt=""></p>
<p>이쁘다...
개발자 도구로 열어보니 html로 이루어진 것 같다.</p>
<p>어떻게 하면 나도 이쁜 이메일을 보낼 수 있을 지 찾아보니 역시 장고에서도 좋은 기능을 제공 해주고 있었다.</p>
<h3 id="emailmultialternatives">EmailMultiAlternatives</h3>
<p>장고 메일에서 제공해주는 기능으로 이번에는 내가 사용하는 코드를 가지고 왔다.</p>
<pre><code class="language-python">from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags

def foo(request):
    subject, from_email, to = &#39;제목&#39;, &#39;발신자&#39;, [&#39;수신자&#39;]
    html_message = render_to_string(&#39;html 파일 위치&#39;)
    plain_message = strip_tags(html_message)

    # Sending email here
    msg = EmailMultiAlternatives(subject=subject, body=plain_message, 
                        from_email=from_email,to=to)      
    msg.attach_alternative(html_message, &quot;text/html&quot;)
    msg.content_subtype = &#39;html&#39;
    msg.mixed_subtype = &#39;related&#39;

    msg.send()</code></pre>
<p>이미 장고를 사용할 줄 아는 개발자가 이 글을 볼 것이라 예상하고 따로 설명은 하지 않겠다.(사실 잘 모르기도 하고...)</p>
<hr>
<h1 id="참고">참고</h1>
<p>django sending email - <a href="https://docs.djangoproject.com/en/3.2/topics/email/">https://docs.djangoproject.com/en/3.2/topics/email/</a>
AWS SES DOCS - <a href="https://docs.aws.amazon.com/ses/latest/dg/send-email-getting-started-migrate.html">https://docs.aws.amazon.com/ses/latest/dg/send-email-getting-started-migrate.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] db.sqlite3 안에 저장된 데이터 옮기기]]></title>
            <link>https://velog.io/@dj-yang/Django-db.sqlite3-%EC%95%88%EC%97%90-%EC%A0%80%EC%9E%A5%EB%90%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%98%AE%EA%B8%B0%EA%B8%B0</link>
            <guid>https://velog.io/@dj-yang/Django-db.sqlite3-%EC%95%88%EC%97%90-%EC%A0%80%EC%9E%A5%EB%90%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%98%AE%EA%B8%B0%EA%B8%B0</guid>
            <pubDate>Sun, 18 Jul 2021 06:29:03 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>장고로 개발을 하다보면 로컬 서버에서 sqlite3를 데이터베이스로 많이 사용하게 된다. 가볍고, 편하고, 장고와 호환도 잘되는 느낌이다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/9a801eb6-5844-4dd2-84e5-c5fc72aa387d/sqlite.png" alt=""></p>
<p>오늘은 데이터베이스를 dump하여 테이블 스키마가 같은 다른 프로젝트에서 해당 데이터를 사용해보려고 한다.</p>
<hr>
<h1 id="본론">본론</h1>
<h2 id="dump-데이터-얻기">dump 데이터 얻기</h2>
<p>sqlite를 실행 후 <strong>.help</strong>를 입력하면 sqlite에 관련된 명령어들을 확인할 수 있다.
오늘 사용할 명령어는 4개에서 5개에 해당하는 간단한 작업이다.</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>하는 일</th>
</tr>
</thead>
<tbody><tr>
<td>.output</td>
<td>출력할 파일명을 지정해주는 역할을 한다.</td>
</tr>
<tr>
<td>.dump</td>
<td>데이터베이스에 대한 모든 정보를 덤프한다.</td>
</tr>
<tr>
<td>.exit &amp; .quit</td>
<td>sqlite shell을 종료한다.</td>
</tr>
<tr>
<td>.read</td>
<td>파일에서 데이터베이스 정보를 읽어온다.</td>
</tr>
</tbody></table>
<p>우선 복사할 db.sqlite3가 있는 위치로 이동한 후 sqlite3를 실행한다. 일반적으로 세팅을 변경하지 않았다면 manage.py에 있는 폴더에 있다.</p>
<pre><code class="language-bash">&gt; cd [project-directory]
&gt; sqlite3 db.sqlite3</code></pre>
<p>그럼 다음과 같이 sqlite 버전과 명령어를 입력할 수 있는 화면이 나타난다.
<img src="https://images.velog.io/images/dj-yang/post/9a06e68d-02c2-43b4-b947-a694f7af4a19/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-18%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.57.43.png" alt=""></p>
<p>화면이 정상적으로 나오면 오늘 사용할 명령어들을 입력한다.</p>
<pre><code class="language-sql">sqlite&gt; .output backup_data.sql
sqlite&gt; .dump
sqlite&gt; .exit or .quit</code></pre>
<p>만약 해당 dump data를 사용할 곳을 미리 만들어두었다면 .output 명령어와 함께 디렉토리를 지정해주면 된다.</p>
<pre><code class="language-sql">sqlite&gt; .output /[file-store-directory]/backup_data.sql</code></pre>
<p>그럼 db.sqlite3 또는 해당 디렉토리에 <strong>backup_data.sql</strong>이라는 파일이 생성되었다.
해당 파일을 열어보면 INSERT 등 SQL 문들이 잔뜩 있는 것을 볼 수 있다.</p>
<h2 id="다른-데이터베이스에-데이터-추가하기">다른 데이터베이스에 데이터 추가하기</h2>
<p>먼저, 데이터가 옮겨지는 장고 프로젝트로 이동한다.</p>
<pre><code class="language-bash">cd [another-project]</code></pre>
<p>db.sqilte3 이름으로 파일을 하나 만들어준다.(여기서는 vim을 사용했다.)</p>
<pre><code class="language-bash">&gt; vi db.sqlite3
......
&gt; :wq</code></pre>
<p>만들어진 db.sqlite3 파일을 이용해서 sqlite 명령창을 다시 한번 실행한다.</p>
<pre><code class="language-bash">&gt; sqlite3 db.sqlite3</code></pre>
<p>이제 빈 db.sqlite3를 컨트롤할 수 있는 명령창이 열렸다. dump 파일을 이용해서 데이터를 읽어온다.</p>
<pre><code class="language-sql">sqlite&gt; .read backup_data.sql
sqlite&gt; .exit or .quit</code></pre>
<p>직접 서버를 구동하거나 툴을 이용하면 데이터가 성공적으로 옮겨진 것을 확인할 수 있다.
이상, 같은 스키마를 가지고 있지만 다른 서버에서 구동시켜야하고 데이터베이스를 공유하기 싫을 때 사용할 수 있는 방법을 알아보았다. (이런 방식을 쓸 일이 있을까..)</p>
<p>현업에서는 어떻게 하는 지 궁금하다.</p>
<hr>
<h1 id="실패-사례">실패 사례</h1>
<h2 id="django-migrate-명령어로-테이블을-만들어줬을-때">django migrate 명령어로 테이블을 만들어줬을 때</h2>
<p>같은 스키마를 가지고 있으니 <em>python manage.py migrate</em> 명령어를 통해 db.sqlite3을 만들어주고 그 데이터베이스에 데이터를 읽어오려는 방식을 처음으로 시도했다.</p>
<p>CREATE TABLE 같은 경우에도 IS NOT EXISTS 옵션을 통해 만약 같은 이름의 테이블이 있어도 문제가 없을 것이라고 판단했다.</p>
<p><img src="https://images.velog.io/images/dj-yang/post/79c8a30d-9b77-49f4-94e2-e8fff0b68020/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-18%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.17.10.png" alt=""></p>
<p>하지만 실제로 실행해보니 UNIQUE 관련된 에러가 계속 발생했고, 유저 관련된 부분에서 오류가 나기 시작했다.</p>
<blockquote>
<p>게시물에 저장되어있던 relation 관계의 유저가 다 사라졌다...</p>
</blockquote>
<p>결국 빈 파일을 만들어서 같은 작업을 반복했더니 문제 없이 실행되었다.</p>
<hr>
<h1 id="고찰">고찰</h1>
<p>실패 사례의 오류 원인을 분석했다. </p>
<p>추측해보면 sqlite에서 파일을 읽어올 때 아래처럼 VALUES(id, ...)형태로 불러온다.</p>
<pre><code class="language-sql">INSERT INTO django_migrations VALUES(3,&#39;auth&#39;,&#39;0001_initial&#39;,&#39;2021-03-18 12:19:46.642335&#39;);</code></pre>
<p>django-migrations 또한 id로 관리되는 객체인데 이미 참조되고 있는 id값으로 다시 한번 마이그레이트를 실행시켜서 &#39;이미 사용하고 있는 마이그레이션 id 값이에요!!&#39; 라고 말해주는 것 같다. </p>
<p>찾아보니 schema가 아니라 들어있는 데이터만 가져올 수 있는 방법이 있는 것 같은데 다음에 시간나면 시도 해봐야겠다.</p>
<p>창업을 시도하다가 실패하고 개발자로 전환한 지 얼마 안되어서 그런 지 하면 할 수록 공부할 것이 많아지는 것 같다. 여태까지 너무 얕게만 공부한 것 같아서 마음이 무겁다.</p>
<hr>
<h1 id="참고">참고</h1>
<p>sqlite docs - <a href="https://sqlite.org/cli.html">https://sqlite.org/cli.html</a>
devkuma - <a href="http://www.devkuma.com/books/pages/1333">http://www.devkuma.com/books/pages/1333</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] generic.CreateView를 이용한 회원가입 후 자동 로그인하기 삽질 과정]]></title>
            <link>https://velog.io/@dj-yang/Django-generic.CreateView%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%9B%84-%EC%9E%90%EB%8F%99-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%98%EA%B8%B0-%EC%82%BD%EC%A7%88-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@dj-yang/Django-generic.CreateView%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%9B%84-%EC%9E%90%EB%8F%99-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%98%EA%B8%B0-%EC%82%BD%EC%A7%88-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Sat, 17 Jul 2021 10:57:06 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>개발자 커뮤니티를 제작 중 이메일 인증 기능을 넣을 필요성이 느껴졌고 개발을 시작했다.
로직은 <em>회원가입 -&gt; 인증 이메일 발송 -&gt; 인증 이메일 발송 안내(이메일 재발송 가능)</em> 만들 생각이다. 이메일 인증을 할 때 어떤 유저의 이메일 필드를 인증한 것인지를 구분할 수 있어야 했기 때문에 해시된 스트링을 인증 값으로 사용했다.</p>
<p>위의 과정을 진행하면서 인증 이메일 발송 안내 페이지에도 유저 객체 정보를 가지고 있어야 한다는 것을 깨달았고, <strong>회원 가입 후 자동 로그인</strong>기능이 필요했다.</p>
<p>이 글은 내가 자동 로그인을 구현하면서 생긴 오류와 오류 발생 원인에 대해서 삽질한 내용에 대해서 다루고 있다.</p>
<hr>
<h1 id="구현-과정">구현 과정</h1>
<h2 id="초기-구현">초기 구현</h2>
<p>django generic view의 CreateView의 <a href="https://github.com/django/django/blob/main/django/views/generic/edit.py">코드</a>를 보면서 어느 부분에 로그인 로직을 넣어야 하는 지에 대해서 고민하던 도중 사용자 정보에 대한 validation 검사를 끝내야 실행되는 로직인form_valid() 메서드에 삽입하기로 했다.</p>
<p><em>초기 구현 CreateView/form_valid()</em></p>
<pre><code class="language-python">class SignUpView(CreateView):
  ...
  def form_valid(self, form):
    user = form.save()
    login(self.request, new_user)
    return super().form_valid(form)</code></pre>
<p> form_valid() 메서드의 전체 동작을 바꾸려는 것이 아니라 기존의 로직 내부에 로그인 과정만 추가하고 싶기 때문에 내가 원하는 로직 후에는 super()를 통해 부모 클래스의 form_vaild() 메서드를 호출했다.</p>
<p> 이 과정에서 살짝 걱정했던 부분은 부모 클래스의 메서드에서 <em>self.object = form.save()</em> 코드가 존재하기 때문에 내가 구성한 로직대로라면 form.save()를 두 번 호출하게 된다. 혹시 오류가 있지는 않을까 걱정을 했지만 다행히 문제 없이 회원 가입 과정을 진행할 수 있었다.</p>
<h2 id="문제-발생">문제 발생</h2>
<p> 다행히 에러가 발생하지 않았기 때문에 간단한 테스트 후 넘어가려고 했지만.. 이럴 수가...! 내가 구성한 코드로는 <strong>로그인이 되지 않았다</strong>..</p>
<p> 차라리 위에서 예상하던 것처럼 form.save()가 두번 발생해서 유니크한 속성 때문에 오류가 발생한 것이라면 이해가 되었지만 User 객체도 제대로 생성이 되었고, 인증 메일도 정상적으로 동작했다. 후..</p>
<h2 id="문제-해결stackoverflow">문제 해결(stackoverflow)</h2>
<p> 결국 구글링을 통해 해결방법에 대해서는 확인할 수 있었다. 변경된 코드는 아래와 같다.</p>
<pre><code class="language-python"> class SignUpView(CreateView):
  ...
  def form_valid(self, form):
    valid = super().form_valid(form)
    login(self.request, self.object)
    return valid</code></pre>
<p> 미리 super()클래스를 실행시켜 그 결과 값을 가지고 있고 그 후 로그인 과정을 진행한 다음 슈퍼 클래스의 리턴 값을 반환하면 로그인도 성공적으로 진행된다.</p>
<p> 여기에서 왜 이런 경우가 생길 지에 대해서 궁금증이 생겼다.</p>
<h2 id="추론">추론</h2>
<h3 id="1-다중-상속으로-인한-오류">1. 다중 상속으로 인한 오류</h3>
<p> 파이썬 CBV(Class-Based View)는 다중 상속을 통해 개발자에게 빠르게 개발할 수 있게 도와준다. 따라서 내가 미처 발견하지 못 하지만 login에 문제가 생길 수 있는 부분이 있을 것이라고 예상했다.</p>
<blockquote>
<p>예를 들어 새로 회원가입을 할 경우 보통 기존 로그인 세션을 해제하고 새로운 세션으로 로그인 하는 서비스들이 존재했다.</p>
</blockquote>
<p> 하지만 거의 10시간에 걸친 반복된 django 코드를 둘러봤지만 실질적으로 영향을 끼치는 코드를 발견하기 어려웠다. 결국 django로 실무를 진행하고 있는 지인에게 도움을 요청해서 같이 머리를 맞대기 시작했다.</p>
<h3 id="2-formsave가-2번-진행되며-user-객체가-2개가-생겨서-생기는-오류">2. form.save()가 2번 진행되며 User 객체가 2개가 생겨서 생기는 오류</h3>
<p> 정확한 내부 원인은 모르겠지만 머리를 맞대고 처음 나온 의견이었다. 하지만 곧, 이부분은 생각하지 않았는데 그 이유는 두 가지였다.</p>
<ol>
<li>회원 가입 과정에서 별도의 오류가 생기지 않았다.</li>
<li>username과 email 필드는 unique 속성을 가지고 있었기 때문에 만약 form.save()가 정상적으로 2번 일어났다면 에러가 발생했었어야 했다.</li>
</ol>
<h3 id="3-우리가-확인할-수-없는-위치에서의-로직이-세션을-초기화-시킨다">3. 우리가 확인할 수 없는 위치에서의 로직이 세션을 초기화 시킨다.</h3>
<p>django 프레임 워크 같은 경우는 middleware와 WSGI 등을 이용한다. 해당 과정을 진행하는 와중에 세션이 초기화 된다.</p>
<p>이 추론을 증명하기 위해서 IDE에서 제공하는 Debug Tool을 이용해서 초기 구현(실패했던 구현) 로직을 따라 가기 시작했다.</p>
<p>로직을 따라갔을 때 발견한 점은 AuthenticationMiddleWare를 실행하는 도중에 session 정보가 사라진다는 점이다.(장고의 로그인 여부는 sessionid를 통해 결정된다.)</p>
<p>다음 코드에서 세션이 초기화 되었다.</p>
<pre><code class="language-python">request.user = SimpleLazyObject(lambda: get_user(request))</code></pre>
<h3 id="4-lazy-방식으로-인해-오류가-발생했다">4. Lazy 방식으로 인해 오류가 발생했다.</h3>
<p>해당 코드의 SimpleLazyObject()를 보자마자 지인이 거론한 원인이었다. lazy 방식은 쉽게 말해서 값이 필요로 할 때 해당 값을 계산하는 방식인데 그로 인해 오류가 발생한 것이 아닐까 추측했다.</p>
<p>하지만 내가 구성한 코드의 다른 로직 중 초기 구현과 유사한 부분이 존재했는데 같은 방식임에도 불구하고 정상적으로 동작했다.</p>
<p>다시 3번에서 발견한 부분에 집중했다. 그 중 get_user() 함수의 코드를 살폈다.</p>
<pre><code class="language-python"># django.contrib.auth.middleware.py
def get_user(request):
    if not hasattr(request, &#39;_cached_user&#39;):
        request._cached_user = auth.get_user(request)
    return request._cached_user</code></pre>
<p>request 속성 중에 <em>_cached_user</em> 속성이 없을 경우 auth.get_user(request)를 호출해 해당 속성을 추가시켜주는 함수였고, 해당 속성이 유저의 로그인 여부를 결정하는데 중요한 속성임을 인지했다.</p>
<p>그 다음에 auth.get_user(request) 부분을 살펴보기로 했다.</p>
<pre><code class="language-python"># django.contrib.auth.__init__.py
def get_user(request):
    &quot;&quot;&quot;
    Return the user model instance associated with the given request session.
    If no user is retrieved, return an instance of `AnonymousUser`.
    &quot;&quot;&quot;
    from .models import AnonymousUser
    user = None
    try:
        user_id = _get_user_session_key(request)
        backend_path = request.session[BACKEND_SESSION_KEY]
    except KeyError:
        pass
    else:
        if backend_path in settings.AUTHENTICATION_BACKENDS:
            backend = load_backend(backend_path)
            user = backend.get_user(user_id)
            # Verify the session
            if hasattr(user, &#39;get_session_auth_hash&#39;):
                session_hash = request.session.get(HASH_SESSION_KEY)
                session_hash_verified = session_hash and constant_time_compare(
                    session_hash,
                    user.get_session_auth_hash()
                )
                if not session_hash_verified:
                    request.session.flush()
                    user = None

    return user or AnonymousUser()</code></pre>
<p>실패한 로직을 따라가니 session_hash_verified가 False 값을 가지고 있었고 밑의 if 문이 실행되어 session이 초기화 되고 user에 None이 들어가면서 AnonymousUser()가 request._cached_user 속성에 들어가고 있었다.</p>
<p>해당 부분의 get_session_auth_hash() 함수를 살펴보면 아래와 같다</p>
<pre><code class="language-python">    def get_session_auth_hash(self):
        &quot;&quot;&quot;
        Return an HMAC of the password field.
        &quot;&quot;&quot;
        key_salt = &quot;django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash&quot;
        return salted_hmac(
            key_salt,
            self.password,
            algorithm=&#39;sha256&#39;,
        ).hexdigest()</code></pre>
<p>본인의 비밀번호를 이용해 해시 값을 반환하는 함수인데, 즉 request하고 있는 유저의 세션에 가지고 있는 해시 값과 저장되어 있는 유저 정보의 해시 값이 동일하지 않아서.. 다른 유저가 Anonymous를 반환한다는 소리였다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>정리하자면 내가 초기에 구현한 로직은 form.save()가 2번 실행되서 발생하는 문제였다.</p>
<p>form.save()는 authenticationform의 save() method를 실행하는데 해당 save()메서드의 동작 과정 중 set_password()라는 함수를 통해 유저의 비밀번호를 만들어서 객체에 저장시킨다.</p>
<p>set_password()는 make_password()를 호출하고 make_password()는 sha 해시 알고리즘을 사용해 해시된 비밀번호를 반환하는데 이때 랜덤하게 바뀌는 salt 값을 이용한다.</p>
<p>즉, 다른 정보는 전부 같지만 비밀번호는 다른 2개의 객체가 발생했고 첫 번째 객체를 저장 후 해당 객체의 비밀번호로 만든 세션 값을 가지고 있었는데 두 번째 form.save()에서 유저 객체의 비밀번호가 업데이트 되어 현재 세션 값과 유저 객체의 패스워드로 만든 세션 값이 일치하지 않아 장고는 유저가 제대로 로그인하지 않았다고 판단해서 Anonymous User 객체를 반환하게 된 것 같다.</p>
<p>사실 아직도 해결되지 않은 부분이 있다. 2번째 form.save()에서는 create 로직이 시작된 것이 아니라 update 로직이 실행되는 것이 정확한 지 모르겠다.</p>
<hr>
<h1 id="고찰">고찰</h1>
<p>얕은 지식으로 인해 디버깅 시간이 굉장히 길어졌다고 생각한다. 당연히 unique 필드를 가지고 있는 User 객체로 인해 form.save()가 두번 실행될 수 없다고 생각부터 시작된 에러였다.</p>
<p>여태까지는 눈에 보이는 에러 페이지를 노출시켜주는 일반화 된 에러만 해결했었다.(그게 아니였어도 굉장히 쉬운 편에 속하는 문제들만 있어 금방 해결되었다.) 이번 과정을 통해 코드 구성에 대한 경각심을 기존보다 더 크게 가지게 되었다.</p>
<p>이번 디버깅을 통해 좋은 점은 장고 로그인에 대해서 조금 더 자세하게 알 수 있었다는 점이다. 추가적으로 궁금한 부분은 session이다. 로그인 인증에서 session을 이용하고 있으니 그 부분을 더 공부해 인증에 대해서 알아보고 싶다.</p>
<hr>
<h1 id="참고-및-도움">참고 및 도움</h1>
<ul>
<li>django - <a href="https://github.com/django/django">https://github.com/django/django</a></li>
<li>jinmay - <a href="https://github.com/jinmay">https://github.com/jinmay</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] AWS-SES를 이용하여 메일 발송하기(1) - AWS-SES 설정]]></title>
            <link>https://velog.io/@dj-yang/Django-AWS-SES%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dj-yang/Django-AWS-SES%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 13 Jul 2021 13:14:47 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>개인 프로젝트로 개발자 커뮤니티(stackoverflow와 유사한 질문형 커뮤니티)를 개발하고 있다.</p>
<p>회원가입과 관련해서 만약 사용자가 회원가입을 한다면 이메일 필드에 적은 이메일로 계정 확인용 메일을 보내는 기능을 구현하려고 이메일 발송에 관련해서 검색해보기 시작했다.</p>
<p>검색해본 결과 장고에서 가장 많이 사용하는 것은 <a href="https://github.com/django-ses/django-ses">django-ses</a>라는 패키지였다. 문서를 살펴보았을 때 아래와 같은 이점이 있다고 한다.</p>
<blockquote>
<ul>
<li>You don&#39;t want to maintain mail servers.</li>
</ul>
</blockquote>
<ul>
<li>You are already deployed on EC2 (In-bound traffic to SES is free from EC2 instances).</li>
<li>You need to send a high volume of email.</li>
<li>You don&#39;t want to have to worry about PTR records, Reverse DNS, email whitelist/blacklist services.</li>
<li>You want to improve delivery rate and inbox cosmetics by DKIM signing your messages using SES&#39;s Easy DKIM feature.</li>
<li>Django-SES is a truely drop-in replacement for the default mail backend. Your code should require no changes.</li>
</ul>
<p>필요한 기능은 단순히 내가 원하는 메일을 발송만 하는 기능이고 대부분 aws 자체적으로 해결 가능했기 때문에 매력적으로 느껴지지는 않았다.</p>
<hr>
<h1 id="메일서버-설정하기">메일서버 설정하기</h1>
<p>이 글은 단순히 <a href="https://docs.aws.amazon.com/ses/latest/dg/send-email-getting-started-migrate.html">AWS SES DOCS</a>를 따라한 것 뿐이다. 이것을 따라하면서 aws가 확실히 사용자 친화적으로 엄청난 노력을 하고 있다고 느껴졌다.</p>
<h2 id="도메인-인증">도메인 인증</h2>
<p>이메일 발송 기능을 구현하려고 하는 개발자라면 이미 도메인은 있을 것이라고 예상하고 따로 말을 안했지만 메일을 발송하려면 본인 소유의 도메인이 필요하다. 본인 소유의 도메인으로 아래와 같은 절차를 진행한다.</p>
<ol>
<li><strong>AWS Simple Email Service</strong> 클릭</li>
<li>왼쪽 사이드 바에서 <strong>Domains</strong> 클릭</li>
<li>상단에 <strong>Verify a New Domain</strong> 클릭</li>
<li>입력창에 <em>본인 소유의 도메인</em> 입력 (example.com)</li>
<li><strong>Generate DKIM settings</strong> 체크</li>
<li><strong>Verify this Domain</strong> 클릭</li>
<li>생성된 정보를 DNS에 입력(본인은 AWS route 53 사용)</li>
</ol>
<p>도메인 인증이 완료되었다!</p>
<h3 id="dkim-settings">DKIM settings</h3>
<p>aws를 사람들이 왜 쓰는 지 느꼈던 것 중의 하나가 도메인 인증에 있는 DKIM settings 체크이다. DKIM(DomainKeys Identified Mail)은 모든 메일의 헤더에 암호화를 진행하여 메일의 변조를 막아주는 역할을 한다. 잘 모르겠지만 그냥 좋은 것 같다..</p>
<p>이런 설정을 버튼 하나만 체크하니 저절로 구현이 되었다..(물론 그렇기 때문에 비싼감이 있는 것도 사실이다)</p>
<h2 id="배포-환경-접근-요청">배포 환경 접근 요청</h2>
<p>이메일 인증을 마치고 나면 우선 우리의 메일 서버(aws 메일 서버)는 샌드박스라는 제한된 기능만 제공한다. 이를 실제 운영하는 웹 페이지처럼 동작하려면 <a href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html">Moving out of the Amazon SES sandbox</a>의 가이드라인을 따르면 된다. 샌드박스에서는 하루에 보낼 수 있는 메일량, 1초에 보낼 수 있는 메일 수, 확인된 수신자에게만 메일 발송 등으로 설정이 되어있어서 실제 운영에서는 사용이 어렵지만 우선은 이메일 발송에 설정에 신경을 써야되니 이 환경을 적극 활용할 생각이다.</p>
<h2 id="도메인-인증-시스템-구성">도메인 인증 시스템 구성</h2>
<p>SPF나 DKIM 같은 인증 시스템을 구성한다.</p>
<blockquote>
<p>SPF(Sender Policy Framework) : 메일서버 정보를 사전에 DNS에 공개 등록함으로써 수신자로 하여금 이메일에 표시된 발송자 정보가 실제 메일서버의 정보와 일치하는지를 확인할 수 있도록 하는 인증기술</p>
</blockquote>
<p>우리는 이미 aws에서 DKIM 설정을 해주었다.</p>
<h2 id="smtp-자격-증명-생성">SMTP 자격 증명 생성</h2>
<p>메일을 발송하려면 SMTP를 이용해야 한다.</p>
<blockquote>
<p>SMPT : 간이 우편 전송 프로토콜, 메일을 작성해서 보내시면 그 메일은 SMTP 서버(보내는 메일서버, Outgoing mailserver)로 일단 전송되며 이 SMTP 서버에서 SENDMAIL 프로그램을 구동하여 해당 메일 주소로 메일을 보내게 됩니다.</p>
</blockquote>
<p>AWS SES에서는 SMTP 설정도 굉장히 쉽게 가능하다.</p>
<ol>
<li><strong>AWS Simple Email Service</strong> 클릭</li>
<li><strong>SMTP Settings</strong> 클릭</li>
<li><strong>Create My SMTP Credentials</strong> 클릭</li>
<li>IAM 유저 네임 설정</li>
<li>오른쪽 하단에 <strong>생성</strong> 클릭</li>
<li>제공되는 Access Key ID와 Access Key PW 따로 저장</li>
<li>생성이 완료되면 Server Name : email-smtp.ap-northeast-2.amazonaws.com 형태로 내가 사용할 SMTP 서버가 보여진다.(이미 AWS에서는 SMTP 서버 또한 구현되어 있다. 우린 그냥 이용만 하면 된다.)</li>
</ol>
<h2 id="endpoint-연결">endpoint 연결</h2>
<p>장고 앱에서 진행하면 된다.</p>
<hr>
<p>다음 글에서는 장고 기본 이메일 벡엔드 설정을 이용하여 엔드포인트를 연결하고 메일 발송을 테스트 해볼 생각이다.</p>
<p>혹시 틀린 부분이 있다면 지적해주세요. 감사합니다.</p>
<p><a href="https://velog.io/@dj-yang/Django-AWS-SES%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A9%94%EC%9D%BC-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B02-AWS-SES-%EC%84%A4%EC%A0%95">[Django] AWS-SES를 이용하여 메일 발송하기(2)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python]Removal of Tuple Parameter Unpacking]]></title>
            <link>https://velog.io/@dj-yang/PythonRemoval-of-Tuple-Parameter-Unpacking</link>
            <guid>https://velog.io/@dj-yang/PythonRemoval-of-Tuple-Parameter-Unpacking</guid>
            <pubDate>Tue, 13 Jul 2021 06:34:58 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>책에서 추천해주는 링크를 읽어보려고 들어간 곳에서 멘탈이 나갔다..</p>
<hr>
<p>파이썬 3버전부터 함수 매개 변수에서 사용할 수 있었던 언패킹이 불가능해졌다.</p>
<pre><code class="language-python">def fxn(a, (b, c), d):
    pass</code></pre>
<p>위와 같은 방식으로 기존에 가능했다면 앞으로 사용하려면 아래와 같은 방법을 사용해야 한다.</p>
<pre><code class="language-python">def fxn(a, b_c, d):
    b, c = b_c
    pass</code></pre>
<p>직접적으로 파이썬 코드를 사용하는 사람에 입장에서는 크게 달라지는 부분은 없다.
왜 이같이 변경되었는 지에 대한 설명은 <a href="https://www.python.org/dev/peps/pep-3113/">pip-3113</a>에 나와있다.</p>
<h1 id="정리">정리</h1>
<p>다양한 근거를 제시하며 변경의 당위성을 알렸는데 이해한 부분은 다음과 같다.</p>
<ol>
<li>변경되어도 사실 개발하는 입장에서는 크게 변하는 부분이 없다.</li>
<li>실제 기존 방식과 같이 사용되는 코드는 조사 당시 40/20,000 개의 확률 정도였다.(굉장히 적게 사용한다.)</li>
</ol>
<p>추가로 파이썬 바이트 코드에 대한 부분에 대해서 언급했는데 이 부분은 아직도 잘 모르겠다.</p>
<blockquote>
<p>In order to get all of the details about the tuple from the function one must analyse the bytecode of the function. <strong>This is because the first bytecode in the function literally translates into the tuple argument being unpacked.</strong> Assuming the tuple parameter is named .1 and is expected to unpack to variables spam and monty (meaning it is the tuple (spam, monty)), the first bytecode in the function will be for the statement spam, monty = .1. This means that to know all of the details of the tuple parameter one must look at the initial bytecode of the function to detect tuple unpacking for parameters formatted as .\d+ and deduce any and all information about the expected argument. Bytecode analysis is how the inspect.getargspec function is able to provide information on tuple parameters. This is not easy to do and is burdensome on introspection tools as they must know how Python bytecode works (an otherwise unneeded burden as all other types of parameters do not require knowledge of Python bytecode).</p>
</blockquote>
<p>이해 안되는 부분은 빨간색으로 강조해놓았다.</p>
<p>개인적으로 저 내용에 대해 추론해본 내용은 파이썬에서는 함수를 초기화할 때 메모리를 차지하게 되는데, 만약 매개 변수 중에 언패킹 상태인 것이 존재한다면 해당 언패킹의 데이터가 메모리의 첫 번째 바이트를 차지하게 된다는 내용인 것 같다...</p>
<p>나중에 시간 날 때 조금 더 자세히 봐야할텐데 시간이 날 지 모르겠다..</p>
<h1 id="고찰">고찰</h1>
<p>역시 영어공부가 필요하다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python]Extended Iterable Unpacking]]></title>
            <link>https://velog.io/@dj-yang/PythonExtended-Iterable-Unpacking</link>
            <guid>https://velog.io/@dj-yang/PythonExtended-Iterable-Unpacking</guid>
            <pubDate>Tue, 13 Jul 2021 06:29:38 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>네이버 블로그에서 velog로 옮기면서 괜찮은 내용이거나 아니면 한번 더 살펴봐야할 포스팅을 옮기게 되었다</p>
<p>이 글에서는 <a href="https://www.python.org/dev/peps/pep-3132/">pep 3132</a>에 대해서 다룬다.</p>
<p>해당 내용은 순환 가능 요소의 언패킹에 *(star-expr)를 사용하는 것을 권장하는 이유에 대해서 설명한다.</p>
<hr>
<h1 id="문제점">문제점</h1>
<p>언패킹에 있어서 많은 알고리즘에서 기존에 사용하는 방식은 아래와 같았다.</p>
<pre><code class="language-python">first, last = seq[0], seq[1:]</code></pre>
<p>위의 방식처럼 index를 이용한 slicing 방법을 사용했지만 이는 다음과 같은 문제점이 있었다.</p>
<ul>
<li>셋과 같이 리스트는 아니지만 순회 가능한 요소의 경우 다음과 같은 과정을 거쳐야해서 성능을 저하시켰다.</li>
</ul>
<pre><code class="language-python">it = iter(seq)
first = it.next()
rest = list(it)</code></pre>
<ul>
<li>불필요한 인덱싱을 사용해야 한다.</li>
</ul>
<hr>
<h1 id="권장-사용">권장 사용</h1>
<p>위의 문제점을 해결하기 위한 방법으로 아래와 같은 방식을 권장한다.</p>
<pre><code class="language-python">first, *last = seq
# 아래와 같은 방식도 가능
a, * b, c = seq 
[a, * b, c] = seq
* a, = seq
a, * b in [(1, 2, 3), (4, 5, 6, 7)]:
    print (b)

# 불가능한 방식
* a = seq # 단일 사용</code></pre>
<p>*를 사용하면 아래와 같은 방식으로 동작한다.</p>
<blockquote>
<ul>
<li>all items for mandatory targets before the starred one</li>
</ul>
</blockquote>
<ul>
<li>collect all remaining items from the iterable in a list</li>
<li>pop items for mandatory targets after the starred one from the list</li>
<li>push the single items and the resized list on the stack</li>
</ul>
<p>위의 설명대로 예시를 들자면 아래의 코드를 실행했을 때:</p>
<pre><code class="language-python">a, *b, c = [1, 2, 3, 4, 5]</code></pre>
<p>다음과 같이 동작한다.</p>
<pre><code class="language-python">a = 1

b = [2, 3, 4, 5]

c = b.pop()</code></pre>
<hr>
<h1 id="정확한-동작-소스-코드cpython">정확한 동작 소스 코드(CPython)</h1>
<pre><code class="language-c">unpack_iterable(PyObject *v, int argcnt, int argcntafter, PyObject **sp)
{
    int i = 0, j = 0;
    Py_ssize_t ll = 0;
    PyObject *it;  /* iter(v) */
    PyObject *w;
    PyObject *l = NULL; /* variable list */
    assert(v != NULL);
    it = PyObject_GetIter(v);
    if (it == NULL)
        goto Error;
    for (; i &lt; argcnt; i++) {
        w = PyIter_Next(it);
        if (w == NULL) {
            /* Iterator done, via error or exhaustion. */
            if (!PyErr_Occurred()) {
                if (argcntafter == -1) {
                    PyErr_Format(PyExc_ValueError,
                        &quot;not enough values to unpack (expected %d, got %d)&quot;,
                        argcnt, i);
                }
                else {
                    PyErr_Format(PyExc_ValueError,
                        &quot;not enough values to unpack &quot;
                        &quot;(expected at least %d, got %d)&quot;,
                        argcnt + argcntafter, i);
                }
            }
            goto Error;
        }
        *--sp = w;
    }
    if (argcntafter == -1) {
        /* We better have exhausted the iterator now. */
        w = PyIter_Next(it);
        if (w == NULL) {
            if (PyErr_Occurred())
                goto Error;
            Py_DECREF(it);
            return 1;
        }
        Py_DECREF(w);
        PyErr_Format(PyExc_ValueError,
            &quot;too many values to unpack (expected %d)&quot;,
            argcnt);
        goto Error;
    }
    l = PySequence_List(it);
    if (l == NULL)
        goto Error;
    *--sp = l;
    i++;
    ll = PyList_GET_SIZE(l);
    if (ll &lt; argcntafter) {
        PyErr_Format(PyExc_ValueError,
            &quot;not enough values to unpack (expected at least %d, got %zd)&quot;,
            argcnt + argcntafter, argcnt + ll);
        goto Error;
    }
    /* Pop the &quot;after-variable&quot; args off the list. */
    for (j = argcntafter; j &gt; 0; j--, i++) {
        *--sp = PyList_GET_ITEM(l, ll - j);
    }
    /* Resize the list. */
    Py_SIZE(l) = ll - argcntafter;
    Py_DECREF(it);
    return 1;
Error:
    for (; i &gt; 0; i--, sp++)
        Py_DECREF(*sp);
    Py_XDECREF(it);
    return 0;
}</code></pre>
<hr>
<h1 id="고찰">고찰</h1>
<p>영어 공부 좀 하자..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python] 데커레이터(decorator)]]></title>
            <link>https://velog.io/@dj-yang/Python-%EB%8D%B0%EC%BB%A4%EB%A0%88%EC%9D%B4%ED%84%B0decorator</link>
            <guid>https://velog.io/@dj-yang/Python-%EB%8D%B0%EC%BB%A4%EB%A0%88%EC%9D%B4%ED%84%B0decorator</guid>
            <pubDate>Tue, 13 Jul 2021 06:13:57 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>장고를 이용한 웹 페이지 제작 중 데커레이터 사용에서 고통 받으며 공부하게 되었다..</p>
<h1 id="데커레이터decorator">데커레이터(decorator)</h1>
<p>데커레이터는 다른 함수를 인수로 받는 콜러블(데커레이터된 함수)이다.</p>
<h4 id="데커레이터-예시">데커레이터 예시</h4>
<pre><code class="language-python"># 예시를 위해 만든 데커레이터
def decorator(func):
    print(&#39;running decorator&#39;)
    return func

@decorator
def func()
    print(&#39;running func&#39;)

# func = decorator(func) 데커레이터가 적용된 함수를
# 우리가 이해하기 좋은 형태로 만들었다.
func()
------------------------------------------
&gt; running decorator
&gt; running func</code></pre>
<p>위의 예제와 같이 데커레이터를 사용한 함수를 감싸주는(wrapping) 해주는 형태로 동작을 하게 된다.</p>
<h1 id="동작-시점">동작 시점</h1>
<p>파이썬에서는 동작 시점을 크게 두 가지로 나누는 것 같다.</p>
<ol>
<li>임포트 타임</li>
<li>런 타임</li>
</ol>
<p>사실 내가 파이썬을 조금 공부하면서 느낀 것은 두 가지 동작에 겹치는 부분이 있는 것 같기는 하다.</p>
<p>데커레이터 같은 경우는 <strong>임포트 타임</strong> 시점에서 실행됨을 알 수 있다.
확인을 위해 몇 가지 코드를 실행시켜보았다.</p>
<pre><code class="language-python"># decorators.py

print(&#39;test start&#39;)

def decorators(func):
    print(&#39;decorator called&#39;)
      return func

@decorators
def inner_function():
      print(&#39;inner function called&#39;)

if __name__ == &#39;__main__&#39;:
      print(&#39;main start&#39;)
      inner_function()
      print(&#39;main end&#39;)

print(&#39;test end&#39;)


-------------------------------
# import time
&gt; test start
&gt; decorator called
&gt; test end

# run time
&gt; test start
&gt; decorator called
&gt; main start
&gt; inner function called
&gt; main end
&gt; test end</code></pre>
<p>위의 결과를 보면 알겠지만 데커레이터 같은 경우는 <strong>임포트 타임</strong>에서 실행된다.</p>
<h1 id="장고에서의-데커레이터">장고에서의 데커레이터</h1>
<p>파이썬 프레임워크인 장고에서 특정 기능을 이용할 때 만약 특정 권한이 필요하다면 그것을 확인하는 기능(ex, 로그인 여부) 등에서 사용하고는 한다.</p>
<pre><code class="language-python"># 사용자가 로그인을 하지 않으면 해당 함수를 실행시킬 수 없다
@login_required
def write_view(request):
    ...</code></pre>
<h1 id="클로저">클로저</h1>
<p>데커레이터를 제대로 사용하려면 클로저에 대해서 알아야 한다고 한다.
클로저에 관해서 이해한 바를 한문장으로 표현한다면 아래와 같지 않을까 싶다</p>
<blockquote>
<p>함수가 비전역 외부 변수를 다루는 것</p>
</blockquote>
<p>말 그대로 global()을 이용한 전역 변수가 아닌 외부 변수를 이용하는 경우를 뜻한다.
다양한 책 또는 코드등을 살펴봐도 이런 경우는 함수가 다른 함수 안에 정의된 경우 밖에 없었다.</p>
<p>파이썬에서 클로저란 <strong><em>&#39;내부함수에서 외부 함수의 변수를 다루게 되는 경우&#39;</em></strong>인 것 같다.</p>
<h3 id="nonlocal">nonlocal</h3>
<p>내부 함수에서 외부 함수에 있는 변수에 값을 증가(+=)시키려고 하면 변수 범위에 관련된 에러가 뜬다.(이 부분까지 추가하기엔 너무 길다.)</p>
<blockquote>
<p>UnboundLocalError: local variable &#39;[variable]&#39; referenced before assignment</p>
</blockquote>
<p>말 그대로 아직 선언되지 않은 지역 변수를 사용했다는 뜻이다. 이때는 변수 앞에 &#39;nonlocal&#39; 키워드를 이용해 내부 함수 내에서 선언해주면 된다.</p>
<pre><code class="language-python">def outter():
    a = 1
    def inner():
        nonlocal a
        a += 1</code></pre>
<hr>
<h1 id="참고">참고</h1>
<p>전문가를 위한 파이썬 - 루시아누 하말류</p>
]]></description>
        </item>
    </channel>
</rss>