<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>suyeonme</title>
    <link>https://suyeonme.tistory.com/</link>
    <description>공부한 내용을 정리하는 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 20 Apr 2026 05:11:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>suyeonme</managingEditor>
    <image>
      <title>suyeonme</title>
      <url>https://tistory1.daumcdn.net/tistory/5365180/attach/ace82c36ccf04851bc425a6c05615443</url>
      <link>https://suyeonme.tistory.com</link>
    </image>
    <item>
      <title>Kustomize, ArgoCD에 대해서 알아보기</title>
      <link>https://suyeonme.tistory.com/125</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;argo-cd.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMAITl/btsJPUhJt15/QpdInIJlALUdVoFcJ3Lqc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMAITl/btsJPUhJt15/QpdInIJlALUdVoFcJ3Lqc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMAITl/btsJPUhJt15/QpdInIJlALUdVoFcJ3Lqc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMAITl%2FbtsJPUhJt15%2FQpdInIJlALUdVoFcJ3Lqc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1500&quot; height=&quot;696&quot; data-filename=&quot;argo-cd.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ArgoCD란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitOps 방식으로 어플리케이션의 상태를 관리하고 배포하는 도구로, 코드 변환에 따라 자동으로 쿠버네티스 리소스를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, git commit hash 또는 git tag가 변경된 경우 자동(또는 수동)으로 이를 감지하고 배포하게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kustomize란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Kubernetes 리소스를 템플릿 없이 선언적으로 관리할 수 있게 해주는 도구이다. ArgoCD와 Kustomize를 함께 사용해서 쿠버네티스 리소스 정의 파일을 렌더링하고 손쉽게 클러스터에 배포할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) Application 리소스 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD가 어떤 Git 레포지토리와 브랜치를 추적하고, 어떤 Kustomize를 사용하고 어플리케이션을 어떤 &lt;span data-token-index=&quot;1&quot;&gt;네임스페이스&lt;/span&gt;에 배포할지 정의해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1727589149222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application # 어플리케이션 정의
metadata:
  name: [어플리케이션 이름]
  namespace: argocd
spec:
  project: default
  source:
    repoURL: [레포지토리 주소]
    targetRevision: HEAD
    path: [Kustomize 설정 파일이 위치한 경로]
    kustomize:
      namePrefix: [이름에 사용할 prefix]
  destination:
    server: [Kubernetes API 서버]
    namespace: [리소스가 배포될 네임스페이스]
  syncPolicy:
    automated:
      prune: true # 필요없는 리소스 제거
      selfHeal: true&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) Kustomize 설정 정의 (Kustomization.yaml)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD가 지정된 경로에서 Kustomize로 어플리케이션 리소스를 생성하는 파일&lt;/p&gt;
&lt;pre id=&quot;code_1727589429381&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: [모든 리소스를 배포할 네임스페이스]

# 리소스 파일 목록
resources:
- deployment-front.yaml
- deployment-server.yaml
...

# 도커 이미지를 새버전으로 치환
images:
- name: [이미지 이름]
  newName: [이미지 이름]
  newTag: 2.0.0
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3) 리소스별 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1727589746729&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Service 정의
apiVersion: v1
kind: Service
...

# Gateway 정의
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
...

# VirtualService 정의
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
...&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;deployment&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일에 정의된대로 어플리케이션의 인스턴스를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; Gateway&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Istio의 리소스로, 클러스터 외부의 트래픽을 수신한다. (트래픽을 적절한 서비스로 라우팅)&lt;/li&gt;
&lt;li&gt;HTTP, HTTPS, TCP 트래픽을 관리하며, 특정 port를 리스닝해서 트래픽을 적적한 서비스로 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; VirtualService&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Istio의 리소스로, 트래픽 라우팅 규칙을 정의한다.&lt;/li&gt;
&lt;li&gt;Gateway와 함께 사용되며 트래픽이 클러스터 내부로 들어온 후, &lt;b&gt;어떤 서비스로 라우팅될지 결정&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Istio란?&lt;/b&gt;&lt;br /&gt;MSA를 관리하고 보호하기위한 오픈소스 &lt;b&gt;Service Mesh&lt;/b&gt; 플랫폼 마이크로 서비스간의 통신을 제어/추적/보안/인증등의 기능을 제공해서 어플리케이션의 가용성/안정성/보안을 강화한다.&lt;/blockquote&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; Service(SVC)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes 리소스로 Pod의 고정 네트워크 엔드포인트를 제공한다. (로드밸런싱, 서비스 디스커버리 지원)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 &amp;zwj; /기타</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/125</guid>
      <comments>https://suyeonme.tistory.com/125#entry125comment</comments>
      <pubDate>Sun, 29 Sep 2024 15:04:47 +0900</pubDate>
    </item>
    <item>
      <title>gRPC, gRPC-gateway란?</title>
      <link>https://suyeonme.tistory.com/124</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;gRPC란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;gRPC (gRPC Remote Procedure Call)&lt;/i&gt;는 Google에서 개발한 언어에 상관없이 사용할 수 있는 고성능 PRC 프레임워크이다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;특히&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;마이크로서비스 아키텍처&lt;/u&gt;에서 많이 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;grpc.svg&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AhF3v/btsIHW7vJTq/NqlszdOaEZiEUDLQY1ezuK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AhF3v/btsIHW7vJTq/NqlszdOaEZiEUDLQY1ezuK/tfile.svg&quot; data-alt=&quot;https://grpc.io/img/landing-2.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AhF3v/btsIHW7vJTq/NqlszdOaEZiEUDLQY1ezuK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAhF3v%2FbtsIHW7vJTq%2FNqlszdOaEZiEUDLQY1ezuK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;327&quot; data-filename=&quot;grpc.svg&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://grpc.io/img/landing-2.svg&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gRPC 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;HTTP/2 기반&lt;/u&gt;&lt;/b&gt;으로 동작한다. HTTP/2의 &lt;u&gt;멀티플렉싱&lt;/u&gt; 기능을 통해 하나의 커넥션에서 동시에 여러 요청과 응답을 처리할 수 있다. 따라서&amp;nbsp;네트워크 성능이 향상되고, 지연 시간이 줄어든다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&lt;u&gt;&lt;b&gt;Protocol Buffer(Protobuf)를 데이터 직렬화 포맷&lt;/b&gt;&lt;/u&gt;으로 사용한다. Protobuf는 데이터 구조를 컴팩트하게 직렬화하며, JSON보다 더 빠르고 효율적인 직렬화와 역직렬화를 제공한다. (전송속도 향상 및 대역폭 절약)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;다양한 언어 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;클라이언트와 서버 간의 &lt;b&gt;양방향 스트리밍&lt;/b&gt;을 지원한다. 따라서 실시간 데이터 전송에 유용하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;HTTP/2의 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Binary Protocol&lt;/b&gt;을 사용해서 데이터를 전송한다. 따라서 데이터의 직렬화와 파싱이 효율적이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Multiplexing&lt;/b&gt;: 하나의 TCP 연결에서 여러 요청과 응답을 동시에 처리할 수 있다. 따라서 네트워크 효율성을 향상시키고 요청 및 응답간의 지연시간을 줄인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HPACK:&lt;/b&gt; HPACK이라는 헤더 압축 알고리즘을 사용해서 요청과 응답 헤더를 압축한다. 헤더의 중복을 줄이고 전송 데이터의 크기를 줄여 네트워크 대역폭을 절약한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Server Push&lt;/b&gt;: 서버가 클라이언트의 요청을 예상하고 필요한 리소스를 클라이언트에게 미리 전송할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;RPC(&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;Remote Procedure Call&lt;/span&gt;)란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPC는 네트워크를 통해 다른 컴퓨터에서 프로시저(함수)를 호출할 수 있게 해주는 프로토콜로, 분산 시스템에서 서로 다른 프로세스 간에 통신을 가능하게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스 아키텍처의 경우, 각 서버는 다양한 언어로 개발될 수 있다. PRC를 사용하면 언어에 상관없이 원격에 있는 프로시저를 로컬 메서드처럼 호출할 수 있다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gRPC 구성요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Stub&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC의 클라이언트와 서버 간의 인터페이스를 정의하고 구현하는 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 Stub: 클라이언트에서 원격 프로시저를 호출하는 데 사용되는 객체이다. 해당 stub으로 서버의 메서드를 로컬 메서드처럼 호출할 수 있다.&lt;/li&gt;
&lt;li&gt;서버 Stub: 서버 측에서 클라이언트의 요청을 처리하는 데 사용되는 객체입니다. 해당 stub으로 클라이언트의 요청을 수신하고, 적절한 로직을 수행하여 응답을 클라이언트에게 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Protocol Buffer(Protobuf)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 구조를 정의&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;(Interface Definition Language, IDL)&lt;/span&gt;하고, 이 구조에 따라 데이터를 직렬화하고 역직렬화한다. Protobuf는 다양한 프로그래밍 언어와 플랫폼에서 사용될 수 있는 데이터를 정의하는 방법을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protobuf는 &lt;b&gt;protoc(Protobuf 컴파일러)&lt;/b&gt;를 이용해서 .proto 파일을 기반으로 다양한 프로그래밍 언어에 맞는 소스 코드를 자동으로 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직렬화: 바이트 스트림으로 변환&lt;/li&gt;
&lt;li&gt;역직렬화: 바이트 스트림을 원래 구조로 복원&lt;/li&gt;
&lt;li&gt;.proto 파일: 데이터 구조와 메시지 형식을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1721393895733&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# proto file
syntax = &quot;proto3&quot;;

// 메시지 정의
message Person {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

// RPC 서비스 정의
service ExampleService {
  rpc GetPerson (PersonRequest) returns (Person);
}

// 요청 메시지 정의
message PersonRequest {
  int32 id = 1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;gRPC-gateway란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;gRPC 서버와 HTTP/JSON API 간의 브릿지 역할을 하는 도구&lt;/b&gt;이다. Protocol Buffers 문서를 기반으로 gRPC 서버에서 정의된 메서드를 HTTP/JSON API로 자동 변환하여, RESTful API와 gRPC를 동시에 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 RESTful(HTTP/JSON) API로 서버에 요청하면 gRPC-gateway가 해당 요청을 gRPC로 변환한다. 따라서 gRPC 서버와 REST 클라이언트간의 통신이 가능해진다. -&amp;gt; &lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;gRPC는 Protocol Buffer를 사용해서 데이터를 직렬화하기 때문에 직접 HTTP/JSON 요청을 보낼 수 없기때문!&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>프로그래밍 &amp;zwj; /기타</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/124</guid>
      <comments>https://suyeonme.tistory.com/124#entry124comment</comments>
      <pubDate>Fri, 19 Jul 2024 22:00:29 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 의존성 충돌과 가상환경(venv)</title>
      <link>https://suyeonme.tistory.com/123</link>
      <description>&lt;h1&gt;의존성 충돌&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 컴퓨터에서 Python으로 개발된 여러 개의 프로젝트 돌리는 경우, 의존성이 충돌할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 1: package v3.x 사용&lt;/li&gt;
&lt;li&gt;프로젝트 2: Django v4 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 프로젝트가 동일한 컴퓨터에 설치된 하나의 파이썬 실행 환경(runtime)을 사용하므로, 파이썬 패키지를 서로 공유하게 되기 때문에 의존성이 충돌할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pip 패키지 매니저의 동작 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 기본적으로 패키지 설치시 pip을 사용하는데 시스템 전역으로 패키지를 설치한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pip으로 패키지 설치시, 컴퓨터의 site-packages 디렉터리에 안에 설치된다.&lt;/li&gt;
&lt;li&gt;npm이나 maven의 경우, 패키지 매니저가 프로젝트별로 패키지 설치를 지원 (의존성이 충돌하지않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;pip 명령어&lt;/h4&gt;
&lt;pre id=&quot;code_1720948838506&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip -V

pip install [pacakge]
pip install [pacakge]==2.28.0 # 특정 버전 설치
pip show requests # 설치된 패키지 확인
pip uninstall [pacakge]
pip list # 컴퓨터에 설치된 전체 패키지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;requirements.txt&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 개발하기 위해서 필요한 모든 패키지의 목록을 저장한 파일이다.&lt;/p&gt;
&lt;pre id=&quot;code_1720948865441&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 현재 가상 환경에서 설치된 패키지 목록을 requirements.txt 파일로 저장
pip freeze &amp;gt; requirements.txt

# 의존성 다운로드
pip install -r requirements.txt&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;가상환경(venv)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트별로 독립적인 Python 실행 환경을 생성하여, 각 프로젝트마다 필요한 패키지들을 격리된 상태에서 관리할 수 있게한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;venv 내장 모듈을 통해서 쉽게 가상 환경을 만들 수 있으며, 이를 통해 프로젝트 간 의존성 충돌 문제를 예방할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상환경을 사용하지 않는다면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 내의 모든 프로젝트에서 하나의 파이썬 실행 환경을 사용하면서 동일한 경로에 패키지를 설치하고 서로 공유한다. 따라서 하나의 프로젝트에서 설치한 패키지의 버전이 다른 프로젝트에서 설치한 동일 패키지의 다른 버전과 충돌할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가상환경 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 환경이 활성화된 상태에서 패키지를 설치하면, .venv 디렉터리 내부에 해당 패키지가 설치된다. 따라서 다른 파이썬 프로젝트에 영향을 미치지않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1720948894359&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd [directory]

# 가상환경 생성
python -m venv .venv
# 가상환경 활성화
source .venv/bin/activate
# 가상환경 비활성화
deactivate

# gitignore에 가상환경 파일 추가해서 사용!&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍 &amp;zwj; /Python</category>
      <category>python</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/123</guid>
      <comments>https://suyeonme.tistory.com/123#entry123comment</comments>
      <pubDate>Sun, 14 Jul 2024 18:24:55 +0900</pubDate>
    </item>
    <item>
      <title>데이터 파이프라인이란?</title>
      <link>https://suyeonme.tistory.com/121</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 파이프라인&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Raw 데이터를 정보(information)로 변환하기 위해 데이터 처리 단계를 거치는 시스템을 의미한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;etl_pipeline.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn5Y8n/btsIv6XVTbt/EFGgRQT7mp7fgvqWRIFRUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn5Y8n/btsIv6XVTbt/EFGgRQT7mp7fgvqWRIFRUk/img.png&quot; data-alt=&quot;https://www.altexsoft.com/media/2020/03/etl_pipeline.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn5Y8n/btsIv6XVTbt/EFGgRQT7mp7fgvqWRIFRUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn5Y8n%2FbtsIv6XVTbt%2FEFGgRQT7mp7fgvqWRIFRUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1792&quot; height=&quot;730&quot; data-filename=&quot;etl_pipeline.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.altexsoft.com/media/2020/03/etl_pipeline.png&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 아래의 과정으로 나타낼 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 수집(Ingestion)&lt;/b&gt;: 다양한 형식의 raw 데이터를 수집하고 파이프라인으로 가져오는 단계&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 처리(Processing)&lt;/b&gt;: 수집된 데이터를 정제, 변환, 집계, 필터링등 필요한 형태로 가공하는 단계로, &lt;i&gt;ETL(Extract, Transform, Load)&lt;/i&gt; 또는 &lt;i&gt;ELT(Extract, Load, Transform)&lt;/i&gt;등의 과정을 포함한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 저장(Storage)&lt;/b&gt;: 처리된 데이터를 저장하는 단계로, 데이터베이스, 데이터 웨어하우스, 데이터 레이크등의 저장소에 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 분석(Analysis)&lt;/b&gt;: 저장된 데이터를 분석하여 인사이트를 도출하는 단계로, 데이터 시각화도구, 통계 분석, 머신러닝등을 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 소비(Consumption)&lt;/b&gt;: 분석된 데이터를 사용자나 시스템이 최정적으로 활용하는 단계로, 보고서, 대시보드, 애플리케이션, API등을 통해서 데이터를 제공한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 파이프라인 관리 도구&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 파이프라인을 효율적으로 관리하는데 사용되는 주요 도구들을 정리해봤다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CDC, Change Data Capture&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 발생하는 변경사항을 캡쳐해서 다른 시스템에 실시간으로 적용하는 기술로, 데이터를 실시간으로 동기화하는데 사용된다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 변경사항을 실시간으로 분석 시스템으로 전송, 실시간 데이터 스트리밍&lt;/li&gt;
&lt;li&gt;Kafka 기반의 Debezium, DMS(Amazon Database Migration Service), Oracle GoldenGate등&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://airflow.apache.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Airflow (Apache)&lt;/a&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 데이터 파이프라인을 정의하고, 스케줄링하며, 모니터링할 수 있는 워크플로우 관리 플랫폼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/dags.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;u&gt;&lt;i&gt;DAG(Directed Acyclic Graph)&lt;/i&gt;&lt;/u&gt;&lt;/a&gt;를 사용하여 작업의 의존성을 정의한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 파이프라인의 오케스트레이션, 스케줄링 및 모니터링&lt;/li&gt;
&lt;li&gt;ETL(Extract, Transform, Load) 파이프라인 관리, 데이터 처리 워크플로우 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DAG(Directed Acyclic Graph)란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: left;&quot;&gt;DAG is&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;a collection of all the tasks you want to run, organized in a way that reflects their relationships and dependencies&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: left;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-07-12 at 10.27.11 AM.png&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tlxBo/btsIvN5n2uD/LaymmCoHcbqI9zATHSB0d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tlxBo/btsIvN5n2uD/LaymmCoHcbqI9zATHSB0d1/img.png&quot; data-alt=&quot;Airflow의 DAG&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tlxBo/btsIvN5n2uD/LaymmCoHcbqI9zATHSB0d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtlxBo%2FbtsIvN5n2uD%2FLaymmCoHcbqI9zATHSB0d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;193&quot; data-filename=&quot;Screenshot 2024-07-12 at 10.27.11 AM.png&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Airflow의 DAG&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://cloud.google.com/bigquery/?utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=japac-KR-all-en-dr-BKWS-all-hv-trial-PHR-dr-1605216&amp;amp;utm_content=text-ad-none-none-DEV_c-CRE_631194537537-ADGP_Hybrid+%7C+BKWS+-+BRO+%7C+Txt+-Data+Analytics-BigQuery-big+query-main-KWID_43700076510494897-kwd-43801843506&amp;amp;userloc_9197392-network_g&amp;amp;utm_term=KW_google%20bigquery&amp;amp;gad_source=1&amp;amp;gclid=Cj0KCQjwhb60BhClARIsABGGtw_YoVabzvAG9ZxNtgqwsQ7R4CuFlCEwukaav-6BUlKqhuiOF4kJtHoaAhT4EALw_wcB&amp;amp;gclsrc=aw.ds&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Google BigQuery&lt;/a&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Cloud Platform에서 제공하는 &lt;i&gt;&lt;u&gt;데이터 웨어하우스(Data Warehouse)&lt;/u&gt;&lt;/i&gt;로, 대규모 데이터 분석과 실시간 데이터 쿼리에 최적화되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터&lt;span&gt;&amp;nbsp;&lt;/span&gt;웨어하우스(Data Warehouse)란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 소스에서 수집한 대량의 데이터를 저장하고 분석하는 데 최적화된 중앙 저장소이다. 주로 데이터 분석 분야에서 많이 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실시간 데이터 분석, SQL 지원, 분산 처리 아키텍쳐로 높은 성능 제공, 데이터 로딩 및 내보내기(CSV, JSON등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://superset.apache.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Superset &lt;span&gt;&amp;nbsp;&lt;/span&gt;(Apache)&lt;/a&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 데이터 소스에서 데이터를 가져와 시각화 대시보드를 만들고 공유해주는 데이터 시각화 도구이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 분석 결과 시각화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;superset.jpg&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IgLZo/btsIxhKU9rQ/PLEUEvRqAfrzMc6y09NtF1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IgLZo/btsIxhKU9rQ/PLEUEvRqAfrzMc6y09NtF1/img.jpg&quot; data-alt=&quot;https://superset.apache.org/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IgLZo/btsIxhKU9rQ/PLEUEvRqAfrzMc6y09NtF1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIgLZo%2FbtsIxhKU9rQ%2FPLEUEvRqAfrzMc6y09NtF1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;900&quot; data-filename=&quot;superset.jpg&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://superset.apache.org/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Spark (Apache)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 데이터를 효율적으로 처리하고 다양한 데이터 소스와 통합할 수 있는 기능을 제공하는, 빅 데이터 처리를 위한 &lt;u&gt;&lt;i&gt;분산 처리 프레임워크&lt;/i&gt;&lt;/u&gt;이다. 메모리 내에서 데이터를 처리하여 디스크 기반 처리보다 빠르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대규모 로그 파일, 이벤트 데이터, 데이터 웨어하우스 데이터 등을 분산 처리하여 집계 및 분석 (웹 로그 분석, 대규모 트랜잭션 데이터 처리), 실시간 데이터 처리, 머신러닝&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Celery&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 작업 큐 시스템으로, 비동기 작업을 예약하고, 큐에 넣고, 작업을 처리한다. 주로 백엔드에서 긴 시간 동안 실행되는 작업을 비동기적으로 처리할 때 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 파이프라인의 특정 작업을 병렬로 실행, 데이터 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 도구가 사용되는 흐름은 아래와 같이 생각해볼 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;운영 데이터베이스에 데이터 저장&lt;/li&gt;
&lt;li&gt;운영 데이터베이스의 변경사항을 &lt;u&gt;CDC&lt;/u&gt;로 캡쳐&lt;/li&gt;
&lt;li&gt;CDC로 캡쳐한 데이터를 Airflow가 BigQuery에 적재&lt;/li&gt;
&lt;li&gt;BigQuery의 데이터를 Superset으로 시각화 및 분석&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 &amp;zwj; /기타</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/121</guid>
      <comments>https://suyeonme.tistory.com/121#entry121comment</comments>
      <pubDate>Fri, 12 Jul 2024 10:54:04 +0900</pubDate>
    </item>
    <item>
      <title>Makefile이란?</title>
      <link>https://suyeonme.tistory.com/120</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Makefile이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Makefile은 make라는 빌드 자동화 도구에서 사용하는 파일로, &lt;b&gt;프로젝트를 빌드, 테스트, 배포하는 작업을 자동화&lt;/b&gt;하는 데 사용되는 파일이다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Python, C언어, Java등 다양한 언어에서 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 프론트엔드는 Next.js, 서버는 Python으로 구성된 모노레포 프로젝트를 보다가 Makefile을 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Makefile은 Java에서는 Maven이나 Gradle, Javascript에서는 NPM script등의 빌드 도구로 대체할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Makefile 구성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Target: 빌드할 파일 또는 수행할 작업의 이름&lt;/li&gt;
&lt;li&gt;Dependency: 타겟이 의존하는 파일이나 타겟, 종속성이 변경되면 타겟을 다시 빌드함&lt;/li&gt;
&lt;li&gt;Command: 타겟을 빌드하기 위해 수행할 명령&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.PHONY&lt;/b&gt;: make 명령이 실행되는 디렉토리에 Makefile의 target과 같은 이름의 파일이 존재할 경우에 충돌이 발생하는데 .PHONY에 명시하여 회피할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 코드를 포매팅하고 싶다면 &lt;b&gt;make format&lt;/b&gt;을 입력하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1720359327552&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 변수 정의
VENV = venv
PYTHON = $(VENV)/bin/python
PIP = $(VENV)/bin/pip
BLACK = $(VENV)/bin/black
FLAKE8 = $(VENV)/bin/flake8
PYTEST = $(VENV)/bin/pytest

# 기본 타겟
.PHONY: all
all: install lint test

# 가상 환경 설정
.PHONY: venv
venv:
    python3 -m venv $(VENV)

# 패키지 설치
.PHONY: install
install: venv
    $(PIP) install -r requirements.txt

# 코드 포매팅
.PHONY: format
format: venv
    $(BLACK) .

# 린트 검사
.PHONY: lint
lint: venv
    $(FLAKE8) .

# 테스트 실행
.PHONY: test
test: venv
    $(PYTEST)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Makefile.venv&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;9c20a05e-9904-40ad-9767-27740f6c5776&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Makefile.venv 파일은 Python 프로젝트에서 가상 환경(virtual environment)을 설정하고 관리하는 Makefile이다. 따라서 Makefile.venv를 만들면 따로 가상환경을 만들지않아도 된다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;.gitignore에 등록해서 사용하면 된다!&lt;/blockquote&gt;
&lt;pre id=&quot;code_1720360435597&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 가상 환경 생성 및 패키지 설치
make -f Makefile.venv
# 가상 환경 활성화
source .venv/bin/activate
# 패키지 업데이트
make -f Makefile.venv update
# 가상 환경 삭제
make -f Makefile.venv clean&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 &amp;zwj; /기타</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/120</guid>
      <comments>https://suyeonme.tistory.com/120#entry120comment</comments>
      <pubDate>Sun, 7 Jul 2024 22:57:08 +0900</pubDate>
    </item>
    <item>
      <title>.envrc 파일로 환경변수 자동으로 설정하기</title>
      <link>https://suyeonme.tistory.com/119</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;.envrc 파일이란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;.envrc 파일은 direnv와 함께 사용되며, 특정 디렉터리에 진입할 때 자동으로 환경 변수를 설정할 수 있도록 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;.env.sample&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 필요한 환경변수를 .env.sample 파일에 작성해서 예시 코드를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1720358664119&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SENTRY_DNS=http://sentry.dom/your-url
SLACK_BOT_TOKEN=your-slack-url&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;.envrc&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트에 .envrc 파일을 만들고, .env.sample 파일을 참고해서 환경변수를 정의한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;direnv&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vscode를 사용중이라면, direnv 플러그인을 설치한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-07-07 at 10.26.52 PM.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJcy5/btsIrw2sq6f/YqJ9OGKra1KDWIaKVS5Kk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJcy5/btsIrw2sq6f/YqJ9OGKra1KDWIaKVS5Kk1/img.png&quot; data-alt=&quot;vscode 플러그인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJcy5/btsIrw2sq6f/YqJ9OGKra1KDWIaKVS5Kk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJcy5%2FbtsIrw2sq6f%2FYqJ9OGKra1KDWIaKVS5Kk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;201&quot; data-filename=&quot;Screenshot 2024-07-07 at 10.26.52 PM.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vscode 플러그인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인 설치 후, 환경변수를 로딩할 프로젝트에서 아래 명령어를 입력하면, 해당 프로젝트 진입시 자동으로 프로젝트의 환경변수를 불러와서 설정해준다!&lt;/p&gt;
&lt;pre id=&quot;code_1720358933101&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;direnv allow&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍 &amp;zwj; /기타</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/119</guid>
      <comments>https://suyeonme.tistory.com/119#entry119comment</comments>
      <pubDate>Sun, 7 Jul 2024 22:29:18 +0900</pubDate>
    </item>
    <item>
      <title>[독서] 가상면접 사례로 배우는 대규모 시스템 설계 기초 - 사용자 수에 따른 규모 확장성</title>
      <link>https://suyeonme.tistory.com/115</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_4513.jpg&quot; data-origin-width=&quot;2991&quot; data-origin-height=&quot;2450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTlts4/btsH70K6vdK/tU8T0mzNXQIQ6Shb5spW10/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTlts4/btsH70K6vdK/tU8T0mzNXQIQ6Shb5spW10/img.jpg&quot; data-alt=&quot;확장가능한 아키텍쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTlts4/btsH70K6vdK/tU8T0mzNXQIQ6Shb5spW10/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTlts4%2FbtsH70K6vdK%2FtU8T0mzNXQIQ6Shb5spW10%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2991&quot; height=&quot;2450&quot; data-filename=&quot;IMG_4513.jpg&quot; data-origin-width=&quot;2991&quot; data-origin-height=&quot;2450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;확장가능한 아키텍쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수직적 규모 확장(Scale up) vs 수평적 규모 확장(Scale out)&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;수직적 규모 확장(Scale up)의 문제점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한대의 서버에 CPU나 메모리를 무한대로 증설할 방법은 없기에 한계가 있다.&lt;/li&gt;
&lt;li&gt;장애에 대한 자동복구(failover)나 다중화(re-dundancy)방안을 제시하지않는다. 따라서 서버에 장애가 발생하면 어플리케이션은 중단된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;로드 밸런서&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;역할&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;트래픽 분산:&lt;/b&gt; 클라이언트 요청을 여러 서버에 분산시켜 각 서버의 부하를 줄인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고가용성:&lt;/b&gt; 서버 장애 시에도 서비스를 지속할 수 있도록 Health Check 및 Fail Over를 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 향상:&lt;/b&gt; 서버 간의 트래픽을 균등하게 분배하여 응답 시간을 줄이고 성능을 최적화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;종류&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소프트웨어 로드 밸런서:&lt;/b&gt; NGINX, Apache Traffic Server등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라우드 기반 로드 밸런서:&lt;/b&gt; AWS Elastic Load Balancing (ELB), Google Cloud Load Balancing, Azure Load Balancer등&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;데이터베이스 다중화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 사이에 master와 slave 관계를 설정하고 데이터 원본을 master 서버에, 사본은 slave 서버에 저장하는 방식이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master 서버: 데이터 원본 저장, 쓰기 연산(write)&lt;/li&gt;
&lt;li&gt;slave 서버: 데이터 사본 저장, 읽기 연산(read)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 변경 연산은 master 서버, 읽기 연산은 slave 서버로 분산된다. 따라서 병렬로 처리될 수 있는 query의 수가 늘어나서 성능이 좋아진다.&lt;/li&gt;
&lt;li&gt;데이터를 지역적으로 떨어진 장소에 다중화 시킴으로써 자연재해등의 이유로 데이터베이스 서버의 일부가 파괴되어도 데이터를 보존할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;데이터를 여러 지역에 복제함으로써 데이터베이스 서버에 장애가 발생해도 다른 서버에 있는 데이터로 서비스를 유지할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;slave 서버가 1대인데 다운된 경우, 읽기 연산은 한시적으로 master 서버로 전달된다. 또한 즉시 새로운 slave 서버가 장애 서버를 대체한다. slave 서버가 여러대인 경우, 읽기 연산은 나머지 slave 서버로 분산될 것이며 새로운 slave 서버가 장애 서버를 대체한다.&lt;/li&gt;
&lt;li&gt;master 서버가 다운되면, 한대의 slave 서버만 있는 경우 해당 slave 서버가 새로운 master 서버가 된다. 모든 데이터베이스 연산은 일시적으로 새로운 master 서버에서 처리된다. 그리고 새로운 slave 서버가 추가된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;캐시&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;응답 시간은 캐시를 붙이고 정적 콘텐츠를 CDN으로 옮기면 개선할 수 있다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시는 비싼 연산 결과 또는 자주 참조되는 데이터를 메모리안에 두고 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소다. 대표적으로 Redis가 있다. 애플리케이션의 성능은 &lt;u&gt;데이터베이스를 얼마나 자주 호출하느냐&lt;/u&gt;에 크게 좌우되는데 캐시는 그런 문제를 완화할 수 있다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;캐시 계층(cache tier)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 계층은 데이터가 잠시 보관되는 곳으로 데이터베이스보다 빠르다. 별도의 캐시 계층을 두면 성능이 개선될 뿐아니라 데이터베이스의 부하를 줄일 수 있고 캐시 계층의 규모를 독립적으로 확장할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;읽기 주도형 캐시 전략(read-through caching strategy)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 주도형 캐시 전략은 다음의 순서로 진행된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청을 받은 웹서버는 캐시에 응답이 저장되어있는지 본다.&lt;/li&gt;
&lt;li&gt;만일 저장되어있다면 해당 데이터를 클라이언트에 반환한다.&lt;/li&gt;
&lt;li&gt;없는 경우라면 데이터베이스 query를 통해 데이터를 찾아 캐시에 저장한 뒤 클라이언트에 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;캐시 사용시 유의할 점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터갱신은 자주 일어나지 않지만 참조가 빈번하게 일어나는 경우 캐시가 적합하다.&lt;/li&gt;
&lt;li&gt;캐시는 데이터를 휘발성 메모리에 저장하므로 영구적으로 보관할 데이터는 캐시에 저장하면 안된다.&lt;/li&gt;
&lt;li&gt;만료된 데이터는 캐시에서 삭제해야한다. 따라서 만료기한을 적절하게 설정해야한다.&lt;/li&gt;
&lt;li&gt;저장소의 원본을 갱신하는 연산과 캐시를 갱신하는 연산이 단일 트랜잭션으로 처리되지않는 경우 일관성이 깨질 수 있다.&lt;/li&gt;
&lt;li&gt;캐시 서버를 한대만 두는 경우, 장애 발생시 해당 서버는 &lt;u&gt;단일 장애 지점(Single point of failure, SPOF)&lt;/u&gt;가 될 수 있다. 따라서 여러 지역에 걸쳐 캐시 서버를 분산해야한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;적절한 &lt;u&gt;캐시 데이터 방출 정책&lt;/u&gt;을 적용해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단일 장애 지점(Single point of failure, SPOF)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 특정 지점에서의 장애가 전체 시스템의 동작을 중단 시키는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐시 데이터 방출 정책&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시가 꽉 찼는데 추가로 캐시에 데이터를 넣어야하는 경우 기존 데이터를 내보내는 정책이다. 대표적으로 &lt;i&gt;&lt;b&gt;LRU(Least recently used)&lt;/b&gt; -- 마지막으로 사용된 시점이 가장 오래된 데이터를 내보내는 알고리즘-- &lt;/i&gt;이나&lt;i&gt;&lt;b&gt; LFU(Least frequently used)&lt;/b&gt; --사용된 빈도가 가장 낮은 데이터를 내보내는 알고리즘&lt;/i&gt;이 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;CDN&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CDN은 정적 컨텐츠를 전송하는데 사용되는 지리적으로 분산된 서버의 네트워크이다. 이미지, 비디오, CSS, Javascript 파일등을 캐시할 수 있다.&amp;nbsp; 사용자가 웹사이트를 방문하면 사용자에게 가장 가까운 CDN 서버가 정적 컨텐츠를 전달한다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무상태(Stateless) 계층&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상태 웹 계층&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 계층을 수평적으로 확장하기위해서 &lt;u&gt;상태 정보(사용자의 세션)를 웹계층에서 제거&lt;/u&gt;해야한다. 상태정보를 관계형 데이터베이스나 NoSQL 같은 저장소에 보관하고 필요할 때 가져오도록 하는 것이다. 상태정보가 웹서버에서 제거되었으므로, 트래픽 양에 따라 웹서버를 넣거나 빼기만하면 자동적으로 규모를 확장 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;상태 정보 의존적인 아키텍쳐&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 정보를 보관하는 서버는 클라이언트 정보를 요청 사이에서 공유한다. 따라서 같은 클라이언트의 요청은 항상 같은 서버로 전송되어야한다. 대부분의 &lt;u&gt;로드밸런서는 고정 세션(sticky session) 기능&lt;/u&gt;으로 이를 지원하는데, 이는 로드밸런서에 부담을 준다.&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;메세지큐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메세지큐는 데이터의 &lt;u&gt;무손실&lt;/u&gt;을 보장하고 &lt;u&gt;비동기 통신&lt;/u&gt;을 지원하는 컴포넌트이다. 메세지큐를 이용하면 서비스 또는 서버간의 결합이 느슨해져서 규모 확장성이 보장되어야하는 안정적 애플리케이션을 구성하기좋다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 아키텍쳐&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Producer/Publisher: 메세지를 만들어 메제시큐에 publish한다. 생산자는 소비자 프로세스가 다운되어도 메세지를 발행할 수 있다.&lt;/li&gt;
&lt;li&gt;Consumer/Subscriber: 메세지를 받아 그에 맞는 동작을 수행한다. 소비자는 생산자 서비스가 가용가능한 상태가 아니여도 메세지를 수신할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진 보정 애플리케이션을 만든다고 가정해보자. 보정은 시간이 오래 걸릴 수 있으므로 비동기로 처리하면 편리하다. 메세지큐를 이용해서 다음과 같이 구상할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;웹서버는 사진 보정 작업을 메세지 큐에 넣는다.&lt;/li&gt;
&lt;li&gt;사진 보정 작업 프로세스는 이 작업을 메세지큐에서 꺼내어 비동기적으로 완료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;데이터베이스의 규모 확장&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Sharding&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 수평적 확장은 sharding이라고 부르는데, 더 많은 서버를 추가함으로써 성능을 향상시킬 수 있다. 샤딩은 &lt;u&gt;대규모 데이터베이스를 Shard라고 부르는 작은 단위로 분할하는 기술&lt;/u&gt;을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Sharding Key(Partition Key)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩 전략을 구현할 때 가장 중요한 것은 샤딩키를 어떻게 정하느냐이다. 샤딩 키는 데&lt;u&gt;이터가 어떻게 분산될지 정하는 하나 이상의 컬럼으로 구성&lt;/u&gt;된다. 샤딩 키를 정할 때 데이터를 고르게 분할할 수 있도록 하는 것이 가장 중요하다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;고려사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;샤드 소진(Shard exhaustion) 현상이 발생하면, 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치해야한다.&lt;/li&gt;
&lt;li&gt;유명인사(Celebrity)/핫스팟 키(Hotspot key) 문제 발생시, 유명인사 각각에 샤드 하나씩을 할당하거나 더 잘게 쪼개야한다.&lt;/li&gt;
&lt;li&gt;여러샤드에 걸친 데이터를 조인하기가 힘든 경우, 데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;샤드 소진(Shard exhaustion)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 너무 많아서 하나의 샤드로는 감당불가한 경우, 또는 샤드간 데이터 분포가 균등하지못해서 어떤 샤드에 할당된 공간 소모가 다른 샤드에 비해 빠르게 진행되는 경우를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유명인사(Celebrity)/핫스팟 키(Hotspot key) 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제이다.&lt;/p&gt;</description>
      <category>독서 </category>
      <category>가상면접사례로배우는대규모시스템설계기초</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/115</guid>
      <comments>https://suyeonme.tistory.com/115#entry115comment</comments>
      <pubDate>Sat, 22 Jun 2024 08:54:47 +0900</pubDate>
    </item>
    <item>
      <title>[Nestjs, Axios] Access Token과 Refresh Token으로 인증 구현하기(클라이언트, 서버)</title>
      <link>https://suyeonme.tistory.com/114</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증 과정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;u&gt;서버&lt;/u&gt;: 로그인 성공시, 서버에서 refresh token은 &lt;u&gt;httpOnly&lt;/u&gt; 헤더에, access token은 클라이언트에 전송합니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;클라이언트&lt;/u&gt;: access token을 로컬스토리지에 저장합니다. 이후 요청시 헤더에 access token을 추가해서 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;클라이언트&lt;/u&gt;: access token의 기간이 만료되었다면(서버의 응답이 401인 경우) refresh token 갱신 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;서버&lt;/u&gt;: refresh token을 검증한 뒤, access token, refresh token을 갱신합니다. 갱신된 access token을 클라이언트에 전송합니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;클라이언트&lt;/u&gt;: 갱신된 access token을 로컬스토리지에 저장합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유효기간 및 저장 위치&amp;nbsp;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token: 30분~1시간, 클라이언트에서 헤더에 추가해서 요청&lt;/li&gt;
&lt;li&gt;Refresh Token: 1주~2주, 서버에서 httpOnly 헤더에 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;서버 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 Next.js로 구성된 프로젝트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키를 다루기위해서 먼저 라이브러리를 설치해야한다. (&lt;a href=&quot;https://docs.nestjs.com/techniques/cookies&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 참고&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1718681356440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pnpm i cookie-parser
$ pnpm i -D @types/cookie-parser&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이후 main.ts에 쿠키 라이브러리를 적용한다. 나의 경우 CORS 에러가 발생하지않도록&amp;nbsp; CORS 관련된 설정도 함께 적용했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718681422185&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;app.use(cookieParser());

app.enableCors({
  origin: process.env.CORS_ORIGIN ? [process.env.CORS_ORIGIN] : [],
  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
  credentials: true, 
  allowedHeaders: 'Content-Type, Accept, Authorization',
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;refresh token은 서버에서 httpOnly 쿠키에 저장하므로 클라이언트에 보내지않아야한다. 따라서 응답 데이터에서 refresh token은 제외하고 access token을 보내도록 코드를 작성했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718680517773&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller('auth')
@UseInterceptors(ClassSerializerInterceptor)
export class AuthController {
    constructor(private authService: AuthService) {}
    
    @UseGuards(LocalAuthGuard)
    @Post('/signin')
    async login(
        @Request() _req: Req,
        @Body() signinUserDto: SigninUserDto,
        @Res({ passthrough: true }) res: Response,
    ) {
        const { user, accessToken } = await this.authService.signin(signinUserDto);

        res.cookie('refreshToken', user.refreshToken, {
            httpOnly: true,
            secure: true, // Ensure secure for HTTPS
            sameSite: 'strict', // Prevent CSRF attack
            maxAge: REFRESH_TOKEN_COOKIE_MAX_AGE, // 7 days
        });

        const copied: Omit&amp;lt;User, 'password' | 'refreshToken'&amp;gt; = { ...user };

        /**@description @Exclude() 항목이 인터셉터에서 제거되지않으므로 수동으로 제거  */
        if ('password' in copied &amp;amp;&amp;amp; 'refreshToken' in copied) {
            delete copied.password;
            delete copied.refreshToken;
        }

        return { ...copied, accessToken };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;httpOnly&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaScript로 접근할 수 없는 쿠키로, 클라이언트와 서버 간의 HTTP 요청을 통해서만 전송될 수 있다.&lt;/li&gt;
&lt;li&gt;클라이언트측 스크립트는 쿠키에 접근이 불가하므로, XSS(Cross-Site Scripting) 공격으로부터 쿠키를 보호하는 데 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;sameSite&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;strict: 쿠키는 동일한 사이트에서만 전송된다. 즉, 사용자가 사이트에서 탐색할 때만 쿠키가 전송된다. 다른 사이트에서 요청이 발생하면 쿠키는 전송되지 않는다.&lt;/li&gt;
&lt;li&gt;위와 같은 이유로 CSRF(Cross-Site Request Forgery) 공격을 방지하는 데 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;응답데이터에서 refresh token 제거하기&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 entity를 바로 반환하는 경우, Controller에 적용한 ClassSerializerInterceptor에 의해서 @Exclude() 필드가 정상적으로 제외된다.&lt;/p&gt;
&lt;pre id=&quot;code_1718680841116&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// user.entity.ts
@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    email: string;

    @Column()
    @Exclude()
    password: string;

    @Column()
    username: string;

    @Column({ type: 'varchar', nullable: true })
    @Exclude()
    refreshToken: string | null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 아래와 같이 바로 entity를 반환하지 않는 경우, @Exclude() 필드가 정상적으로 필터링되지 않았다. 따라서 수동으로 제외시켰다.&lt;/p&gt;
&lt;pre id=&quot;code_1718680961803&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @UseGuards(LocalAuthGuard)
    @Post('/signin')
    async login(
        @Request() _req: Req,
        @Body() signinUserDto: SigninUserDto,
        @Res({ passthrough: true }) res: Response,
    ) {
    	// Service에서 Entity를 바로 반환하지 않음
        const { user, accessToken } = await this.authService.signin(signinUserDto);
        ...
        const copied: Omit&amp;lt;User, 'password' | 'refreshToken'&amp;gt; = { ...user };

        /**@description @Exclude() 항목이 인터셉터에서 제거되지않으므로 수동으로 제거  */
        if ('password' in copied &amp;amp;&amp;amp; 'refreshToken' in copied) {
            delete copied.password;
            delete copied.refreshToken;
        }

        return { ...copied, accessToken };
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;클라이언트 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 프로젝트 기준 axios의 인터셉터를 이용해서 인증 로직을 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;axios/axios.config.ts&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios의 요청, 응답 인터셉터를 다루는 로직을 별도의 모듈로 분리했다.&lt;/p&gt;
&lt;pre id=&quot;code_1718681135255&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// axios.config.ts

export const createBaseAPI = (
  baseUrl: string,
  options: AxiosRequestConfig = {}
) =&amp;gt; {
  return axios.create({
    baseURL: baseUrl,
    timeout: REQUEST_TIMEOUT,
    withCredentials: true,
    headers: {
      &quot;Content-Type&quot;: &quot;application/json&quot;,
    },
    ...options,
  });
};

/**
 * @description 요청 인터셉터, 액세스토큰을 헤더에 추가합니다.
 */
export const addRequestInterceptors = (
  instance: ReturnType&amp;lt;typeof axios.create&amp;gt;
) =&amp;gt; {
  instance.interceptors.request.use(
    (config) =&amp;gt; {
      const accessToken = localStorage.getItem(ACCESS_TOKEN_KEY);
      if (accessToken) {
        config.headers[&quot;Authorization&quot;] = `Bearer ${accessToken}`;
      }
      return config;
    },
    (error) =&amp;gt; Promise.reject(error)
  );
};

/**
 * @description
 * - 응답 인터셉터, 액세스토큰이 만료되었을 경우, 리프레시 토큰을 사용하여 재발급합니다.
 * - 에러 발생시 에러 로그를 출력합니다.
 */
export const addResponseInterceptors = (
  instance: ReturnType&amp;lt;typeof axios.create&amp;gt;
) =&amp;gt; {
  instance.interceptors.response.use(
    (response) =&amp;gt; {
      const accessToken = response.data.data[ACCESS_TOKEN_KEY];
      if (accessToken) {
        localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
      }
      return response;
    },
    async (error) =&amp;gt; {
      if (config.nodeEnv === &quot;development&quot;) {
        // 에러 로깅
        errorMessageLogger(error);
      }

      // 액세스토큰 만료시 리프레시 토큰 재발급
      const originalRequest = error.config as AxiosRequestConfig &amp;amp; {
        _retry?: boolean;
      };

      if (error?.response?.status === 401 &amp;amp;&amp;amp; !originalRequest?._retry) {
        originalRequest._retry = true;
        try {
          const { data } = await instance.get(&quot;/auth/refresh&quot;);
          const accessToken = data[ACCESS_TOKEN_KEY];

          localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
          axios.defaults.headers.common[
            &quot;Authorization&quot;
          ] = `Bearer ${accessToken}`;

          return instance(originalRequest);
        } catch (refreshError) {
          return Promise.reject(refreshError);
        }
      }

      return Promise.reject(error);
    }
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;axios/baseInstance.ts&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718681224599&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @description 일반적인 API 요청시 사용하는 axios 인스턴스입니다.
 */
const baseInstance = createBaseAPI(APP_SERVER_BASE_URL);

addRequestInterceptors(baseInstance);
addResponseInterceptors(baseInstance);

export default baseInstance;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스를 사용하는 곳에서는 다음과 같이 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1718681282933&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const handleSubmit = async (event: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
    try {
      const response = await signin(form);
      const { data } = response;

      if (data.statusCode === 201) {
        handleResetForm();
        router.push(&quot;/&quot;);
      } else {
        console.error(data.message);
      }
    } catch (error) {
      /**@todo 에러 메세지 출력 */
    }
  }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍 &amp;zwj; /기타</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/114</guid>
      <comments>https://suyeonme.tistory.com/114#entry114comment</comments>
      <pubDate>Tue, 18 Jun 2024 12:32:22 +0900</pubDate>
    </item>
    <item>
      <title>[Git] 모노레포 환경에서 Husky로 Git Hook 설정하기(Commit전 console.log 제거)</title>
      <link>https://suyeonme.tistory.com/113</link>
      <description>&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Commit 대상 파일에 console.log가 포함되어있는 경우, commit  막기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git flow 전략을 사용해서 브랜치를 관리하는 경우, master 브랜치는 곧 배포 브랜치입니다. 따라서 브랜치에 작업물을 Commit하는 경우, console이 포함되어있다면 Github Hook을 이용하여 commit을 못하도록 막을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보편적으로 운영환경에서는 console을 제거합니다. 이유는 다음과 같습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;console에는 민감정보를 포함할 수 있습니다.&lt;/li&gt;
&lt;li&gt;console로 번들사이즈가 늘어납니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; 스크립트 작성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(1) 스크립트 파일 생성 및 작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우, script/check-console.sh 파일을 생성했습니다. 해당 스크립트는 husky가 설치된 루트에 위치시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 기본적으로 메세지를 띄우는 스크립트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715846710406&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env sh

# 커밋 대상 파일에 console.log가 포함되어있는지 확인
if git diff --cached --name-only --diff-filter=ACMRT | xargs grep -i 'console\.log' --with-filename --line-number; then
  echo &quot;COMMIT REJECTED!  Please remove console.&quot;
  exit 1
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트에 색상을 입히고 싶은 경우, 다음과 같이 스크립트를 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715846912835&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env sh

red='\033[0;31m'
green='\033[0;32m'
yellow='\033[0;33m'
no_color='\033[0m'

# 커밋 대상 파일에 console.log가 포함되어있는지 확인
if git diff --cached --name-only | xargs grep -i 'console\.log' --with-filename --line-number;
then
    echo -e &quot;\n${red}COMMIT REJECTED!  Please remove console.&quot;
    exit 1;
fi
echo -e &quot;${green}SUCCESS! No console is found.&quot;
exit 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(2) pre-commit 파일에 스크립트 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.husky/pre-commit 파일에 작성한 스크립트를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715846986046&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env sh

# Check console.log is included in staged files
sh scripts/check-console.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우, 모노레포로 구성된 프로젝트라서 아래와 같이 작성해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715846954015&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env sh

# 현재 스크립트 파일이 위치한 디렉토리를 기준으로 .husky.sh 파일을 실행
. &quot;$(dirname -- &quot;$0&quot;)/_/husky.sh&quot;

cd packages/backend

# Check console.log is included in staged files
sh scripts/check-console.sh&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 테스트&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드에 console.log 추가 후, commit을 시도합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;COMMIT REJECTED! 부분은 소스코드에 console을 추가한 경우입니다.&lt;/li&gt;
&lt;li&gt;SUCCESS! 부분은 소스코드에 console이 포함되지않은 경우입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-05-16 at 3.15.48 PM.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gcn1z/btsHrxPtR7h/xsMGFThk5BdxA3GTDUn0A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gcn1z/btsHrxPtR7h/xsMGFThk5BdxA3GTDUn0A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gcn1z/btsHrxPtR7h/xsMGFThk5BdxA3GTDUn0A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGcn1z%2FbtsHrxPtR7h%2FxsMGFThk5BdxA3GTDUn0A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;178&quot; data-filename=&quot;Screenshot 2024-05-16 at 3.15.48 PM.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>프로그래밍 &amp;zwj; /Git</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/113</guid>
      <comments>https://suyeonme.tistory.com/113#entry113comment</comments>
      <pubDate>Thu, 16 May 2024 17:12:46 +0900</pubDate>
    </item>
    <item>
      <title>[Git] 모노레포 환경에서 Husky로 Git Hook 설정하기(commitlint)</title>
      <link>https://suyeonme.tistory.com/112</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;commitlint로 Commit 컨벤션 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모가 큰 팀에서 협업시, 커밋 컨벤션을 따르면 커밋의 일관성을 지킬 수 있다. 대표적으로 다음과 같은 커밋 컨벤션을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715835366334&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;feat(AppController): Format response
feat: Format response&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://commitlint.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;lintcommit&lt;/a&gt;을 이용해서 작성한 commit이 컨벤션을 지켰는지 검사해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 라이브러리 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715835683623&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# husky 설치
pnpm add --save-dev husky
pnpm exec husky init

# commitlint 패키지 다운로드
pnpm install --save-dev @commitlint/config-conventional @commitlint/cli

# commitlint.config.js 파일 생성
echo &quot;export default { extends: ['@commitlint/config-conventional'] };&quot; &amp;gt; commitlint.config.js&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commitlint.config.js에서 &lt;b&gt;SyntaxError: Unexpected token 'export'&lt;/b&gt;에러가 나는경우, 아래와 같이 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715836051201&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 수정 전
export default { extends: ['@commitlint/config-conventional'] };

# 수정 후
module.exports = { extends: [&quot;@commitlint/config-conventional&quot;] };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) commit-msg 훅 스크립트 작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래 명령어는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;.husky/commit-msg 파일을 추가한 뒤, commitlint 패키지를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715835715290&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# commit-msg 훅 스크립트 작성
echo &quot;pnpm dlx commitlint --edit \$1&quot; &amp;gt; .husky/commit-msg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3)  작성한 훅이 정상적으로 동작하는지 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어는 최근 두 커밋 메세지를 검사한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715835965867&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pnpm exec commitlint --from HEAD~1 --to HEAD --verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 작성한 커밋이 있다면 해당 커밋이 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-05-16 at 12.13.23 PM.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lOSK7/btsHr1CcNVP/teIEQBiuV10RGaRv7gb3sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lOSK7/btsHr1CcNVP/teIEQBiuV10RGaRv7gb3sK/img.png&quot; data-alt=&quot;최근 커밋 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lOSK7/btsHr1CcNVP/teIEQBiuV10RGaRv7gb3sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlOSK7%2FbtsHr1CcNVP%2FteIEQBiuV10RGaRv7gb3sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;116&quot; data-filename=&quot;Screenshot 2024-05-16 at 12.13.23 PM.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최근 커밋 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 변경 사항을 add한 뒤, 커밋 메세지 작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨벤션에 맞지않는 커밋을 작성한 경우&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-05-16 at 12.47.24 PM.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL9Nu1/btsHr1Cc3Kn/HykSUpyuBzJNij9v7IWR01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL9Nu1/btsHr1Cc3Kn/HykSUpyuBzJNij9v7IWR01/img.png&quot; data-alt=&quot;Commit 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL9Nu1/btsHr1Cc3Kn/HykSUpyuBzJNij9v7IWR01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL9Nu1%2FbtsHr1Cc3Kn%2FHykSUpyuBzJNij9v7IWR01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;225&quot; data-filename=&quot;Screenshot 2024-05-16 at 12.47.24 PM.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Commit 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨벤션에 맞는 커밋을 작성한 경우&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-05-16 at 1.08.56 PM.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SLB3M/btsHq6qIilt/kFTinTHnwQhHHzGWPeBGG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SLB3M/btsHq6qIilt/kFTinTHnwQhHHzGWPeBGG0/img.png&quot; data-alt=&quot;Commit 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SLB3M/btsHq6qIilt/kFTinTHnwQhHHzGWPeBGG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSLB3M%2FbtsHq6qIilt%2FkFTinTHnwQhHHzGWPeBGG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;72&quot; data-filename=&quot;Screenshot 2024-05-16 at 1.08.56 PM.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Commit 성공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;모노레포에서 적용하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우, 아래의 디렉터리 구조를 가진 모노레포에 적용하였다. commitlint.config.js는 husky가 설치된 디렉터리에 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 전체 워크스페이스에 Git hook을 적용해야한다면 루트에 husky를 적용해야한다. 이 경우 &lt;a href=&quot;https://www.npmjs.com/package/@commitlint/config-pnpm-scopes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;config-pnpm-scopes&lt;/a&gt;를 참고해볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715836304557&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
├── packages/
│   ├── frontend
│   └── backend/
│       ├── src
│       ├── .eslintrc.js
│       ├── .prettierrc
│       ├── .commitlint.config.js 
│       └── package.json &amp;lt;--- Husky 설치!
├── package.json
├── pnpm-workspace.yaml
└── pnpm-workspace.lock.yaml
└── .git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 commit-msg를 적용한 뒤, 커밋을 시도하자 &lt;b&gt;Please add rules to your commitlint.config.js&lt;/b&gt; 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-05-16 at 12.36.36 PM.png&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JrntX/btsHqv5BNl0/b2hpY54wTp3zu81DsMVp10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JrntX/btsHqv5BNl0/b2hpY54wTp3zu81DsMVp10/img.png&quot; data-alt=&quot;commitlint 에러 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JrntX/btsHqv5BNl0/b2hpY54wTp3zu81DsMVp10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJrntX%2FbtsHqv5BNl0%2Fb2hpY54wTp3zu81DsMVp10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;208&quot; data-filename=&quot;Screenshot 2024-05-16 at 12.36.36 PM.png&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;commitlint 에러 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우, commit-msg 파일에서 git이 설치된 경로를 잡아주면 정상적으로 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715836552059&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env sh

. &quot;$(dirname -- &quot;$0&quot;)/_/husky.sh&quot;

cd packages/backend # 추가!

pnpm dlx commitlint --edit $1&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍 &amp;zwj; /Git</category>
      <author>suyeonme</author>
      <guid isPermaLink="true">https://suyeonme.tistory.com/112</guid>
      <comments>https://suyeonme.tistory.com/112#entry112comment</comments>
      <pubDate>Thu, 16 May 2024 14:18:52 +0900</pubDate>
    </item>
  </channel>
</rss>