<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>우노</title>
    <link>https://wooono.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 11 Jul 2026 11:41:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>운호(Noah)</managingEditor>
    <image>
      <title>우노</title>
      <url>https://tistory1.daumcdn.net/tistory/4029591/attach/8e9fc5e6189449629bacae69bd708b23</url>
      <link>https://wooono.tistory.com</link>
    </image>
    <item>
      <title>[K8S] Label, Taint, Toleration 예제</title>
      <link>https://wooono.tistory.com/800</link>
      <description>&lt;article class=&quot;post-body&quot;&gt;
  &lt;style&gt;
    .post-body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Apple SD Gothic Neo, Noto Sans KR, sans-serif; line-height: 1.65; color: #111; }
    .post-body h1, .post-body h2, .post-body h3 { line-height: 1.35; }
    .post-body h1 { font-size: 1.9rem; margin: 1.2em 0 .6em; }
    .post-body h2 { font-size: 1.4rem; margin: 1.4em 0 .6em; }
    .post-body h3 { font-size: 1.15rem; margin: 1.2em 0 .4em; }
    .post-body p { margin: .6em 0; }
    .post-body blockquote { margin: 1em 0; padding: .8em 1em; background:#f8fafc; border-left: 4px solid #94a3b8; color:#334155; }
    .post-body hr { border: 0; height: 1px; background: #e5e7eb; margin: 1.6em 0; }
    .post-body table { width: 100%; border-collapse: collapse; margin: .8em 0 1.2em; font-size: .95rem; }
    .post-body thead th { background:#f1f5f9; }
    .post-body th, .post-body td { border:1px solid #e5e7eb; padding:.6em .7em; text-align:left; vertical-align: top; }
    .post-body code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace; background:#f6f8fa; padding:.15em .35em; border-radius:.3em; }
    .post-body pre { background:#0b1020; color:#e5e7eb; padding:1em; border-radius:.6em; overflow:auto; }
    .post-body pre code { background: transparent; padding:0; }
    .node-good { color:#16a34a; font-weight:600; }
    .node-bad { color:#dc2626; font-weight:600; }
    .tip { background:#fef9c3; border:1px solid #fde68a; padding:.75em 1em; border-radius:.5em; }
  &lt;/style&gt;

  &lt;h1&gt;Kubernetes - Label / Taint / Toleration 관계 정리&lt;/h1&gt;
  &lt;p&gt;
    이 글에서는 &lt;code&gt;Label&lt;/code&gt;, &lt;code&gt;nodeSelector&lt;/code&gt;, &lt;code&gt;Taint&lt;/code&gt;, &lt;code&gt;Toleration&lt;/code&gt;의 관계와
    스케줄링 과정이 어떻게 흘러가는지를 구체적인 예시로 정리했습니다.
  &lt;/p&gt;

  &lt;hr&gt;

  &lt;h2&gt;Label, Taint, Toleration 관계&lt;/h2&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;개념&lt;/th&gt;
        &lt;th&gt;설정 대상&lt;/th&gt;
        &lt;th&gt;역할&lt;/th&gt;
        &lt;th&gt;예시&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Label&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;노드 or 파드&lt;/td&gt;
        &lt;td&gt;“나는 이런 속성을 가지고 있다”&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;nodepool=variant&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;nodeSelector&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;파드&lt;/td&gt;
        &lt;td&gt;파드가 “어떤 라벨 가진 노드로 가고 싶은지” 지정&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;nodeSelector: { nodepool: variant }&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Taint&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;노드&lt;/td&gt;
        &lt;td&gt;노드가 “아무나 오지 마라”는 제한&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;variant=required:NoSchedule&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Toleration&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;파드&lt;/td&gt;
        &lt;td&gt;파드가 “그 제한을 견딜 수 있다”는 허가&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;key=variant, value=required, effect=NoSchedule&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;hr&gt;

  &lt;h2&gt;노드 스케줄링 판단 흐름&lt;/h2&gt;
  &lt;blockquote&gt;
    nodeSelector 일치 여부 확인 → taint/toleration 확인 → 배치
  &lt;/blockquote&gt;

  &lt;hr&gt;

  &lt;h2&gt;클러스터 구성 예시&lt;/h2&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;노드 이름&lt;/th&gt;
        &lt;th&gt;라벨(Label)&lt;/th&gt;
        &lt;th&gt;Taint&lt;/th&gt;
        &lt;th&gt;비고&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  &lt;strong&gt;node-a&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;nodepool=default&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;(없음)&lt;/td&gt;
        &lt;td&gt;일반 노드&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  &lt;strong&gt;node-b&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;nodepool=variant&lt;/code&gt;, &lt;code&gt;gpu=true&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;variant=required:NoSchedule&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;GPU 노드 (taint 있음)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  &lt;strong&gt;node-c&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;nodepool=debug&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code&gt;debug=true:NoSchedule&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;디버깅 전용 노드&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;hr&gt;

  &lt;h2&gt;  동작 예시&lt;/h2&gt;

  &lt;h3&gt;예시 1. 아무 제약이 없는 기본 파드&lt;/h3&gt;
  &lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spec:
  containers:
  - name: web
    image: nginx
&lt;/code&gt;&lt;/pre&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;가능 노드&lt;/th&gt;
        &lt;th&gt;이유&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-a&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - Pod에 nodeSelector가 없다면, 스케줄러는 라벨 조건은 신경 쓰지 않고, 스케줄 가능한(taint에 막히지 않은) 노드 중 하나를 선택함&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-b&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node taint에 해당하는 Pod toleration이 없음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-c&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node taint에 해당하는 Pod toleration이 없음&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
  &lt;hr&gt;

  &lt;h3&gt;예시 2. nodeSelector로 GPU 노드만 지정&lt;/h3&gt;
  &lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spec:
  nodeSelector:
    gpu: &quot;true&quot;
  containers:
  - name: trainer
    image: ml/trainer:latest
&lt;/code&gt;&lt;/pre&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;가능 노드&lt;/th&gt;
        &lt;th&gt;이유&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-a&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨이 동일하지 않음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-b&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨은 맞지만, Node taint에 해당하는 Pod toleration이 없음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-c&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨이 동일하지 않음&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;blockquote&gt;
    Node와 Pod의 라벨이 일치하더라도, 그 Node에 taint가 걸려 있다면,
    Pod에 해당 taint를 허용하는 toleration이 필요합니다.
  &lt;/blockquote&gt;

  &lt;hr&gt;

  &lt;h3&gt;예시 3. nodeSelector + toleration 둘 다 있음 (정상 조합)&lt;/h3&gt;
  &lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spec:
  nodeSelector:
    gpu: &quot;true&quot;
  tolerations:
    - key: &quot;variant&quot;
      operator: &quot;Equal&quot;
      value: &quot;required&quot;
      effect: &quot;NoSchedule&quot;
  containers:
  - name: trainer
    image: ml/trainer:latest
&lt;/code&gt;&lt;/pre&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;가능 노드&lt;/th&gt;
        &lt;th&gt;이유&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-a&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨이 동일하지 않음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-b&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - Label, Taint, Toleration이 모두 일치&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-c&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨이 동일하지 않음&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;hr&gt;

  &lt;h3&gt;예시 4. toleration만 있고 nodeSelector가 없음&lt;/h3&gt;
  &lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spec:
  tolerations:
    - key: &quot;debug&quot;
      operator: &quot;Equal&quot;
      value: &quot;true&quot;
      effect: &quot;NoSchedule&quot;
  containers:
  - name: debug-agent
    image: internal/debug-agent:latest
&lt;/code&gt;&lt;/pre&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;가능 노드&lt;/th&gt;
        &lt;th&gt;이유&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-a&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - Node에 선언되어있는 taint가 없음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-b&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Taint, Toleration 불일치&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-c&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - Taint, Toleration이 일치&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;blockquote&gt;Pod에 Label이 없으면, toleration이 있거나 taint 없는 노드에 배치됩니다.&lt;/blockquote&gt;

  &lt;hr&gt;

  &lt;h3&gt;예시 5. debug 노드 전용 파드&lt;/h3&gt;
  &lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spec:
  nodeSelector:
    nodepool: debug
  tolerations:
    - key: &quot;debug&quot;
      operator: &quot;Equal&quot;
      value: &quot;true&quot;
      effect: &quot;NoSchedule&quot;
  containers:
  - name: debug-agent
    image: internal/debug-agent:latest
&lt;/code&gt;&lt;/pre&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;가능 노드&lt;/th&gt;
        &lt;th&gt;이유&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-a&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨이 동일하지 않음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-b&lt;/td&gt;
        &lt;td class=&quot;node-bad&quot;&gt;❌(불가능) - Node와 Pod의 라벨이 동일하지 않음&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-c&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - Label, Taint, Toleration이 모두 일치&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;hr&gt;

  &lt;h3&gt;예시 6. “나는 어디든 가고 싶다” (모든 taint 허용)&lt;/h3&gt;
  &lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spec:
  tolerations:
    - operator: &quot;Exists&quot;      # 모든 taint 허용
  containers:
  - name: agent
    image: global/agent:latest
&lt;/code&gt;&lt;/pre&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;가능 노드&lt;/th&gt;
        &lt;th&gt;이유&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-a&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-b&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - 모든 taint 허용&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;  node-c&lt;/td&gt;
        &lt;td class=&quot;node-good&quot;&gt;✅(가능) - 모든 taint 허용&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;blockquote&gt;
    &lt;code&gt;operator: Exists&lt;/code&gt;는 “모든 taint를 무시한다”는 의미입니다.
    주로 시스템 데몬(예: logging, monitoring agent)에서 자주 사용됩니다.
  &lt;/blockquote&gt;

  &lt;hr&gt;

  &lt;h2&gt;  정리&lt;/h2&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Label / nodeSelector&lt;/strong&gt;: “가고 싶은 곳”을 지정&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Taint / Toleration&lt;/strong&gt;: “들어올 수 있는가”를 결정&lt;/li&gt;
    &lt;li&gt;스케줄링 순서:
      &lt;ol&gt;
        &lt;li&gt;nodeSelector 일치 여부&lt;/li&gt;
        &lt;li&gt;taint/toleration 허용 여부&lt;/li&gt;
      &lt;/ol&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  
&lt;/article&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/800</guid>
      <comments>https://wooono.tistory.com/800#entry800comment</comments>
      <pubDate>Tue, 11 Nov 2025 14:59:37 +0900</pubDate>
    </item>
    <item>
      <title>[Network] L4 로드 밸런서 vs L7 로드 밸런서</title>
      <link>https://wooono.tistory.com/799</link>
      <description>&lt;h3&gt;선요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L4 로드 밸런서는 빠르고 간단한 트래픽 분배가 필요한 경우에 적합합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;성능이 중요하고, 복잡한 애플리케이션 로직이 필요 없는 경우 유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L7 로드 밸런서는 애플리케이션 로직에 맞춰 세분화된 트래픽 분배가 필요한 경우에 적합합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;고급 기능이 필요하거나 세션 유지와 동적 라우팅이 중요한 애플리케이션에서 유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;로드 밸런서는 네트워크 트래픽을 여러 서버나 리소스로 분산시켜서 시스템의 부하를 효율적으로 처리하고, 가용성을 높이는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;로드 밸런서에는 L4 (Layer 4) 로드 밸런서와 L7 (Layer 7) 로드 밸런서 두 가지 주요 유형이 있습니다.&lt;/li&gt;
&lt;li&gt;이들은 OSI 모델의 서로 다른 계층에서 동작하며, 각기 다른 용도와 특성을 가지고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;L4 로드 밸런서 (Layer 4 Load Balancer)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;개념&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;L4 로드 밸런서는 OSI 모델의 &lt;strong&gt;전송 계층 (Transport Layer)&lt;/strong&gt;인 4계층에서 동작합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP/IP&lt;/strong&gt; 또는 &lt;strong&gt;UDP&lt;/strong&gt; 프로토콜을 기반으로 하여 클라이언트의 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;L4 로드 밸런서는 요청의 &lt;strong&gt;IP 주소&lt;/strong&gt;와 &lt;strong&gt;포트 번호&lt;/strong&gt;를 사용하여 트래픽을 분산합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;L4 로드 밸런서는 주로 &lt;strong&gt;IP 주소&lt;/strong&gt;와 &lt;strong&gt;포트 번호&lt;/strong&gt;를 기반으로 트래픽을 분산시킵니다.&lt;/li&gt;
&lt;li&gt;HTTP, HTTPS, FTP와 같은 &lt;strong&gt;애플리케이션 프로토콜&lt;/strong&gt;이 아닌 &lt;strong&gt;저수준의 전송 프로토콜&lt;/strong&gt;을 다룹니다.&lt;/li&gt;
&lt;li&gt;로드 밸런서는 &lt;strong&gt;패킷 헤더 정보&lt;/strong&gt;를 바탕으로 트래픽을 전달하므로 요청의 내용을 알지 못합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;속도와 성능&lt;/strong&gt;이 뛰어나며, 높은 트래픽을 빠르게 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;복잡한 애플리케이션 프로세스를 알지 못하므로 &lt;strong&gt;시스템 자원 소비가 적습니다&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;낮은 레벨에서 동작하기 때문에 &lt;strong&gt;단순한 구현&lt;/strong&gt;과 &lt;strong&gt;빠른 트래픽 처리&lt;/strong&gt;가 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;요청의 &lt;strong&gt;세부 정보를 알지 못하고&lt;/strong&gt;, 단지 네트워크 정보를 기반으로 분배하므로, 특정한 로직에 따른 세분화된 라우팅이 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;애플리케이션 계층에 대한 세부 정보&lt;/strong&gt;를 고려하지 않기 때문에 &lt;strong&gt;복잡한 라우팅 전략이나 세션 유지&lt;/strong&gt;가 필요한 경우에는 적합하지 않을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;L7 로드 밸런서 (Layer 7 Load Balancer)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;개념&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;L7 로드 밸런서는 OSI 모델의 &lt;strong&gt;애플리케이션 계층 (Layer 7)&lt;/strong&gt;에서 동작합니다.&lt;/li&gt;
&lt;li&gt;HTTP, HTTPS, WebSocket, FTP, SMTP와 같은 &lt;strong&gt;애플리케이션 프로토콜&lt;/strong&gt;에 대한 트래픽을 분석하고 처리합니다.&lt;/li&gt;
&lt;li&gt;L7 로드 밸런서는 &lt;strong&gt;HTTP 헤더&lt;/strong&gt;, &lt;strong&gt;쿠키&lt;/strong&gt;, &lt;strong&gt;URI 경로&lt;/strong&gt; 등을 분석하여 요청을 처리하고, 요청에 맞는 서버로 트래픽을 분배합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;애플리케이션 레벨&lt;/strong&gt;에서 트래픽을 분배하며, HTTP 요청의 세부 내용까지 파악할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동적 라우팅&lt;/strong&gt;과 &lt;strong&gt;세분화된 정책&lt;/strong&gt;을 설정할 수 있습니다. 예를 들어, URL 경로에 따라 서로 다른 서버로 트래픽을 전달하는 등의 처리가 가능합니다.&lt;/li&gt;
&lt;li&gt;SSL 종료, 리다이렉트, 리버스 프록시 등 &lt;strong&gt;고급 기능&lt;/strong&gt;을 제공할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;세분화된 라우팅&lt;/strong&gt;과 &lt;strong&gt;애플리케이션 계층의 동적 제어&lt;/strong&gt;가 가능하여 복잡한 애플리케이션 로직을 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션 유지&lt;/strong&gt; 및 &lt;strong&gt;상태 정보 기반 라우팅&lt;/strong&gt;이 가능하며, 트래픽의 세부 내용을 기반으로 한 최적화가 가능합니다.&lt;/li&gt;
&lt;li&gt;애플리케이션 레벨에서 &lt;strong&gt;SSL 종료&lt;/strong&gt;, &lt;strong&gt;URL 기반 라우팅&lt;/strong&gt;, &lt;strong&gt;IP 주소 기반 라우팅&lt;/strong&gt; 등 다양한 기능을 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;L4보다 &lt;strong&gt;성능이 떨어질 수 있습니다&lt;/strong&gt;.&lt;ul&gt;
&lt;li&gt;애플리케이션 프로토콜을 분석하고 처리해야 하므로, 더 많은 시스템 리소스를 사용하고 더 높은 지연을 발생시킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구현이 더 &lt;strong&gt;복잡&lt;/strong&gt;하며, 자원 소비가 많을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;L4와 L7 로드 밸런서를 선택하는 기준&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L4 로드 밸런서 사용 사례&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;단순한 트래픽 분배&lt;/strong&gt;: 트래픽이 단순히 네트워크 계층에서 분배되고, 애플리케이션의 복잡한 로직을 처리할 필요가 없는 경우.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고성능 및 빠른 처리&lt;/strong&gt;: 빠른 응답 시간이 필요한 애플리케이션이나 &lt;strong&gt;고속 트래픽 처리&lt;/strong&gt;가 중요한 경우.&lt;ul&gt;
&lt;li&gt;예를 들어, 게임 서버, 비디오 스트리밍 등에서는 L4 로드 밸런서를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP/UDP 기반 서비스&lt;/strong&gt;: HTTP 외의 서비스(FTP, DNS 등)를 로드 밸런싱하는 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L7 로드 밸런서 사용 사례&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;애플리케이션 세부 제어&lt;/strong&gt;: URL, 헤더, 쿠키, 파라미터 등에 따라 &lt;strong&gt;정밀한 라우팅&lt;/strong&gt;이 필요한 경우.&lt;ul&gt;
&lt;li&gt;예를 들어, 웹 애플리케이션에서 &lt;code&gt;/images/*&lt;/code&gt; 요청은 특정 서버로, &lt;code&gt;/api/*&lt;/code&gt; 요청은 다른 서버로 보내는 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션 유지를 위한 로드 밸런싱&lt;/strong&gt;: 사용자의 상태를 유지하며 같은 서버로 계속 요청을 보내는 경우.&lt;ul&gt;
&lt;li&gt;예를 들어, 로그인 상태를 유지해야 하는 웹 애플리케이션.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL 종료&lt;/strong&gt;: HTTPS 트래픽을 로드 밸런서에서 종료하고, 이후에는 HTTP로 전달하는 기능을 제공하는 경우.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;복잡한 애플리케이션 요구 사항&lt;/strong&gt;: 트래픽을 기반으로 동적으로 처리해야 하는 경우 (예: 리버스 프록시, 인증 처리 등).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;L4와 L7 로드 밸런서의 결합&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;하이브리드 방식&lt;/strong&gt;으로 L4와 L7 로드 밸런서를 &lt;strong&gt;조합&lt;/strong&gt;하여 사용하는 것도 가능합니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 트래픽의 대다수는 L4 로드 밸런서를 통해 빠르게 처리하고, 특정 요청은 L7 로드 밸런서를 사용하여 세부 로직을 처리하는 방식입니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Network &amp;amp; Security/Concept</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/799</guid>
      <comments>https://wooono.tistory.com/799#entry799comment</comments>
      <pubDate>Tue, 7 Jan 2025 16:08:12 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] Bulk Insert</title>
      <link>https://wooono.tistory.com/798</link>
      <description>&lt;h3&gt;Bulk Insert란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bulk Insert&lt;/strong&gt;는 데이터베이스에 여러 개의 데이터를 한 번에 삽입하는 기법으로,&lt;/li&gt;
&lt;li&gt;여러 번의 &lt;strong&gt;Single Insert&lt;/strong&gt; 대신 한 번에 여러 레코드를 삽입하여 속도와 효율성을 개선할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Single Insert vs Bulk Insert&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single Insert&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;데이터를 하나씩 삽입하는 방식입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;  INSERT INTO table1 (col1, col2) VALUES (val11, val12);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;여러 번 반복되는 경우&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;  INSERT INTO table1 (col1, col2) VALUES (val11, val12);
  INSERT INTO table1 (col1, col2) VALUES (val21, val22);
  INSERT INTO table1 (col1, col2) VALUES (val31, val32);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bulk Insert&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;여러 개의 데이터를 한 번에 삽입하는 방식입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;  INSERT INTO table1 (col1, col2) VALUES
  (val11, val12),
  (val21, val22),
  (val31, val32);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bulk Insert의 장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;속도 개선&lt;/strong&gt;: 한 번의 네트워크 호출로 여러 개의 데이터를 삽입하여 성능이 향상됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리소스 절약&lt;/strong&gt;: 네트워크 호출 횟수와 데이터베이스 연결을 줄여 시스템 자원을 절약할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트랜잭션 관리&lt;/strong&gt;: 여러 데이터를 하나의 트랜잭션으로 처리하여 일관성을 유지하고, 오류 발생 시 전체 작업을 롤백할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JPA와 JDBC에서 Bulk Insert 처리 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JPA의 saveAll() 사용하기&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JPA는 saveAll() 메서드를 통해 여러 데이터를 한 번에 저장할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;하지만 내부적으로 반복적으로 save()를 호출하여 데이터를 개별적으로 처리하므로 진정한 Bulk Insert가 아님&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;save() : N개의 트랜잭션과 N개의 쿼리&lt;/li&gt;
&lt;li&gt;saveAll() : 1개의 트랜잭션과 N개의 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;문제점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hibernate의 쓰기 지연(Write-Behind) 전략&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;데이터는 영속성 컨텍스트에 저장되고 트랜잭션이 커밋될 때 INSERT 쿼리가 실행됩니다.&lt;/li&gt;
&lt;li&gt;하지만 IDENTITY 전략을 사용하면, 각 엔티티의 ID를 알기 위해 매번 INSERT가 실행되어 Batch Insert가 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;영속성 컨텍스트의 메모리 문제&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;많은 데이터를 한 번에 저장하면 메모리 사용량이 증가하고 성능 저하가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성능 문제&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;JPA의 saveAll()은 개별 쿼리를 실행하므로 네트워크 호출이 많아지고 속도가 느려질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ID 생성 전략 변경&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IDENTITY 대신 SEQUENCE 또는 TABLE 전략을 사용하여 ID 값을 미리 생성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Batch Insert 활성화&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hibernate 설정을 통해 Batch Insert를 활성화합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  spring.jpa.properties.hibernate.jdbc.batch_size=30
  spring.jpa.properties.hibernate.order_inserts=true
  spring.jpa.properties.hibernate.order_updates=true&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JDBC의 batchUpdate 사용하기&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JDBC는 데이터베이스에 최적화된 Batch Insert 기능을 제공합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JdbcTemplate의 batchUpdate() 메서드를 사용하면 여러 데이터를 효율적으로 삽입할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String sql = &amp;quot;INSERT INTO table1 (col1, col2) VALUES (?, ?)&amp;quot;;

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
  @Override
  public void setValues(PreparedStatement ps, int i) throws SQLException {
      ps.setString(1, dataList.get(i).getCol1());
      ps.setString(2, dataList.get(i).getCol2());
  }

  @Override
  public int getBatchSize() {
      return dataList.size();
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Native SQL을 사용한 Bulk Insert&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Spring Data JPA는 Native SQL을 지원합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이를 통해 직접 SQL 쿼리를 실행하여 효율적으로 Bulk Insert를 처리할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Query(value = &amp;quot;INSERT INTO table1 (col1, col2) VALUES :values&amp;quot;, nativeQuery = true)
  void bulkInsert(@Param(&amp;quot;values&amp;quot;) List&amp;lt;Object[]&amp;gt; values);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bulk Insert의 성능 비교&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;방법&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;10000건 처리 시간&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;JPA saveAll()&lt;/td&gt;
&lt;td&gt;느림 (~5초)&lt;/td&gt;
&lt;td&gt;코드 간결, 트랜잭션 관리 자동화&lt;/td&gt;
&lt;td&gt;개별 INSERT 쿼리 실행으로 성능 저하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDBC batchUpdate()&lt;/td&gt;
&lt;td&gt;빠름 (~1초)&lt;/td&gt;
&lt;td&gt;최적화된 성능, 네트워크 호출 감소&lt;/td&gt;
&lt;td&gt;직접 쿼리 작성 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native SQL&lt;/td&gt;
&lt;td&gt;빠름 (~1초)&lt;/td&gt;
&lt;td&gt;데이터베이스 기능 활용 가능&lt;/td&gt;
&lt;td&gt;유지보수가 어렵고 JPA의 장점 활용 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Bulk Insert 시 주의사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MySQL의 max_allowed_packet 설정&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;MySQL에서 한번에 보낼 수 있는 데이터 크기 제한이 max_allowed_packet 설정값을 넘지 않도록 주의해야 합니다.&lt;/li&gt;
&lt;li&gt;너무 큰 데이터를 한 번에 보내지 않도록 배치 크기를 조절하는 것이 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메모리 관리&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;대량 데이터를 처리할 경우 메모리 부족 문제가 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;배치 크기를 조정하여 메모리 사용량을 관리하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;롤백 비용&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;배치 처리 중 오류가 발생하면 전체 트랜잭션이 롤백되므로, 배치 크기를 적절히 조정하여 롤백 비용을 최소화해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bulk Insert&lt;/strong&gt;는 대량 데이터를 효율적으로 삽입할 수 있는 기법으로, &lt;strong&gt;성능 향상&lt;/strong&gt;과 &lt;strong&gt;리소스 절약&lt;/strong&gt;이 가능합니다.&lt;/li&gt;
&lt;li&gt;JPA를 사용할 때는 &lt;strong&gt;Hibernate Batch Insert&lt;/strong&gt; 설정을 활용하거나, &lt;strong&gt;JDBC batchUpdate&lt;/strong&gt;와 &lt;strong&gt;Native Query&lt;/strong&gt;를 적절히 활용하여 성능을 최적화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;데이터 크기, 메모리 사용량, 롤백 비용을 고려하여 배치 크기와 전략을 신중히 설계하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sabarada.tistory.com/195&quot;&gt;https://sabarada.tistory.com/195&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sabarada.tistory.com/220&quot;&gt;https://sabarada.tistory.com/220&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twosky.tistory.com/62&quot;&gt;https://twosky.tistory.com/62&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/798</guid>
      <comments>https://wooono.tistory.com/798#entry798comment</comments>
      <pubDate>Fri, 3 Jan 2025 15:22:07 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] @Transactional, Service vs Repository 어디에 적용할까?</title>
      <link>https://wooono.tistory.com/797</link>
      <description>&lt;h3&gt;&lt;strong&gt;Spring에서 @Transactional, 어디에 적용할까?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring에서 트랜잭션 관리를 위해 사용하는 @Transactional은 주로 Service 계층과 Repository 계층에 적용됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;두 계층에서의 사용법과 차이를 알아보고, 올바른 설계 방향을 살펴보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Service 계층에서 @Transactional&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service 계층은 비즈니스 로직을 처리하는 핵심 부분입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이 계층에 @Transactional을 사용하면 비즈니스 작업 단위 전체를 하나의 트랜잭션으로 묶을 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예제&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Service
  public class OrderService {
      @Transactional
      public void placeOrder(Order order) {
          orderRepository.save(order);
          paymentService.processPayment(order);
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;트랜잭션 경계 설정&lt;/strong&gt;: 비즈니스 로직 전체를 하나의 트랜잭션으로 묶어 관리 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예외 처리&lt;/strong&gt;: Service 계층에서 발생한 예외에 따라 트랜잭션을 자동으로 커밋하거나 롤백.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;책임 분리&lt;/strong&gt;: Repository는 데이터 접근만 담당하고, 트랜잭션 관리는 Service에서 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;언제 사용하면 좋을까?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;여러 Repository 호출이나 외부 API 호출을 조합한 복잡한 로직을 처리할 때.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비즈니스 작업 전체를 하나의 트랜잭션으로 묶어야 할 때.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Repository 계층에서 @Transactional&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository 계층은 데이터 접근 로직을 처리하는 곳입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이 계층에 @Transactional을 사용하면 특정 데이터 접근 작업을 트랜잭션 단위로 묶을 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예제&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Repository
  public class OrderRepository {
      @Transactional
      public void save(Order order) {
          entityManager.persist(order);
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;특정 데이터 접근 로직만 트랜잭션으로 묶고자 할 때 유용.&lt;/li&gt;
&lt;li&gt;단순한 데이터 작업에 적합.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러 Repository를 조합한 작업에서는 트랜잭션 경계가 불명확해질 수 있음.&lt;/li&gt;
&lt;li&gt;Service 계층에서 트랜잭션을 관리하지 않으면 코드가 비일관적이 될 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;언제 사용하면 좋을까?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service 계층이 필요 없을 정도로 간단한 데이터 작업만 처리할 때.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트랜잭션 범위를 좁게 설정하고 싶을 때.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Service vs Repository: 무엇을 선택해야 할까?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;둘 중 하나를 선택해야 한다면 Service 계층에서 트랜잭션을 관리하는 것이 일반적으로 더 권장되며, 이유는 다음과 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service 계층에서 트랜잭션 관리&lt;/strong&gt;: 비즈니스 로직의 경계를 명확히 설정할 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository는 데이터 접근만 담당&lt;/strong&gt;: 트랜잭션의 책임을 분리하고 역할을 명확히 할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예외 상황&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;간단한 애플리케이션이나 특정 Repository 메서드만 트랜잭션이 필요한 경우, Repository에 @Transactional을 사용하는 것도 괜찮습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Service와 Repository에 동시에 사용하면?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service와 Repository에 중복으로 @Transactional을 사용해도 문제가 되지는 않습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다만, Service 계층의 트랜잭션이 우선됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;즉, Service에서 시작된 트랜잭션에 Repository가 참여하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;또한, 중복 사용의 단점이 발생합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;불필요한 코드 중복 발생.&lt;/li&gt;
&lt;li&gt;트랜잭션 관리 책임이 명확하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;결론&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@Transactional은 어디에 적용하느냐에 따라 트랜잭션 관리 방식이 달라집니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service 계층&lt;/strong&gt;: 비즈니스 로직 전체를 트랜잭션 단위로 묶어야 할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository 계층&lt;/strong&gt;: 특정 데이터 접근 작업만 트랜잭션 단위로 묶을 때 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;추천 설계&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;대부분의 경우 Service 계층에 @Transactional을 사용하고,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository는 데이터 접근에 집중하도록 설계하는 것이 유지보수성과 확장성 면에서 유리합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/797</guid>
      <comments>https://wooono.tistory.com/797#entry797comment</comments>
      <pubDate>Thu, 26 Dec 2024 16:36:57 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] @Transactional</title>
      <link>https://wooono.tistory.com/796</link>
      <description>&lt;h3&gt;&lt;strong&gt;@Transactional 이란?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@Transactional은 Spring에서 트랜잭션 관리를 자동화해주는 강력한 애노테이션입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메서드나 클래스에 선언하면, 트랜잭션이 시작되고 작업 완료 시 자동으로 커밋하거나 예외 발생 시 롤백됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;1. 메서드 수준에서 사용&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;메서드가 실행되는 동안 트랜잭션이 시작됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  @Service
  class UserService(private val userRepository: UserRepository) {

      @Transactional
      fun createUser(user: User): User {
          userRepository.save(user) // 트랜잭션 내 작업
          return user // 트랜잭션 커밋
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;2. 클래스 수준에서 사용&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;클래스에 선언하면 해당 클래스의 모든 메서드에 트랜잭션이 적용됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  @Service
  @Transactional
  class UserService(private val userRepository: UserRepository) {

      fun createUser(user: User): User {
          userRepository.save(user)
          return user
      }

      fun updateUser(user: User): User {
          userRepository.save(user)
          return user
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;주요 옵션&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@Transactional은 다양한 옵션을 제공하여 트랜잭션 동작을 세밀하게 제어할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;1. 전파 방식 (Propagation)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;트랜잭션이 호출될 때, 기존 트랜잭션을 사용할지, 새로 생성할지 결정합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;REQUIRED: 기본값. 기존 트랜잭션이 있으면 사용하고, 없으면 새로 생성합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;REQUIRES_NEW: 항상 새 트랜잭션을 생성하고, 기존 트랜잭션은 일시 중단됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SUPPORTS: 기존 트랜잭션이 있으면 사용하고, 없으면 트랜잭션 없이 실행됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NOT_SUPPORTED: 트랜잭션 없이 실행하고, 기존 트랜잭션이 있으면 일시 중단됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MANDATORY: 기존 트랜잭션이 반드시 있어야 하며, 없으면 예외가 발생합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NEVER: 트랜잭션 없이 실행하고, 기존 트랜잭션이 있으면 예외가 발생합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NESTED: 중첩 트랜잭션을 생성하며, 독립적인 롤백이 가능합니다. (JDBC에서만 지원)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Transactional(propagation = Propagation.REQUIRES_NEW)
fun processPayment(payment: Payment) {
  // 항상 새로운 트랜잭션으로 실행
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;2. 격리 수준 (Isolation)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;동시에 실행되는 트랜잭션 간 데이터 일관성을 유지하는 방법을 설정합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DEFAULT: 데이터베이스의 기본 설정을 사용합니다. 일반적으로 READ_COMMITTED입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;READ_UNCOMMITTED: 커밋되지 않은 데이터도 읽을 수 있습니다. (Dirty Read 허용)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;READ_COMMITTED: 커밋된 데이터만 읽을 수 있습니다. (Dirty Read 방지)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;REPEATABLE_READ: 트랜잭션 동안 동일 데이터를 읽으면 항상 같은 값을 읽습니다. (Phantom Read 허용)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SERIALIZABLE: 가장 높은 격리 수준으로, 트랜잭션이 완전히 순차적으로 실행됩니다. (성능 저하 가능)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Transactional(isolation = Isolation.REPEATABLE_READ)
fun getAccountBalance(accountId: Long): BigDecimal {
  // 트랜잭션 내 동일 데이터의 일관성 유지
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;3. 읽기 전용 (ReadOnly)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터 조회만 수행하는 경우, 최적화를 위해 읽기 전용으로 설정합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  @Transactional(readOnly = true)
  fun getAllUsers(): List&amp;lt;User&amp;gt; {
      return userRepository.findAll() // 읽기 전용 작업
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;4. 타임아웃 (Timeout)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;트랜잭션이 특정 시간 내 완료되지 않으면 롤백합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  @Transactional(timeout = 5) // 5초 초과 시 롤백
  fun processLargeData() {
      // 오래 걸리는 작업
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;5. 롤백 조건 (RollbackFor, NoRollbackFor)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특정 예외에 대해 롤백 여부를 설정할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  @Transactional(rollbackFor = [CustomException::class])
  fun updateUser(user: User) {
      if (user.name.isEmpty()) {
          throw CustomException(&amp;quot;Invalid user name&amp;quot;) // 롤백 발생
      }
      userRepository.save(user)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;주의사항&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;같은 클래스 내 메서드 호출&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;@Transactional은 Spring 프록시로 작동하므로, 같은 클래스 내 메서드 호출 시 적용되지 않습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Transactional
fun methodA() {
  methodB() // 트랜잭션 적용되지 않음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Checked Exception&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기본적으로 &lt;strong&gt;Checked Exception&lt;/strong&gt;이 발생해도 트랜잭션은 롤백되지 않습니다.&lt;/li&gt;
&lt;li&gt;롤백을 원하면 rollbackFor를 명시적으로 설정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;요약&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;@Transactional은 데이터의 일관성과 안정성을 보장하며, 트랜잭션 관리를 쉽게 해줍니다.&lt;/li&gt;
&lt;li&gt;주요 옵션(전파 방식, 격리 수준, 읽기 전용 등)을 상황에 맞게 설정하면, &lt;strong&gt;성능과 유지보수성&lt;/strong&gt;을 동시에 확보할 수 있습니다.&lt;/li&gt;
&lt;li&gt;기본 동작을 이해하고 적절히 설정하여 애플리케이션의 데이터 무결성을 유지하세요.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/796</guid>
      <comments>https://wooono.tistory.com/796#entry796comment</comments>
      <pubDate>Mon, 23 Dec 2024 13:43:04 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] @Service vs @Component</title>
      <link>https://wooono.tistory.com/795</link>
      <description>&lt;h3&gt;@Service&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;의미&lt;/strong&gt;: 서비스 계층(비즈니스 로직을 처리하는 계층)을 나타내는 클래스에 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용 목적&lt;/strong&gt;: 애플리케이션의 &lt;strong&gt;핵심 비즈니스 로직&lt;/strong&gt;을 담당하는 클래스임을 명확히 하기 위해 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;비즈니스 규칙 및 트랜잭션 관리.&lt;/li&gt;
&lt;li&gt;다른 계층(@Repository, @Component)의 Bean을 사용하여 로직을 조합.&lt;/li&gt;
&lt;li&gt;데이터와 사용자 인터페이스 사이를 연결하는 가교 역할.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;@Component&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;의미&lt;/strong&gt;: 특정 계층에 속하지 않는 일반적인 클래스를 Spring Bean으로 등록할 때 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용 목적&lt;/strong&gt;: &lt;strong&gt;유틸리티, 공통 로직, 지원 클래스&lt;/strong&gt; 등 다양한 곳에서 재사용될 수 있는 Bean을 등록합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;특정 계층(Service, Controller, Repository)에 명시적으로 속하지 않는 클래스.&lt;/li&gt;
&lt;li&gt;범용적인 기능(예: 문자열 처리, 포맷팅, 데이터 변환)을 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;비유로 이해하기&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스프링 애플리케이션을 &lt;strong&gt;하나의 조직&lt;/strong&gt;으로 생각해 봅시다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@Service&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;비즈니스 로직을 수행하는 핵심 부서의 직원&amp;quot;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;예를 들어, 사용자의 주문을 처리하거나 결제 시스템과 상호작용하는 업무 담당자입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;@Component&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;특정 부서에 속하지 않고 여러 부서를 지원하는 지원 직원&amp;quot;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;예를 들어, 모든 부서에서 사용하는 공용 도구(문서 양식 변환기, 데이터 계산기) 같은 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다른 계층 어노테이션과의 비교&lt;ul&gt;
&lt;li&gt;@Controller: 사용자와 직접 소통하는 &lt;strong&gt;프런트라인 직원&lt;/strong&gt;(예: 사용자 요청을 받고 결과를 반환).&lt;/li&gt;
&lt;li&gt;@Repository: 데이터베이스를 관리하는 &lt;strong&gt;기록 담당자&lt;/strong&gt;(예: 데이터 저장소와 직접 상호작용).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;@Component 예제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;아래는 특정 계층에 속하지 않는 문자열 포맷팅 유틸리티 클래스입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  import org.springframework.stereotype.Component

  @Component
  class StringFormatter {
      fun capitalize(input: String): String {
          return input.replaceFirstChar { it.uppercaseChar() }
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 클래스는 비즈니스 로직이 아니며, 특정 계층에 속하지 않습니다.&lt;/li&gt;
&lt;li&gt;여러 계층에서 재사용될 수 있는 &lt;strong&gt;범용 유틸리티&lt;/strong&gt;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;@Service 예제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;아래는 비즈니스 로직을 처리하는 서비스 클래스입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  import org.springframework.stereotype.Service

  @Service
  class GreetingService(private val stringFormatter: StringFormatter) {
      fun getGreeting(name: String): String {
          return &amp;quot;Hello, ${stringFormatter.capitalize(name)}!&amp;quot;
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GreetingService는 비즈니스 로직을 담당합니다.&lt;/li&gt;
&lt;li&gt;내부적으로 StringFormatter를 사용하여 문자열을 변환하는 로직을 조합합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서비스 계층&lt;/strong&gt;에 적합한 작업(예: &amp;quot;환영 메시지 생성&amp;quot;)을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;FAQ&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@Component 대신 항상 @Service를 사용하면 안 되나요?&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;가능은 하지만, @Service는 비즈니스 로직을 처리하는 클래스를 나타내기 위해 사용합니다.&lt;/li&gt;
&lt;li&gt;모든 클래스에 @Service를 사용하면 계층을 구분하는 의도가 모호해질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;@Component와 @Service가 내부적으로 다른가요?&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;내부적으로는 동일합니다. 둘 다 Spring Bean으로 등록되며, 동작에 차이는 없습니다.&lt;/li&gt;
&lt;li&gt;단지 코드의 의도를 명확히 하기 위해 @Service를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;결론&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;비즈니스 로직을 처리하는 클래스에는 @Service를 사용하세요.&lt;/li&gt;
&lt;li&gt;특정 계층에 속하지 않고 범용적인 지원 역할을 하는 클래스에는 @Component를 사용하세요.&lt;/li&gt;
&lt;li&gt;이렇게 하면 코드의 가독성이 향상되고 역할이 명확해져 유지보수와 협업이 쉬워집니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/795</guid>
      <comments>https://wooono.tistory.com/795#entry795comment</comments>
      <pubDate>Mon, 23 Dec 2024 11:14:05 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] Controller와 Service의 계층 구조</title>
      <link>https://wooono.tistory.com/794</link>
      <description>&lt;h3&gt;&lt;strong&gt;컨트롤러(Controller)와 서비스(Service)의 개념&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Spring Framework에서 컨트롤러(Controller)와 서비스(Service)는 애플리케이션 아키텍처의 중요한 부분으로, 각각의 역할과 책임이 명확히 나뉘어 있습니다.&lt;/li&gt;
&lt;li&gt;이 둘은 &lt;strong&gt;Spring MVC(Model-View-Controller)&lt;/strong&gt; 패턴의 일부로, 애플리케이션의 데이터 흐름을 구조화하고 유지보수를 용이하게 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;컨트롤러(Controller)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;컨트롤러는 클라이언트의 요청을 받아 응답을 반환하는 역할을 합니다. 주로 RESTful API 개발에서 사용됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 보낸 요청(Request)을 받고, 적절한 서비스를 호출하여 처리 결과를 반환(Response).&lt;/li&gt;
&lt;li&gt;URL 경로를 매핑하고 HTTP 요청을 처리.&lt;/li&gt;
&lt;li&gt;데이터를 직접 처리하지 않고, 비즈니스 로직은 서비스에 위임.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;요청/응답 흐름을 관리.&lt;/li&gt;
&lt;li&gt;REST API에서는 &lt;code&gt;@RestController&lt;/code&gt;를 사용하여 JSON 데이터를 반환.&lt;/li&gt;
&lt;li&gt;Spring MVC 뷰를 사용하는 경우 &lt;code&gt;@Controller&lt;/code&gt;를 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  import org.springframework.web.bind.annotation.*;

  @RestController
  @RequestMapping(&amp;quot;/users&amp;quot;) // 모든 요청이 /users로 시작
  public class UserController {

      private final UserService userService;

      public UserController(UserService userService) {
          this.userService = userService; // Service 의존성 주입
      }

      @GetMapping
      public List&amp;lt;User&amp;gt; getAllUsers() {
          return userService.getAllUsers(); // 서비스 호출
      }

      @GetMapping(&amp;quot;/{id}&amp;quot;)
      public User getUserById(@PathVariable Long id) {
          return userService.getUserById(id); // 서비스 호출
      }

      @PostMapping
      public User createUser(@RequestBody User user) {
          return userService.createUser(user); // 서비스 호출
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;서비스(Service)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;서비스는 비즈니스 로직을 처리하는 계층으로, 컨트롤러와 데이터 액세스 계층(Repository) 사이에서 중간 역할을 합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;역할&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컨트롤러에서 받은 요청 데이터를 바탕으로 실제 처리 작업 수행.&lt;/li&gt;
&lt;li&gt;데이터베이스와의 직접적인 상호작용은 Repository에 위임.&lt;/li&gt;
&lt;li&gt;애플리케이션의 핵심 로직 구현 및 재사용 가능한 메서드 작성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터 검증, 트랜잭션 관리 등의 비즈니스 로직 포함.&lt;/li&gt;
&lt;li&gt;데이터 액세스 코드와 컨트롤러 코드를 분리하여 의존성을 줄임.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  import org.springframework.stereotype.Service;

  @Service
  public class UserService {

      private final UserRepository userRepository;

      public UserService(UserRepository userRepository) {
          this.userRepository = userRepository; // Repository 의존성 주입
      }

      public List&amp;lt;User&amp;gt; getAllUsers() {
          return userRepository.findAll(); // 데이터베이스에서 모든 유저 조회
      }

      public User getUserById(Long id) {
          return userRepository.findById(id)
                  .orElseThrow(() -&amp;gt; new RuntimeException(&amp;quot;User not found&amp;quot;));
      }

      public User createUser(User user) {
          return userRepository.save(user); // 새로운 유저 저장
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;컨트롤러와 서비스의 계층 구조&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring MVC 애플리케이션의 계층 구조는 아래와 같습니다&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Controller Layer&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;사용자의 요청을 받음 → 적절한 HTTP 응답 반환.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@RestController&lt;/code&gt;, &lt;code&gt;@RequestMapping&lt;/code&gt;, &lt;code&gt;@GetMapping&lt;/code&gt; 등을 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Layer&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;비즈니스 로직 처리.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Service&lt;/code&gt;를 사용하여 선언.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository Layer&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;데이터베이스와의 상호작용을 담당.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JpaRepository&lt;/code&gt;와 같은 인터페이스로 데이터 조회, 저장, 삭제.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상단 예제 코드 기반 계층 구조 흐름&lt;/strong&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Controller:&lt;/strong&gt; &lt;code&gt;GET /users&lt;/code&gt; 요청을 받으면, &lt;code&gt;UserService&lt;/code&gt;의 &lt;code&gt;getAllUsers()&lt;/code&gt; 호출.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service:&lt;/strong&gt; &lt;code&gt;getAllUsers()&lt;/code&gt;에서 &lt;code&gt;UserRepository&lt;/code&gt;를 호출하여 데이터를 가져옴.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;code&gt;findAll()&lt;/code&gt; 메서드를 통해 데이터베이스에서 데이터를 조회.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;컨트롤러와 서비스를 분리하는 이유&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;유지보수성 향상&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;컨트롤러는 요청/응답만, 서비스는 비즈니스 로직만 관리.&lt;/li&gt;
&lt;li&gt;각 계층이 독립적이므로 코드 가독성과 유지보수성이 높아짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재사용성&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;서비스 계층은 여러 컨트롤러에서 공통적으로 호출 가능.&lt;/li&gt;
&lt;li&gt;동일한 비즈니스 로직을 중복 작성하지 않아도 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 용이성&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;역할 분리로 인해 컨트롤러와 서비스를 독립적으로 테스트하기 쉬움.&lt;/li&gt;
&lt;li&gt;Mock 객체를 활용하여 단위 테스트 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/794</guid>
      <comments>https://wooono.tistory.com/794#entry794comment</comments>
      <pubDate>Fri, 20 Dec 2024 11:40:51 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] @SqlResultSetMapping</title>
      <link>https://wooono.tistory.com/793</link>
      <description>&lt;h3&gt;@SqlResultSetMapping&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@SqlResultSetMapping은 네이티브 쿼리의 결과를 JPA에서 사용할 수 있는 형식으로 매핑하기 위해 사용하는 어노테이션입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쉽게 말하면, 데이터베이스에서 가져온 데이터를 Entity나 DTO로 변환해주는 설계도라고 보면 됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;왜 필요한가요?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기본적으로 JPA는 쿼리 결과를 자동으로 매핑해 주지만,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;복잡한 쿼리나 특정 구조의 데이터를 반환해야 할 경우 직접 매핑을 설정해야 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;@SqlResultSetMapping을 사용하면 이런 매핑 과정을 명확하고 간단하게 정의할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;어떻게 사용하나요?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터베이스의 결과를 Entity로 매핑 (EntityResult)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;사용 예시&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @SqlResultSetMapping(
      name = &amp;quot;UserMapping&amp;quot;, // 매핑 이름
      entities = @EntityResult( // 엔티티로 매핑
          entityClass = User.class, // 어떤 엔티티에 매핑할지 지정
          fields = { // 쿼리 결과 컬럼 -&amp;gt; 엔티티 필드 매핑
              @FieldResult(name = &amp;quot;id&amp;quot;, column = &amp;quot;user_id&amp;quot;),
              @FieldResult(name = &amp;quot;username&amp;quot;, column = &amp;quot;user_name&amp;quot;),
              @FieldResult(name = &amp;quot;email&amp;quot;, column = &amp;quot;user_email&amp;quot;)
          }
      )
  )

  // User 엔티티 정의
  @Entity
  public class User { 
      @Id
      private Long id;
      private String username;
      private String email;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;쿼리 사용&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Query(value = &amp;quot;SELECT user_id, user_name, user_email FROM users&amp;quot;, nativeQuery = true)
  @SqlResultSetMapping(name = &amp;quot;UserMapping&amp;quot;) // 매핑 참조
  List&amp;lt;User&amp;gt; findUsers();&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스에서 가져온 쿼리 결과가 User Entity 객체에 자동으로 매핑됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예를 들어, user_id → id, user_name → username과 같이 컬럼 이름이 엔티티 필드에 대응됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터베이스의 결과를 DTO로 매핑 (ConstructorResult)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Entity가 아닌 DTO 또는 별도의 클래스로 데이터를 매핑할 때 사용합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이 경우 생성자를 통해 데이터를 전달합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;사용 예시&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @SqlResultSetMapping(
      name = &amp;quot;UserDTOConstructorMapping&amp;quot;, // 매핑 이름
      classes = @ConstructorResult( // 생성자를 통해 매핑
          targetClass = UserDTO.class, // 어떤 클래스에 매핑할지 지정
          columns = { // 쿼리 결과 컬럼 -&amp;gt; 생성자 매개변수 매핑
              @ColumnResult(name = &amp;quot;user_id&amp;quot;, type = Long.class),
              @ColumnResult(name = &amp;quot;user_name&amp;quot;, type = String.class),
              @ColumnResult(name = &amp;quot;user_email&amp;quot;, type = String.class)
          }
      )
  )

  // DTO 클래스 정의
  public class UserDTO {
      private Long id;
      private String username;
      private String email;

      public UserDTO(Long id, String username, String email) { // 생성자
          this.id = id;
          this.username = username;
          this.email = email;
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;쿼리 사용&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Query(value = &amp;quot;SELECT user_id, user_name, user_email FROM users&amp;quot;, nativeQuery = true)
  @SqlResultSetMapping(name = &amp;quot;UserDTOConstructorMapping&amp;quot;) // 매핑 참조
  List&amp;lt;UserDTO&amp;gt; findUserDTOs();&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;쿼리 결과가 UserDTO 객체에 매핑됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예를 들어, user_id → 생성자의 id, user_name → username 매개변수로 전달됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;요약&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EntityResult : 데이터베이스 결과를 Entity로 매핑&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ConstructorResult : 데이터베이스 결과를 DTO로 매핑&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;유연성&lt;/strong&gt;: 복잡한 쿼리 결과를 Entity나 DTO에 매핑 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재사용성&lt;/strong&gt;: 동일한 매핑 이름을 여러 네이티브 쿼리에서 재사용 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가독성&lt;/strong&gt;: 매핑 규칙이 명확히 정의되어 코드 가독성 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;유지 보수 어려움&lt;/strong&gt;: 컬럼 이름 변경 시 매핑 설정도 함께 수정 필요&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DB 의존성 증가&lt;/strong&gt;: 네이티브 쿼리 사용으로 특정 DB 종속 가능성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;사용 시점&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;여러 테이블 조합&lt;/strong&gt;: 엔티티로 표현하기 어려운 데이터를 반환해야 할 때&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DTO 반환&lt;/strong&gt;: 엔티티 외부 데이터를 가공해 API 응답으로 전달해야 할 때&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/793</guid>
      <comments>https://wooono.tistory.com/793#entry793comment</comments>
      <pubDate>Fri, 20 Dec 2024 10:12:57 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] ORM, JPA, Spring Data JPA</title>
      <link>https://wooono.tistory.com/792</link>
      <description>&lt;h3&gt;구성 요소 요약&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;JPA (Java Persistence API)
   ├── ORM (Object-Relational Mapping)
   │    └── Entity
   ├── Spring Data JPA
   │    └── Repository
   │         ├── JpaRepository
   │         └── CrudRepository&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPA&lt;/strong&gt; : ORM을 Java에서 쉽게 사용할 수 있도록 정의한 표준 API입니다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ORM&lt;/strong&gt; : 객체와 데이터베이스 간 매핑을 처리하는 기술입니다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Entity&lt;/strong&gt; : 데이터베이스의 테이블과 매핑되는 자바 객체입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Data JPA&lt;/strong&gt; : JPA를 쉽게 사용할 수 있도록 도와주는 Spring 모듈입니다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repository&lt;/strong&gt; : 데이터베이스에 접근하기 위한 인터페이스입니다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JpaRepository&lt;/strong&gt; : 기본적인 CRUD 기능뿐만 아니라 페이징, 정렬, 쿼리 메소드 등을 지원하는 기능이 추가된 인터페이스입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CrudRepository :&lt;/strong&gt; 가장 기본적인 CRUD 기능(생성, 읽기, 수정, 삭제)을 제공하는 인터페이스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Entity&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;목적&lt;/strong&gt;: 데이터베이스 테이블과 매핑되는 자바 객체입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;: @Entity 어노테이션을 사용하여 데이터베이스 테이블을 자바 클래스와 연결합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Entity
  public class Product {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;

      private String name;
      private Double price;

      // Getters, Setters
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;ORM (Object-Relational Mapping)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;목적&lt;/strong&gt;: 데이터베이스와 객체(Entity) 간의 매핑을 관리하는 기술입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: SQL을 직접 작성하지 않고 객체 지향적으로 데이터베이스 작업을 처리할 수 있어 생산성과 유지보수성을 높입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Entity
  public class User {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;

      private String name;
      private String email;

      // Getters, Setters
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;JPA (Java Persistence API)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적&lt;/strong&gt;: Java에서 ORM(Object-Relational Mapping)을 사용하기 위한 표준 API입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기능&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Entity 매핑&lt;/strong&gt;: 객체와 데이터베이스 테이블 간의 매핑을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;영속성 컨텍스트 관리&lt;/strong&gt;: 엔티티 상태를 관리하여, 데이터의 생명 주기를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JPQL (Java Persistence Query Language)&lt;/strong&gt;: 객체지향적 쿼리 언어를 사용하여 데이터를 조회합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트랜잭션 관리&lt;/strong&gt;: 데이터 작업의 원자성 보장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필수 구현체&lt;/strong&gt;: JPA는 표준 API이므로 Hibernate, EclipseLink, DataNucleus와 같은 구현체가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Spring Data JPA&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적&lt;/strong&gt;: JPA를 쉽게 사용할 수 있도록 돕는 Spring 모듈입니다. JPA를 사용한 데이터 접근을 더 간편하게 구현할 수 있게 해줍니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기능&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자동 구현&lt;/strong&gt;: 인터페이스에서 JpaRepository를 상속받으면 기본적인 CRUD 작업이 자동으로 제공됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쿼리 메소드&lt;/strong&gt;: 메서드 이름으로 쿼리를 생성할 수 있습니다. 예: findByName(String name)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;페이징 및 정렬 지원&lt;/strong&gt;: PagingAndSortingRepository로 페이징과 정렬 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스텀 쿼리&lt;/strong&gt;: @Query 어노테이션으로 복잡한 쿼리를 정의할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적&lt;/strong&gt;: 데이터베이스에 접근하기 위한 인터페이스로, CRUD (Create, Read, Update, Delete) 및 쿼리 메소드 등을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Data JPA의 주요 인터페이스&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JpaRepository&lt;/strong&gt;: 기본적인 CRUD 기능과 더불어 페이징, 정렬, 쿼리 메소드 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CrudRepository&lt;/strong&gt;: 기본적인 CRUD 기능만 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;JpaRepository&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;목적&lt;/strong&gt;: Spring Data JPA에서 데이터베이스 작업을 더 간편하게 처리할 수 있도록 돕는 인터페이스입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기본적인 CRUD 기능과 페이징, 정렬, 쿼리 메소드 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;개발자가 직접 구현하지 않고, Spring Data JPA가 자동으로 구현해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;기본적인 CRUD 제공&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;save() : 엔티티 저장&lt;/li&gt;
&lt;li&gt;findById() : ID로 엔티티 조회&lt;/li&gt;
&lt;li&gt;findAll() : 모든 엔티티 조회&lt;/li&gt;
&lt;li&gt;delete() : 엔티티 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;메서드 이름에 따른 자동 쿼리 생성&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예를 들어, findByName(String name) 메서드를 작성하면 name 필드를 기준으로 데이터를 조회하는 쿼리가 자동으로 생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JpaRepository 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
      // name 필드를 기준으로 데이터를 조회하는 쿼리가 자동으로 생성됩니다.
      List&amp;lt;User&amp;gt; findByName(String name);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 예시에서 UserRepository는 JpaRepository를 상속받아 User 엔티티에 대한 CRUD 작업을 자동으로 제공합니다.&lt;/li&gt;
&lt;li&gt;findByName 메서드는 name 필드를 기준으로 자동으로 쿼리를 생성하여 데이터를 조회합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/792</guid>
      <comments>https://wooono.tistory.com/792#entry792comment</comments>
      <pubDate>Wed, 18 Dec 2024 15:43:10 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] @Entity와 @Repository</title>
      <link>https://wooono.tistory.com/791</link>
      <description>&lt;h3&gt;&lt;strong&gt;@Entity&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;목적&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPA의 Entity 클래스는 데이터베이스 테이블에 매핑되는 객체입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스 테이블과 자바 객체 간의 매핑을 담당합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;어노테이션&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Entity: 해당 클래스가 데이터베이스 테이블과 매핑된다는 것을 명시합니다.&lt;/li&gt;
&lt;li&gt;@Table(name = &amp;quot;table_name&amp;quot;): 데이터베이스 테이블 이름을 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;@Id: 해당 필드가 기본 키(primary key)임을 지정합니다.&lt;/li&gt;
&lt;li&gt;@GeneratedValue: 기본 키 값의 자동 생성 전략을 지정합니다.&lt;ul&gt;
&lt;li&gt;예: GenerationType.IDENTITY&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Column(name = &amp;quot;column_name&amp;quot;): 필드와 데이터베이스 컬럼 간의 매핑을 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Entity 클래스는 데이터베이스 작업을 직접 수행하지 않고, JPA에서 데이터를 저장하고 관리하기 위한 객체 모델입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity 자체로는 비즈니스 로직을 수행하지 않으며, 단순히 데이터베이스와의 매핑을 위한 구조체 역할을 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Entity
  @Table(name = &amp;quot;user_table&amp;quot;)
  public class User {

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Column(name = &amp;quot;user_id&amp;quot;)
      private Long id;

      @Column(name = &amp;quot;username&amp;quot;)
      private String username;

      @Column(name = &amp;quot;email&amp;quot;)
      private String email;

      // Getters and Setters
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;복합 기본키&lt;/strong&gt; 예시&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  // 복합 기본키 클래스를 @Embeddable로 정의
  @Embeddable
  public class OrderDetailId implements Serializable {
      private Long orderId;
      private Long productId;
  }

  // 복합 기본키를 @EmbeddedId로 엔티티에 포함
  @Entity
  public class OrderDetail {
      @EmbeddedId
      private OrderDetailId id;

      private Integer quantity;
      private BigDecimal price;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;@Repository&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;목적&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repository는 주로 특정 Entity를 기준으로 데이터베이스에 접근하는 기능을 제공합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JpaRepository나 CrudRepository를 상속받은 인터페이스는 해당 Entity에 대한 CRUD 기능을 자동으로 제공합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repository는 기본적으로 특정 Entity를 다룹니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이를 기반으로 쿼리 메소드나 네이티브 쿼리를 실행하여 데이터를 처리합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JpaRepository는 기본적인 CRUD 기능 외에도 페이징, 정렬, 복잡한 쿼리 등을 처리할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
      List&amp;lt;User&amp;gt; findByUsername(String username);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위의 예시에서 UserRepository는 User 엔티티와 관련된 쿼리만 다루며,&lt;/li&gt;
&lt;li&gt;findByUsername 메서드는 User 엔티티를 기반으로 데이터를 조회합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;nativeQuery 사용 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {

      @Query(value = &amp;quot;SELECT * FROM another_table WHERE condition = ?1&amp;quot;, nativeQuery = true)
      List&amp;lt;Object[]&amp;gt; findByCustomQuery(String condition);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;nativeQuery를 사용하면 Entity와 관계없는 다른 테이블을 참조하거나 복잡한 SQL 쿼리를 작성하여 데이터베이스 작업을 할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;기타&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DataSource 설정&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;application.properties 또는 application.yml 파일에서 DataSource를 설정하여 JPA가 사용할 데이터베이스 연결 정보를 제공합니다.&lt;/li&gt;
&lt;li&gt;Spring Data JPA는 이 설정을 통해 데이터베이스와의 연결을 관리하고, Entity에 매핑된 데이터를 쿼리 및 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/791</guid>
      <comments>https://wooono.tistory.com/791#entry791comment</comments>
      <pubDate>Wed, 18 Dec 2024 14:39:58 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] DTO와 Entity의 관계</title>
      <link>https://wooono.tistory.com/790</link>
      <description>&lt;h3&gt;&lt;strong&gt;DTO와 Entity의 관계&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DTO&lt;/strong&gt;(Data Transfer Object)와 &lt;strong&gt;Entity&lt;/strong&gt;는 역할과 사용 목적이 다르며,&lt;/li&gt;
&lt;li&gt;이로 인해 서로 명확히 구분해서 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;DTO와 Entity의 차이&lt;/strong&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;항목&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DTO&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Entity&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;역할&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;데이터 전송에 사용되는 객체&lt;/td&gt;
&lt;td&gt;데이터베이스와 직접 연결된 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;목적&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;클라이언트-서버 간 데이터 교환 간소화&lt;/td&gt;
&lt;td&gt;데이터베이스 테이블의 구조를 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;속성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;필요한 데이터만 포함, 가공된 데이터 가능&lt;/td&gt;
&lt;td&gt;데이터베이스의 컬럼에 직접 매핑되는 필드 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;위치&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;주로 API 요청/응답에서 사용됨&lt;/td&gt;
&lt;td&gt;서비스 내부에서 사용됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;종속성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;독립적, 데이터베이스와 무관&lt;/td&gt;
&lt;td&gt;데이터베이스와 밀접하게 연관&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;DTO → Entity&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;필수는 아닙니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;단순 조회만 수행하는 경우에는 DTO를 Entity로 변환할 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;하지만, DTO 데이터를 기반으로 데이터베이스 저장, 수정, 삭제 작업을 진행하는 경우에는 필수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Entity → DTO&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;필수는 아닙니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;하지만, Entity를 그대로 사용한다면 클라이언트에게 불필요한 데이터가 노출되거나&lt;/li&gt;
&lt;li&gt;스키마 변경 시 유지보수성에 어려움이 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;변환 예제 - ModelMapper 사용&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ModelMapper는 DTO와 Entity 간의 변환을 쉽게 해주는 Java 라이브러리입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;객체 간의 매핑 작업을 자동화하고, 복잡한 변환을 간단하게 처리할 수 있게 도와줍니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ModelMapper&lt;/code&gt;를 사용하여 &lt;code&gt;User&lt;/code&gt; Entity를 &lt;code&gt;UserDTO&lt;/code&gt;로 변환하는 예제를 확인해보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;User Entity&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class User {
    private Long id;
    private String name;
    private String email;

    // Constructor, getters and setters
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;UserDTO&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserDTO {
    private Long id;
    private String name;
    private String email;

    // Constructor, getters and setters
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ModelMapper를 사용한 변환&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import org.modelmapper.ModelMapper;

public class ModelMapperExample {
    public static void main(String[] args) {
        // ModelMapper 객체 생성
        ModelMapper modelMapper = new ModelMapper();

        // Entity 객체 생성
        User user = new User();
        user.setId(1L);
        user.setName(&amp;quot;John Doe&amp;quot;);
        user.setEmail(&amp;quot;john.doe@example.com&amp;quot;);

        // Entity -&amp;gt; DTO 변환
        UserDTO userDTO = modelMapper.map(user, UserDTO.class);

        // 결과 출력
        System.out.println(&amp;quot;UserDTO ID: &amp;quot; + userDTO.getId());
        System.out.println(&amp;quot;UserDTO Name: &amp;quot; + userDTO.getName());
        System.out.println(&amp;quot;UserDTO Email: &amp;quot; + userDTO.getEmail());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;결론&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DTO ↔ Entity 변환은 필수가 아니지만, 보안, 유지보수, 유연성&lt;/strong&gt;을 위해 권장됩니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/790</guid>
      <comments>https://wooono.tistory.com/790#entry790comment</comments>
      <pubDate>Tue, 17 Dec 2024 17:46:13 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] @Scheduled 란?</title>
      <link>https://wooono.tistory.com/789</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring의 @Scheduled 작업은 기본적으로 싱글 스레드에서 실행됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;두 개 이상의 @Scheduled 작업이 정의되어 있어도, 병렬 스케줄링 설정을 하지 않으면 스프링의 기본 설정으로 인해 동시에 실행되지 않고 순차적으로 실행됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 개념을 모르고 무작정 코드부터 짰다가 병렬 쓰레드로 동작하지 않아, 원인을 찾느라 시간을 많이 썼습니다.. ㅜㅜ&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;@Scheduled 란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;@Scheduled는 Spring 프레임워크에서 제공하는 &lt;strong&gt;스케줄링 작업&lt;/strong&gt;을 정의하는 어노테이션입니다.&lt;/li&gt;
&lt;li&gt;특정 메서드를 주기적으로 실행하거나 예약된 시간에 실행할 수 있도록 설정하는 데 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Scheduling&lt;/strong&gt; 기능을 활성화하면, @Scheduled 어노테이션이 붙은 메서드가 자동으로 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;주요 기능&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;주기적으로 실행해야 하는 작업 (배치 작업, 데이터 처리 등)에 사용.&lt;/li&gt;
&lt;li&gt;간단하게 설정으로 크론 표현식, 고정된 간격(fixedRate), 고정된 지연(fixedDelay) 등을 지원.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;사용 방법&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;스케줄링 활성화&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;@Scheduled를 사용하려면 Spring Boot 또는 Spring 프로젝트에서 스케줄링 기능을 활성화해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootApplication
@EnableScheduling // 스케줄링 활성화
public class MyApplication {
   public static void main(String[] args) {
       SpringApplication.run(MyApplication.class, args);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;@Scheduled 어노테이션 적용&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;@Scheduled 어노테이션을 메서드에 붙여 스케줄링 작업을 정의합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;@Scheduled 어노테이션 적용 방법&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;(1) &lt;strong&gt;고정된 간격으로 실행 (fixedRate)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;이전 작업이 시작된 시간 기준으로 일정 간격으로 실행.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
public class ScheduledTasks {

  @Scheduled(fixedRate = 5000) // 5초 간격으로 실행
  public void fixedRateTask() {
      System.out.println(&amp;quot;Fixed Rate Task: &amp;quot; + System.currentTimeMillis());
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(2) &lt;strong&gt;작업이 끝난 후 일정 시간 뒤 실행 (fixedDelay)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;이전 작업이 종료된 시점 기준으로 일정 간격 뒤 실행.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Scheduled(fixedDelay = 5000) // 이전 작업 종료 후 5초 뒤 실행
public void fixedDelayTask() {
  System.out.println(&amp;quot;Fixed Delay Task: &amp;quot; + System.currentTimeMillis());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(3) &lt;strong&gt;초기 지연 시간 설정 (initialDelay)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;작업이 시작되기 전에 초기 대기 시간을 설정.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Scheduled(fixedRate = 5000, initialDelay = 2000) // 2초 대기 후 시작, 5초 간격 실행
public void initialDelayTask() {
  System.out.println(&amp;quot;Initial Delay Task: &amp;quot; + System.currentTimeMillis());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(4) &lt;strong&gt;크론 표현식 (cron)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;크론 표현식을 사용해 원하는 시간대나 요일에 작업 실행.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Scheduled(cron = &amp;quot;0 0 12 * * ?&amp;quot;) // 매일 12시에 실행
public void cronTask() {
  System.out.println(&amp;quot;Cron Task: &amp;quot; + System.currentTimeMillis());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;크론 표현식 구조&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;  초(0-59) 분(0-59) 시(0-23) 일(1-31) 월(1-12) 요일(0-7)&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;표현식&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0 * * * * ?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;매 분 0초마다 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0 0 12 * * ?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;매일 정오(12:00)에 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0 0 0 * * 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;매주 월요일 자정에 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*/10 * * * * ?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;매 10초마다 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;주의사항&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;빈으로 등록된 메서드만 실행&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;@Scheduled가 적용된 메서드는 반드시 Spring 빈으로 등록된 클래스에 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리턴 타입은 void&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;@Scheduled 메서드는 리턴 값을 가질 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;병렬 실행 주의&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;기본적으로 단일 스레드에서 실행되므로, 두 개 이상의 @Scheduled 작업이 정의되어 있어도, 병렬 스케줄링 설정을 하지 않으면 스프링의 기본 설정으로 인해 동시에 실행되지 않고 순차적으로 실행됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;병렬 처리가 필요하면 TaskScheduler 또는 @Async를 함께 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;병렬 스케줄링 설정&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스케줄러가 단일 스레드로 동작하는 기본 동작을 변경하려면, 커스텀 TaskScheduler를 설정할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Configuration
  public class SchedulerConfig {

      @Bean
      public TaskScheduler taskScheduler() {
          ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
          scheduler.setPoolSize(5); // 5개의 스레드 풀
          scheduler.setThreadNamePrefix(&amp;quot;MyScheduler-&amp;quot;);
          return scheduler;
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Web_App/Kotlin</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/789</guid>
      <comments>https://wooono.tistory.com/789#entry789comment</comments>
      <pubDate>Mon, 2 Dec 2024 16:18:52 +0900</pubDate>
    </item>
    <item>
      <title>[BigQuery] streaming buffer란?</title>
      <link>https://wooono.tistory.com/788</link>
      <description>&lt;h3&gt;에러 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BigQuery Table에서 특정 레코드를 DELETE하려했지만, 아래 에러가 발생했습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  UPDATE or DELETE statement over table &amp;#39;&amp;#39; would affect rows in the streaming buffer, which is not supported&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;에러 발생 원인&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BigQuery는 데이터를 테이블에 영구적으로 저장하기 전에 스트리밍 버퍼(streaming buffer)라는 메모리 기반 저장소에 임시로 저장합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;높은 데이터 수신 속도를 제공하기 위함입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러 메시지는 &lt;code&gt;UPDATE&lt;/code&gt; 또는 &lt;code&gt;DELETE&lt;/code&gt; 문이 스트리밍 버퍼에 있는 행을 수정하려 했기 때문에 발생한 에러입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스트리밍 버퍼에 있는 데이터는 아직 테이블에 완전히 반영되지 않았기 때문에 이러한 행에 대해 직접적인 수정 작업은 불가능합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;최근에 삽입한 데이터가 아니라면 이미 테이블로 반영됐기 때문에, 수정이 가능하며 위 에러 또한 발생하지 않습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스트리밍 버퍼에 있는 최신 데이터는 일정 시간(최대 약 90분) 이후에 테이블에 반영됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;에러 해결 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;스트리밍 버퍼에 있는 데이터를 삭제하거나 수정하려면 다음 방법을 사용할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;기다리기&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스트리밍 버퍼에 있는 데이터가 테이블에 반영될 때까지 기다립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;임시 테이블 사용&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;  -- 임시 테이블로 원본 데이터 복사
  CREATE OR REPLACE TABLE mydataset.temp_table AS
  SELECT * FROM mydataset.original_table;

  -- 임시 테이블에서 수정 작업 수행
  UPDATE mydataset.temp_table
  SET column_name = &amp;#39;new_value&amp;#39;
  WHERE condition;

  DELETE FROM mydataset.temp_table
  WHERE condition;

  -- 임시 테이블에서 원본 테이블로 데이터를 병합
  MERGE mydataset.original_table AS T
  USING mydataset.temp_table AS S
  ON T.id = S.id
  WHEN MATCHED THEN -- temp_table의 값으로 업데이트
    UPDATE SET column_name = S.column_name
  WHEN NOT MATCHED BY SOURCE THEN -- original_table에 있고 temp_table에는 없는 행은 삭제
    DELETE;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>GCP/Bigquery</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/788</guid>
      <comments>https://wooono.tistory.com/788#entry788comment</comments>
      <pubDate>Thu, 25 Jul 2024 16:02:54 +0900</pubDate>
    </item>
    <item>
      <title>Polling과 Pulling의 차이</title>
      <link>https://wooono.tistory.com/787</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Polling과 Pulling은 모두 클라이언트가 서버로부터 데이터를 가져오는 방식을 나타내지만, 두 용어는 약간의 차이가 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Polling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Polling은 클라이언트가 주기적으로 서버에 요청을 보내어 새로운 데이터나 업데이트가 있는지 확인하는 방식입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클라이언트는 일정한 시간 간격으로 서버에 요청을 보내며, 서버는 해당 시점에 가용한 데이터를 응답으로 전송합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;새로운 데이터가 없더라도 클라이언트는 계속해서 요청을 보내므로, 불필요한 통신 오버헤드가 발생할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;실시간성이 중요하지 않은 경우나 업데이트 빈도가 낮은 경우에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Pulling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pulling은 클라이언트가 필요할 때마다 서버에 요청을 보내어 데이터를 가져오는 방식입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클라이언트는 데이터가 필요한 시점에 서버에 요청을 보내고, 서버는 요청을 받은 시점의 데이터를 응답으로 전송합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pulling은 클라이언트의 요구사항에 따라 데이터를 가져오므로, 불필요한 통신 오버헤드를 줄일 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클라이언트가 능동적으로 데이터를 요청하므로, 실시간성이 필요한 경우에도 사용될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Polling과 Pulling의 주요 차이점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Polling은 새로운 데이터가 없더라도 지속적으로 요청을 보내므로 불필요한 통신 오버헤드가 발생할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;반면에 Pulling은 필요한 시점에만 요청을 보내므로 오버헤드를 줄일 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;추가적으로&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;주기적인 Pulling은 Polling으로도 볼 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.naver.com/chaosson/130158941618&quot;&gt;https://blog.naver.com/chaosson/130158941618&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joshua95.tistory.com/332&quot;&gt;https://joshua95.tistory.com/332&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Etc/CS Term</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/787</guid>
      <comments>https://wooono.tistory.com/787#entry787comment</comments>
      <pubDate>Mon, 8 Apr 2024 14:40:19 +0900</pubDate>
    </item>
    <item>
      <title>[Network] HTTP, HTTPS 및 SSL/TLS 암호화 프로토콜</title>
      <link>https://wooono.tistory.com/786</link>
      <description>&lt;h3&gt;HTTP(Hyper Text Transfer Protocol)란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HyperText 문서(ex. html)를 주고받는 프로토콜입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ex) 클라이언트가 웹 브라우저에 URL을 입력하면, 웹 서버에 HyperText 문서를 요청하고 응답받는 과정이 진행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP 요청(Request) 예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  GET /index.html HTTP/1.1
  Host: www.example.com
  User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;GET : HTTP 메서드로 서버 리소스를 요청합니다.&lt;/li&gt;
&lt;li&gt;/index.html : 요청하는 리소스의 경로입니다.&lt;/li&gt;
&lt;li&gt;Host : 요청을 보내는 대상 서버의 도메인 이름입니다.&lt;/li&gt;
&lt;li&gt;User-Agent : 요청을 보내는 클라이언트(브라우저)의 정보입니다.&lt;/li&gt;
&lt;li&gt;Accept : 클라이언트가 받을 수 있는 콘텐츠 타입을 나열합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP 응답(Response) 예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  HTTP/1.1 200 OK
  Date: Fri, 22 May 2015 06:07:21 GMT
  Content-Type: text/html
  Content-Length: 1256

  &amp;lt;!doctype html&amp;gt;
  &amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
      &amp;lt;title&amp;gt;Example Domain&amp;lt;/title&amp;gt;
      ... (응답 본문 생략)
  &amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;HTTP/1.1 200 OK : HTTP 버전과 상태 코드(200은 성공)입니다.&lt;/li&gt;
&lt;li&gt;Date : 응답이 생성된 날짜와 시간입니다.&lt;/li&gt;
&lt;li&gt;Content-Type : 응답 본문의 콘텐츠 타입(여기서는 HTML)입니다.&lt;/li&gt;
&lt;li&gt;Content-Length : 응답 본문의 길이(바이트 단위)입니다.&lt;/li&gt;
&lt;li&gt;(응답 본문) : 서버가 클라이언트에게 보내는 실제 콘텐츠입니다(예시에서는 HTML 코드).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;HTTPS(Hyper Text Transfer Protocol Secure)란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTPS는 SSL/TLS 암호화 프로토콜을 사용하여 클라이언트와 서버 간 통신 내용을 암호화하는 프로토콜입니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;일반 HTTP 통신에서는 평문 데이터를 그대로 전송하므로, 통신 내용이 제3자에게 노출될 수 있는 위험이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS에서 암호화되는 부분은 다음과 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;요청 본문 (Request Body)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;POST 또는 PUT 요청과 같이 요청 본문에 데이터가 포함된 경우, 해당 데이터는 암호화됩니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 로그인 폼의 사용자 이름과 비밀번호와 같은 민감한 정보가 요청 본문에 포함될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;응답 본문 (Response Body)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;서버에서 클라이언트로 전송되는 응답의 내용은 암호화됩니다.&lt;/li&gt;
&lt;li&gt;응답 본문에는 HTML, JSON, 이미지 등의 데이터가 포함될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;나머지 정보들은 요청, 응답을 올바르게 처리하고 라우팅하는데 필요한 정보이기 때문에 암호화되지 않습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SSL(Secure Sockets Layer) / TLS(Transport Layer Security) 암호화 프로토콜이란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;위에서 언급했듯이, SSL/TLS 암호화 프로토콜은 HTTPS에서 클라이언트와 서버 간 통신 내용을 암호화할 때 사용되는 프로토콜입니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;TLS는 SSL의 후속 버전이며, 현재는 TLS가 널리 사용되고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL/TLS 암호화 프로토콜은 다음 순서로 진행됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;암호화 통신 세션 연결 (SSL / TLS 핸드셰이크)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 통신&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 통신 세션 연결 해제&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;암호화 통신 세션 연결 (SSL / TLS 핸드셰이크) 방법&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;45%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjESE/btsGt7Dn65w/tHMqSmnuV8kWFkRtaYBpN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjESE/btsGt7Dn65w/tHMqSmnuV8kWFkRtaYBpN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjESE/btsGt7Dn65w/tHMqSmnuV8kWFkRtaYBpN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjESE%2FbtsGt7Dn65w%2FtHMqSmnuV8kWFkRtaYBpN1%2Fimg.png&quot; width=&quot;45%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;클라이언트에서 서버의 SSL/TLS 인증서를 확인해 신뢰 가능한 서버인지 검증하고 클라이언트와 서버간의 안전한 데이터 전송을 위해 대칭키를 공유합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;클라이언트&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTPS 통신을 시작하겠다는 요청(Client Hello)을 보냅니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트가 지원하는 SSL/TLS 버전, 암호화 알고리즘, 압축 방식 등의 정보를 포함합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;응답(Server Hello)을 보냅니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트의 SSL/TLS 버전, 암호화 알고리즘 등을 선택합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버의 SSL/TLS 인증서를 보냅니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;SSL/TLS 인증서에는 서버 공개키, 서버 도메인, 인증기관 정보, 유효기간 등이 포함되어 있으며,&lt;/li&gt;
&lt;li&gt;인증기관(CA, Certificate Agent)의 개인키로 암호화(서명)되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;클라이언트&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;서버의 SSL/TLS 인증서 검증&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트는 신뢰할 수 있는 CA(인증기관)의 공개키를 사용해, 서버 인증서의 서명을 검증하고,&lt;/li&gt;
&lt;li&gt;인증서에 포함된 정보(서버 도메인, 유효기간 등)를 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대칭키 전송&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;인증서 검증에 성공하면 클라이언트는 데이터 암호화에 사용될 대칭키를 생성하고,&lt;/li&gt;
&lt;li&gt;서버의 공개키로, 대칭키를 암호화하여 전송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;서버의 개인키로, 암호화된 대칭키를 복호화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;전체적인 HTTPS 통신 순서&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TCP 연결 설정 (3-way Handshake)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트가 서버에게 SYN 패킷을 보냅니다.&lt;/li&gt;
&lt;li&gt;서버는 SYN 패킷을 받고, 클라이언트에게 SYN+ACK 패킷을 보냅니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 SYN+ACK 패킷을 받고, 서버에게 ACK 패킷을 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 통신 세션 연결 (SSL/TLS 핸드셰이크)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트와 서버는 SSL/TLS 버전 및 암호화 알고리즘을 협상합니다.&lt;/li&gt;
&lt;li&gt;서버는 SSL/TLS 인증서를 클라이언트에게 전송합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 서버의 인증서를 검증하고, 안전한 데이터 전송을 위한 대칭키를 생성하여 서버의 공개키로 암호화한 후 전송합니다.&lt;/li&gt;
&lt;li&gt;서버는 개인키로 대칭키를 복호화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 통신&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트와 서버는 공유된 대칭키를 사용하여 데이터를 암호화하고 복호화하며 통신합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 통신 세션 연결 해제&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트 또는 서버가 SSL/TLS 세션 종료를 알리는 &amp;quot;close_notify&amp;quot; 경고 메시지를 보냅니다.&lt;/li&gt;
&lt;li&gt;상대방은 &amp;quot;close_notify&amp;quot; 경고 메시지로 응답하여 SSL/TLS 세션 종료를 확인합니다.&lt;/li&gt;
&lt;li&gt;클라이언트와 서버는 공유된 대칭키를 폐기합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP 연결 해제 (4-way handshake)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;클라이언트 또는 서버가 상대방에게 FIN 패킷을 보냅니다.&lt;/li&gt;
&lt;li&gt;상대방은 FIN 패킷을 받고, ACK 패킷을 보냅니다.&lt;/li&gt;
&lt;li&gt;상대방도 FIN 패킷을 보냅니다.&lt;/li&gt;
&lt;li&gt;클라이언트 또는 서버가 FIN 패킷을 받고, ACK 패킷을 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wooono.tistory.com/114&quot;&gt;https://wooono.tistory.com/114&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wooono.tistory.com/106&quot;&gt;https://wooono.tistory.com/106&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wooono.tistory.com/507&quot;&gt;https://wooono.tistory.com/507&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wooono.tistory.com/508&quot;&gt;https://wooono.tistory.com/508&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thesslstore.com/blog/explaining-ssl-handshake/&quot;&gt;https://www.thesslstore.com/blog/explaining-ssl-handshake/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Network &amp;amp; Security/Concept</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/786</guid>
      <comments>https://wooono.tistory.com/786#entry786comment</comments>
      <pubDate>Mon, 8 Apr 2024 13:49:51 +0900</pubDate>
    </item>
    <item>
      <title>[Greedy] LeetCode &amp;ldquo;53. Maximum Subarray&amp;ldquo; Python 풀이</title>
      <link>https://wooono.tistory.com/785</link>
      <description>&lt;h3&gt;문제 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/problems/maximum-subarray/description/&quot;&gt;https://leetcode.com/problems/maximum-subarray/description/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;풀이&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;해당 문제는 그리디 알고리즘(Greedy Algorithm)의 한 종류인 카데인 알고리즘(Kadane’s Algorithm)을 사용해서 해결할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;카데인 알고리즘(Kadane&amp;#39;s Algorithm)은 배열에서 연속된 부분 배열의 최대 합을 구하는 효율적인 알고리즘입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이 알고리즘은 O(N)의 시간 복잡도를 가지며, 다음과 같은 단계로 진행됩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;현재 합&amp;quot;과 &amp;quot;최대 합&amp;quot;을 -inf(음의 무한대)로 초기화합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배열을 순회하며 다음 두 가지 경우 중 큰 값을 “현재 합”으로 갱신합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이전 인덱스까지의 “현재 합” + 현재 인덱스의 값&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;현재 인덱스의 값&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“현재 합”이 “최대 합”보다 크면 “최대 합”을 “현재 합”으로 갱신합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예를 들어, 배열 [-2, 1, -3, 4, -1, 2, 1, -5, -2, 5]의 경우 다음과 같이 진행됩니다.&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;70%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTVmq5/btsF157PfVR/FWUCb9bu67JOqcnvEFKaZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTVmq5/btsF157PfVR/FWUCb9bu67JOqcnvEFKaZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTVmq5/btsF157PfVR/FWUCb9bu67JOqcnvEFKaZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTVmq5%2FbtsF157PfVR%2FFWUCb9bu67JOqcnvEFKaZ0%2Fimg.png&quot; width=&quot;70%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  초기화: 최대 합 = -inf, 현재 합 = -inf

  -2: 현재 합 = max(-2, -inf + (-2)) = -2
  1: 현재 합 = max(1, -2 + 1) = 1
  -3: 현재 합 = max(-3, 1 + (-3)) = -2
  4: 현재 합 = max(4, -2 + 4) = 4, 최대 합 갱신 = 4
  -1: 현재 합 = max(-1, 4 + (-1)) = 3
  2: 현재 합 = max(2, 3 + 2) = 5, 최대 합 갱신 = 5
  1: 현재 합 = max(1, 5 + 1) = 6, 최대 합 갱신 = 6
  -5: 현재 합 = max(-5, 6 + (-5)) = 1
  -2: 현재 합 = max(-2, 1 + (-2)) = -1
  5: 현재 합 = max(5, -1 + 5) = 4&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;따라서, 이 배열에서 연속된 부분 배열의 최대 합은 6입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Solution:
    def maxSubArray(self, nums: List[int]) -&amp;gt; int:
        curr_sum = float(-inf)
        max_sum = float(-inf)

        for i in range(len(nums)):
            curr_sum = max(curr_sum + nums[i], nums[i])
            max_sum = max(curr_sum, max_sum)

        return max_sum&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wellsw.tistory.com/125&quot;&gt;https://wellsw.tistory.com/125&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Algorithm/Greedy</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/785</guid>
      <comments>https://wooono.tistory.com/785#entry785comment</comments>
      <pubDate>Sat, 23 Mar 2024 19:32:34 +0900</pubDate>
    </item>
    <item>
      <title>[DP] 백준 11722번 &amp;ldquo;가장 긴 감소하는 부분 수열&amp;rdquo; Python 풀이</title>
      <link>https://wooono.tistory.com/784</link>
      <description>&lt;h3&gt;문제 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11722&quot;&gt;https://www.acmicpc.net/problem/11722&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import sys

n = int(sys.stdin.readline())
item = list(map(int, sys.stdin.readline().split()))

# 아이템 역순 정렬
item_reversed = item[::-1]

# DP 테이블 생성
dp = [1] * n

for i in range(n):
    for j in range(i+1,n):
        # 현재 위치의 값(item_reversed[i])보다 비교 대상(item_reversed[j])보다 크면,
        # 증가하는 순서가 성립하므로 DP 테이블 업데이트
       if (item_reversed[i] &amp;lt; item_reversed[j]):
           dp[j] = max(dp[i]+1, dp[j])

print(max(dp))&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/Dynamic Programming</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/784</guid>
      <comments>https://wooono.tistory.com/784#entry784comment</comments>
      <pubDate>Thu, 21 Mar 2024 15:36:47 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] Bigquery 테이블 생성 with Python</title>
      <link>https://wooono.tistory.com/783</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 Google Cloud의 BigQuery Python 클라이언트 라이브러리를 사용하여 BigQuery에 파티션된 테이블을 생성하는 코드에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Google Cloud의 BigQuery 클라이언트 라이브러리를 가져옵니다.
from google.cloud import bigquery

# 사용할 BigQuery 프로젝트의 ID를 설정합니다.
project_id = &amp;quot;your_project_id&amp;quot;

# BigQuery 클라이언트 객체를 생성합니다.
bigquery_client = bigquery.Client(project=project_id)

# 생성할 BigQuery 테이블의 스키마를 정의합니다.
schema = [
    bigquery.SchemaField(&amp;#39;category_l&amp;#39;, &amp;#39;STRING&amp;#39;),
    bigquery.SchemaField(&amp;#39;category_m&amp;#39;, &amp;#39;STRING&amp;#39;),
    bigquery.SchemaField(&amp;#39;category_s&amp;#39;, &amp;#39;STRING&amp;#39;),
    bigquery.SchemaField(&amp;#39;company_name&amp;#39;, &amp;#39;STRING&amp;#39;),
    bigquery.SchemaField(&amp;#39;date&amp;#39;, &amp;#39;DATE&amp;#39;),
    bigquery.SchemaField(&amp;#39;hour&amp;#39;, &amp;#39;INTEGER&amp;#39;),
    bigquery.SchemaField(&amp;#39;minute&amp;#39;, &amp;#39;INTEGER&amp;#39;),
    bigquery.SchemaField(&amp;#39;usage&amp;#39;, &amp;#39;FLOAT&amp;#39;),
]

# 대상 데이터셋 ID와 테이블 ID를 지정합니다.
dataset_id = &amp;quot;your_dataset_id&amp;quot;
table_id = &amp;quot;your_table_id&amp;quot;

# 데이터셋 및 테이블 참조를 생성합니다.
table_ref = bigquery_client.dataset(dataset_id).table(table_id)

# 테이블 객체를 생성하고 스키마를 설정합니다.
table = bigquery.Table(table_ref, schema=schema)

# 테이블에 대한 시간 기반 파티션 설정을 추가합니다.
table.time_partitioning = bigquery.TimePartitioning(
    type_=bigquery.TimePartitioningType.DAY,  # 파티션 유형 설정 (일별)
    field=&amp;#39;date&amp;#39;,  # 파티션을 위한 컬럼 지정
    require_partition_filter=True  # 파티션 필터 필요 여부 설정
)

# 동일한 이름의 테이블이 존재하는 경우 삭제합니다 (선택적).
try:
    bigquery_client.delete_table(table)
except Exception as e:
    print(e)  # 에러 출력

# 테이블을 생성합니다.
create_table_result = bigquery_client.create_table(table)

# 생성 결과를 출력합니다.
print(f&amp;quot;Table {create_table_result.full_table_id} created.&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>GCP/Bigquery</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/783</guid>
      <comments>https://wooono.tistory.com/783#entry783comment</comments>
      <pubDate>Thu, 21 Mar 2024 14:56:35 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] GCS &amp;rarr; Bigquery with Python</title>
      <link>https://wooono.tistory.com/782</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 Google Cloud Storage(GCS)에서 CSV 파일을 읽어서 Google BigQuery에 데이터를 저장하는 코드에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 필요한 라이브러리를 불러옵니다.
from google.cloud import storage, bigquery
import pandas as pd
from io import StringIO

# Google Cloud Storage(GCS) 설정
bucket_name = &amp;#39;your_bucket_name&amp;#39;  # GCS 버킷 이름
prefix = &amp;#39;your_file_prefix&amp;#39;  # 파일 경로 및 이름의 공통된 시작 부분
gcs_client = storage.Client()  # GCS 클라이언트 객체 생성
bucket = gcs_client.bucket(bucket_name)  # 버킷 객체 생성

# BigQuery 설정
bq_client = bigquery.Client()  # BigQuery 클라이언트 객체 생성
dataset_id = &amp;#39;your_dataset_id&amp;#39;  # 데이터셋 ID
table_id = &amp;#39;your_table_id&amp;#39;  # 테이블 ID
table_ref = bq_client.dataset(dataset_id).table(table_id)  # 테이블 참조 생성

# GCS에서 CSV 파일들을 읽고 BigQuery에 저장하는 과정
blobs = bucket.list_blobs(prefix=prefix)  # 지정된 경로의 파일 목록을 가져옵니다.
for blob in blobs:

    # CSV 파일 읽기
    data = blob.download_as_string()  # 파일 내용을 문자열로 다운로드
    df = pd.read_csv(StringIO(data.decode(&amp;#39;utf-8&amp;#39;)))  # 데이터프레임으로 변환

    # BigQuery에 데이터프레임 저장
    job = bq_client.load_table_from_dataframe(df, table_ref)
    job.result()  # Job이 완료될 때까지 대기

    print(f&amp;#39;{blob.name} 처리 완료&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>GCP/Bigquery</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/782</guid>
      <comments>https://wooono.tistory.com/782#entry782comment</comments>
      <pubDate>Thu, 21 Mar 2024 14:55:42 +0900</pubDate>
    </item>
    <item>
      <title>[DP] 백준 12865번 &amp;ldquo;평범한 배낭&amp;rdquo; Python 풀이</title>
      <link>https://wooono.tistory.com/780</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;배낭 문제(Knapsack Problem)는 제한된 공간에 가치가 높은 물건들을 최대한 많이 넣는 문제입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이 문제는 물건을 나눌 수 있냐 없냐에 따라 두 가지 유형으로 나뉩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;분할 가능한 배낭 문제(Fractional Knapsack Problem)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;물건을 일부분으로 나눌 수 있는 경우 (예: 설탕 500g 중 300g만 넣기)&lt;/li&gt;
&lt;li&gt;그리디 알고리즘으로 해결 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0-1 배낭 문제(0-1 Knapsack Problem)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;물건을 나눌 수 없고, 전부 넣거나 전혀 넣지 않아야 하는 경우&lt;/li&gt;
&lt;li&gt;동적 프로그래밍(Dynamic Programming)으로 해결 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 문제는 0-1 배낭 문제(0-1 Knapsack Problem)에 해당됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;설명&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문제 설정&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;물건 개수: N = 4&lt;/li&gt;
&lt;li&gt;배낭 용량: K = 7&lt;/li&gt;
&lt;li&gt;물건 정보: (무게, 가치) = (6, 13), (4, 8), (3, 6), (5, 12)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동적 프로그래밍 테이블 d[n][k] 정의&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;d[n][k]&lt;ul&gt;
&lt;li&gt;n번째 물건까지 고려했을 때, 배낭 용량 k에서 얻을 수 있는 최대 가치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재귀 관계식&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;현재 물건 i, 현재 물건의 무게 weight, 현재 물건의 가치 value, 담으려는 배낭의 용량 j 일 때&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;경우 1) 현재 물건을 배낭에 담을 수 없는 경우 (weight &amp;gt; j)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;d[i][j] = d[i-1][j]&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;이전 물건을 담았을 때까지의 최대 가치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;경우 2) 현재 물건을 배낭에 담을 수 있는 경우 (weight ≤ j)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;d[i][j] = max( d[i-1][j-weight] + value , d[i-1][j] )&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;d[i-1][j-weight] + value : 현재 물건의 무게만큼 배낭에서 뺀 뒤, 현재 물건을 담았을 때의 최대 가치&lt;/li&gt;
&lt;li&gt;d[i-1][j] : 현재 물건을 담지 않았을 때의 최대 가치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;최종 코드&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;모든 값이 채워진 2차원 DP 테이블의 d[N][K] 값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;진행 순서&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;첫 번째 물건(6, 13) 고려&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KIMr4/btsFNaoUGYS/bkprvsKNLPGmdy2dJxDMi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KIMr4/btsFNaoUGYS/bkprvsKNLPGmdy2dJxDMi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KIMr4/btsFNaoUGYS/bkprvsKNLPGmdy2dJxDMi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKIMr4%2FbtsFNaoUGYS%2FbkprvsKNLPGmdy2dJxDMi0%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;두 번째 물건(4, 8) 고려&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEbsiH/btsFPb09t2d/koFKEBEpUCPoKaxyOsUlVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEbsiH/btsFPb09t2d/koFKEBEpUCPoKaxyOsUlVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEbsiH/btsFPb09t2d/koFKEBEpUCPoKaxyOsUlVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEbsiH%2FbtsFPb09t2d%2FkoFKEBEpUCPoKaxyOsUlVK%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;세 번째 물건(3, 6) 고려&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OrAZk/btsFP5MUjXK/ndnl74LCX8BpXedrk79uA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OrAZk/btsFP5MUjXK/ndnl74LCX8BpXedrk79uA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OrAZk/btsFP5MUjXK/ndnl74LCX8BpXedrk79uA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOrAZk%2FbtsFP5MUjXK%2Fndnl74LCX8BpXedrk79uA1%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;네 번째 물건(5, 12) 고려&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/omSYl/btsFP2CFDQh/76mn9xErOHNOgzm1Dk2fC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/omSYl/btsFP2CFDQh/76mn9xErOHNOgzm1Dk2fC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/omSYl/btsFP2CFDQh/76mn9xErOHNOgzm1Dk2fC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FomSYl%2FbtsFP2CFDQh%2F76mn9xErOHNOgzm1Dk2fC1%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

# 물건 개수, 배낭의 최대 용량
n, k = map(int, sys.stdin.readline().split())

# DP 테이블
dp = [[0] * (k+1) for _ in range(n+1)]

# 다루고자하는 물건(무게, 가치) 목록
items = [[0, 0]]
for _ in range(n):
    w, v = map(int, sys.stdin.readline().split())
    items.append([w, v])

for i in range(1, n+1):
    # 현재 물건의 무게와 가치
    w = items[i][0]
    v = items[i][1]

    for j in range(1, k+1):

        # 현재 물건을 배낭에 담을 수 없는 경우
        if (w &amp;gt; j):
            dp[i][j] = dp[i-1][j]

        # 현재 물건을 배낭에 담을 수 있는 경우
        else:
            dp[i][j] = max(dp[i-1][j-w] + v, dp[i-1][j])

print(dp[n][k])&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://claude-u.tistory.com/208&quot;&gt;https://claude-u.tistory.com/208&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codingmovie.tistory.com/48&quot;&gt;https://codingmovie.tistory.com/48&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hongcoding.tistory.com/50&quot;&gt;https://hongcoding.tistory.com/50&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@keynene/Python%ED%8C%8C%EC%9D%B4%EC%8D%AC5-%EB%B0%B1%EC%A4%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-12865-%ED%8F%89%EB%B2%94%ED%95%9C%EB%B0%B0%EB%82%AD&quot;&gt;https://velog.io/@keynene/Python파이썬5-백준-알고리즘-12865-평범한배낭&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ryu-e.tistory.com/90&quot;&gt;https://ryu-e.tistory.com/90&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Algorithm/Dynamic Programming</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/780</guid>
      <comments>https://wooono.tistory.com/780#entry780comment</comments>
      <pubDate>Fri, 15 Mar 2024 13:40:47 +0900</pubDate>
    </item>
    <item>
      <title>B 트리란?</title>
      <link>https://wooono.tistory.com/779</link>
      <description>&lt;h3&gt;B 트리란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;B트리는 데이터베이스와 파일 시스템에서 널리 사용되는 자가 균형 이진 검색 트리의 일종입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B트리의 설계는 디스크 I/O 작업의 최소화를 목표로 하며, 이를 위해 트리의 높이를 가능한 낮게 유지합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;해당 포스팅에선 B트리의 규칙과 조회, 삽입, 삭제 방법에 대해서 다뤄보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;규칙&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;노드&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;노드 차수(M): 각 노드가 가질 수 있는 자식 노드의 최대 개수&lt;ul&gt;
&lt;li&gt;각 노드는 최소 M/2개에서 최대 M개의 자식 노드를 가질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;루트 노드: 최소 2개의 자식 노드 (트리 생성/삭제 시 예외 가능)&lt;/li&gt;
&lt;li&gt;내부 노드: 최소 ⌈M/2⌉개의 자식 노드&lt;/li&gt;
&lt;li&gt;리프 노드: 모든 리프 노드는 동일한 레벨에 있어야 함 (데이터 접근 시간 균일화)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;키&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;각 노드에 저장된 값입니다.&lt;/li&gt;
&lt;li&gt;키 개수 범위: 최소 (M/2)-1개, 최대 M-1개 (최소 키 개수는 1개)&lt;/li&gt;
&lt;li&gt;키는 노드 내부에서 정렬된 상태로 유지되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;조회 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;B트리에서 데이터를 조회할 때는 루트에서 시작하여 재귀적으로 자식 노드를 탐색하며, 리프 노드에서 최종적으로 키의 존재 유무를 판단하는 방식입니다.&lt;/strong&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;루트 노드에서 시작&lt;/strong&gt;: 루트 노드에서 검색할 키 값과 노드 내 키 값들을 비교합니다. 검색 키가 노드 내 키들보다 작으면 왼쪽 자식 노드로, 크면 오른쪽 자식 노드로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재귀적 탐색&lt;/strong&gt;: 선택된 자식 노드에서 동일한 과정을 반복하여 리프 노드에 도달하거나 검색 키를 찾을 때까지 탐색을 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;키 발견 또는 실패&lt;/strong&gt;: 리프 노드에서 검색 키를 발견하면 검색에 성공한 것이고, 발견하지 못하면 해당 키가 트리에 없는 것이므로 검색에 실패한 것입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;삽입 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;B트리에 새 키를 삽입할 때는 먼저 적절한 리프 노드를 찾고, 그 노드에 여유 공간이 있으면 삽입하고, 없으면 노드를 분할하여 상위 노드로 중간값을 올리는 과정을 거치게 됩니다.&lt;/strong&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;적절한 리프 노드 찾기&lt;/strong&gt; : 삽입하려는 키에 대해 조회 과정을 수행하여 적절한 리프 노드를 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리프 노드에 삽입&lt;/strong&gt; : 해당 리프 노드에 여유 공간이 있다면 키를 삽입합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;노드 분할&lt;/strong&gt; : 여유 공간이 없다면 노드를 분할하여 새로운 키와 기존 키들을 두 노드에 분배합니다. 중간값은 상위 노드로 올라갑니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재귀적 분할&lt;/strong&gt; : 상위 노드도 꽉 차 있다면 이 과정을 재귀적으로 반복하여, 필요한 경우 루트까지 분할합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;삽입 예제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;차수(M)가 3인 B트리를 통해 삽입 과정 및 노드 분할을 설명하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;               [10, 20]
              /    |    \
           [5]   [15]   [25, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;삽입 시 노드 분할이 일어나지 않는 경우&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&amp;#39;17&amp;#39; 삽입 과정&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;적절한 리프 노드 찾기&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;루트 노드 [10, 20]에서 17은 10과 20 사이에 위치하므로, 중간 자식 노드 [15]로 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;리프 노드 삽입&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리프 노드 [15]에 17을 삽입하면 [15, 17]이 됩니다.&lt;/li&gt;
&lt;li&gt;리프 노드에 여유 공간이 있어 17을 바로 삽입할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;노드 분할 없이 삽입 완료&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리프 노드에 바로 삽입이 가능하므로 노드 분할 과정 없이 삽입이 완료됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;       [10,    20]
      /     |     \
   [5]   [15, 17] [25, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;삽입 시 노드 분할이 일어나는 경우&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&amp;#39;22&amp;#39; 삽입 과정&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;적절한 리프 노드 찾기&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;#39;22&amp;#39;는 루트 노드 [10, 20]를 거쳐 리프 노드 [25, 30]로 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;리프 노드 삽입 시도&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;리프 노드 [25, 30]에 &amp;#39;22&amp;#39;를 삽입하려 하지만, 이미 최대 키 수(2개)를 가지고 있어 노드 분할이 필요합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;       [10,    20]
      /     |     \
   [5]   [15, 17] [22, 25, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;노드 분할 준비&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[22, 25, 30]에서 중간값 &amp;#39;25&amp;#39;를 기준으로 노드 분할을 준비합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;분할 실행&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;왼쪽 새 노드 [22], 오른쪽 새 노드 [30]이 생성됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;중간값 &amp;#39;25&amp;#39;는 상위 노드로 이동합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;       [10,   20,   25]
      /    |      |    \
   [5]   [15,17] [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;루트 노드 분할&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;루트 노드 [10, 20, 25]도 최대 키 수(2개)를 넘어서므로 중간값 &amp;#39;20&amp;#39;을 기준으로 분할합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;분할 결과 &amp;#39;20&amp;#39;이 새로운 루트 노드가 되고, [10]과 [25]가 자식 노드가 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;         [20]
        /       \
   [10]        [25]
  /    |      /    \
[5]   [15,17] [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;삭제 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;먼저 삭제할 키를 찾고, 그 키를 삭제한 후 트리의 균형이 깨지면 인접 노드들과 병합하거나 키를 재분배하여 균형을 유지하는 작업을 수행하게 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;키 위치 찾기&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;삭제하려는 키의 위치를 트리에서 탐색하여 찾습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;키 삭제&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;키가 리프 노드에 있다면 그 노드에서 바로 삭제합니다.&lt;/li&gt;
&lt;li&gt;키가 내부 노드에 있다면, 그 키를 리프 노드로 내리고 리프 노드에서 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트리 균형 조정&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;키 삭제 후 해당 노드의 키 개수가 최소 요구 개수(M/2 - 1) 미만이 되면 트리의 균형을 맞추기 위해 조정이 필요합니다.&lt;/li&gt;
&lt;li&gt;인접한 노드와 병합(merge)하거나 키를 재배치(redistribution)하여 균형을 맞춥니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;리프 노드에서 삭제하는 경우&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;키 삭제 후 노드의 키 개수가 최소 개수 이상인 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;단순히 해당 키를 삭제하면 됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;트리 재조정이 필요하지 않습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;    # 키(17) 삭제

                  [20]
                /       \
            [10]        [25]
          /    |       /    \
       [5]   [15,17] [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;                  [20]
                /       \
           [10]        [25]
          /    |      /    \
       [5]   [15]   [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;키 삭제 후 노드의 키 개수가 최소 개수 미만인 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A. 형제 노드의 키 개수가 최소 개수를 초과하는 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;삭제하려는 키를 부모의 키로 대체합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;왼쪽 형제라면 왼쪽 형제의 가장 큰 값을, 오른쪽 형제라면 오른쪽 형제의 가장 작은 값을 부모 키로 대체합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;    # 키(5) 삭제
                   [20]
                /       \
             [10]        [25]
          /    |        /    \
       [5]   [15,17] [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;                  [20]
                /       \
           [15]        [25]
          /    |      /    \
       [10]   [17] [22]   [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;B. 형제 노드의 키 개수도 최소 개수인 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a) 부모 노드의 키 개수가 최소 개수를 초과하는 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;키를 삭제하고 부모 키를 내려 형제 노드와 병합합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  # 키(13) 삭제
                      [20]
                   /        \
           [10, 15]         [25]
          /    |   \      /    \
       [5]   [13] [17] [22]   [30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;                 [20]
               /      \
           [15]       [25]
          /   \      /    \
      [5, 10] [17] [22]   [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;b) 부모 노드의 키 개수도 최소 개수인 경우&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;키를 삭제합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;삭제된 키의 부모 노드의 키를 인접한 형제 노드에 붙입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;만약 새로 구성된 형제 노드가 최대 키 개수를 넘어갔다면, 삽입 연산에 따른 노드 분할 과정을 수행합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;형제 노드가 최대 키 개수를 넘지 않았지만, 부모 노드의 키 개수가 최소 개수를 만족하지 못한다면, 부모 키를 기준으로 2번을 다시 진행합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;삽입에 따른 분할 과정을 수행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt; # 키(22) 삭제
                   [20]
                 /        \
         [10, 15]        [25]
        /    |   \      /    \
     [5]   [13] [17] [22]   [30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;                   [20]
                /        \
          [10, 15]       []
        /    |    \        \
     [5]   [13]  [17]      [25, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;         [10, 15, 20]
        /    |   \    \
     [5]   [13] [17]  [25, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;              [15]
             /    \
         [10]      [20]
        /   \     /    \
     [5]   [13] [17]    [25, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;내부 노드에서 삭제하는 경우&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;리프 노드에 있는, 삭제하려는 키의 선임자 또는 후임자 키와 위치를 바꿉니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;선임자 : 나보다 작은 데이터들 중 가장 큰 데이터&lt;/li&gt;
&lt;li&gt;후임자 : 나보다 큰 데이터들 중 가장 작은 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;리프 노드에서 키 삭제&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위치가 바뀐 선임자 또는 후임자 키를 리프 노드에서 삭제합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이때 리프 노드에서의 삭제 방식은 앞서 설명한 리프 노드 삭제 규칙을 따릅니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;    # 키(10) 삭제
                  [20]
                /       \
             [10]      [25]
          /    |      /    \
       [5]   [15,17] [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;                 [20]
                /       \
            [15]       [25]
          /    |      /    \
       [5]   [10, 17] [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;                [20]
              /       \
           [15]        [25]
          /    |      /    \
       [5]   [17]   [22]  [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Algorithm/Concept</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/779</guid>
      <comments>https://wooono.tistory.com/779#entry779comment</comments>
      <pubDate>Wed, 13 Mar 2024 11:34:47 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 리스트(List), 튜플(Tuple), 셋(Set), 딕셔너리(Dictionary)의 차이</title>
      <link>https://wooono.tistory.com/778</link>
      <description>&lt;h3&gt;&lt;strong&gt;리스트(List)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가변(Mutable) : 요소의 추가, 삭제, 변경이 가능합니다.&lt;/li&gt;
&lt;li&gt;대괄호 &lt;code&gt;[]&lt;/code&gt;를 사용하여 생성합니다.&lt;/li&gt;
&lt;li&gt;중복된 요소를 가질 수 있습니다.&lt;/li&gt;
&lt;li&gt;순서가 있어 인덱스를 통해 요소에 접근 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주로 동적인 데이터 관리에 사용됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;my_list = [1, 2, 3]
my_list.append(4)    # 추가
my_list[1] = 5       # 변경
del my_list[0]       # 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;튜플(Tuple)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;불변(Immutable) : 한 번 생성된 후에는 요소의 추가, 삭제, 변경이 불가능합니다.&lt;/li&gt;
&lt;li&gt;소괄호 &lt;code&gt;()&lt;/code&gt;를 사용하여 생성합니다.&lt;/li&gt;
&lt;li&gt;중복된 요소를 가질 수 있습니다.&lt;/li&gt;
&lt;li&gt;순서가 있어 인덱스를 통해 요소에 접근 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주로 데이터의 불변성이 필요한 경우에 사용됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;my_tuple = (1, 2, 3)
# my_tuple.append(4)   # 에러 발생 - 튜플은 변경 불가능
# my_tuple[1] = 5      # 에러 발생 - 튜플은 변경 불가능&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;셋(Set)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가변(Mutable) : 요소의 추가, 삭제가 가능합니다.&lt;/li&gt;
&lt;li&gt;중괄호 &lt;code&gt;{}&lt;/code&gt;를 사용하여 생성합니다.&lt;/li&gt;
&lt;li&gt;중복된 요소를 허용하지 않습니다.&lt;/li&gt;
&lt;li&gt;순서가 없어 인덱스를 통한 접근이 불가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주로 고유한 값들을 저장하고 중복을 허용하지 않는 경우에 사용됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;my_set = {1, 2, 3}
my_set.add(4)    # 추가
my_set.remove(2) # 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;딕셔너리(Dictionary)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가변(Mutable) : 키-값 쌍을 추가, 삭제, 변경할 수 있습니다.&lt;/li&gt;
&lt;li&gt;중괄호 &lt;code&gt;{}&lt;/code&gt;를 사용하여 생성하며, 각 키-값은 콜론 &lt;code&gt;:&lt;/code&gt;으로 구분됩니다.&lt;/li&gt;
&lt;li&gt;키는 중복되지 않아야 하며, 값은 중복이 가능합니다.&lt;/li&gt;
&lt;li&gt;순서가 없어 인덱스를 통한 접근이 불가능하며, 키를 통해 값을 참조합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터의 키-값 쌍을 저장하고 검색하는데 사용됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;my_dict = {&amp;#39;key1&amp;#39;: &amp;#39;value1&amp;#39;, &amp;#39;key2&amp;#39;: &amp;#39;value2&amp;#39;}
my_dict[&amp;#39;key3&amp;#39;] = &amp;#39;value3&amp;#39;  # 추가
del my_dict[&amp;#39;key1&amp;#39;]         # 삭제
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Language/Python</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/778</guid>
      <comments>https://wooono.tistory.com/778#entry778comment</comments>
      <pubDate>Tue, 12 Mar 2024 11:23:45 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Xcoms란?</title>
      <link>https://wooono.tistory.com/776</link>
      <description>&lt;h3&gt;Xcoms란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;XComs(Cross-Communications)는 Apache Airflow에서 태스크 간에 메시지나 데이터를 교환하기 위한 메커니즘입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Xcoms를 통해 한 태스크가 생성한 데이터를 다른 태스크가 사용할 수 있으며, 이는 태스크 간의 의존성 관리와 데이터 공유에 매우 유용합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XComs는 Key-Value 쌍으로 데이터를 저장하며, Airflow의 메타데이터 데이터베이스에 저장됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;태스크에서 데이터를 XCom에 push하는 가장 간단한 방법은 태스크의 실행 함수에서 값을 반환하는 것입니다. Airflow는 반환된 값을 자동으로 XCom에 push합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
  @task
  def push_task():
      # XCom으로 메시지를 push
      return &amp;#39;Hello from the push side!&amp;#39;

  @task
  def pull_task(**kwargs):
      # XCom에서 메시지를 pull
      message = kwargs[&amp;#39;ti&amp;#39;].xcom_pull(task_ids=&amp;quot;push_task&amp;quot;)
      print(f&amp;quot;Received message: {message}&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UCw9G/btsFFlinITM/uvCExga22bEEk6MkINj0Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UCw9G/btsFFlinITM/uvCExga22bEEk6MkINj0Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UCw9G/btsFFlinITM/uvCExga22bEEk6MkINj0Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUCw9G%2FbtsFFlinITM%2FuvCExga22bEEk6MkINj0Mk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;보다 세밀한 데이터 관리가 필요할 땐, 키-값 쌍을 지정해서 XCom에 Push 할 수도 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  @task
  def push_task(**kwargs):
      # 키를 지정하여 XCom으로 메시지를 push
      message = &amp;#39;Hello from the push side!&amp;#39;
      kwargs[&amp;#39;ti&amp;#39;].xcom_push(key=&amp;#39;message&amp;#39;, value=message)

  @task
  def pull_task(**kwargs):
      # 지정된 키로 XCom에서 메시지를 pull
      message = kwargs[&amp;#39;ti&amp;#39;].xcom_pull(task_ids=&amp;quot;push_task&amp;quot;, key=&amp;#39;message&amp;#39;)
      print(f&amp;quot;Received message: {message}&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA6gb1/btsFEMmZPNu/26udaBveB0HSDPrCZmScpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA6gb1/btsFEMmZPNu/26udaBveB0HSDPrCZmScpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA6gb1/btsFEMmZPNu/26udaBveB0HSDPrCZmScpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA6gb1%2FbtsFEMmZPNu%2F26udaBveB0HSDPrCZmScpk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data/Airflow</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/776</guid>
      <comments>https://wooono.tistory.com/776#entry776comment</comments>
      <pubDate>Fri, 8 Mar 2024 14:20:19 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] ShortCircuitOperator란?</title>
      <link>https://wooono.tistory.com/775</link>
      <description>&lt;h3&gt;&lt;strong&gt;ShortCircuitOperator란?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;지정된 조건이 참(True)일 때만 다운스트림 작업을 실행하는 Operator입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지정된 조건이 거짓(False)이라면, ShortCircuitOperator는 다운스트림 작업의 실행을 건너뜁니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ignore_downstream_trigger_rules 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ShortCircuitOperator가 거짓(False)된 조건으로 인하여 다운스트림 작업을 건너뛸 때,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다운스트림 작업들의 트리거 규칙을 무시할 것인지 결정하는 설정입니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;ignore_downstream_trigger_rules=True를 적용한 경우 (기본 설정)&lt;ul&gt;
&lt;li&gt;조건이 참(True)일 때 : ShortCircuitOperator 뒤에 오는 모든 다운스트림 작업을 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;조건이 거짓(False)일 때 : 모든 다운스트림을 건너뜁니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ignore_downstream_trigger_rules=False를 적용한 경우&lt;ul&gt;
&lt;li&gt;조건이 참(True)일 때 : ShortCircuitOperator 뒤에 오는 모든 다운스트림 작업을 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;조건이 거짓(False)일 때 : 특정 트리거 룰이 있는 다운스트림을 제외한 모든 다운스트림을 건너뜁니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ignore_downstream_trigger_rules=True를 적용한 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;short_circuit Task의 조건이 참(True)이면, ShortCircuitOperator 뒤에 오는 모든 다운스트림 작업을 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  @task.short_circuit(ignore_downstream_trigger_rules=True)
  def step_1():
      return True

  @task
  def step_2():
      return &amp;quot;step_2&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z1BNu/btsFC0Tsmyv/RFWHOjzfb4DSEVIP4f3cLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z1BNu/btsFC0Tsmyv/RFWHOjzfb4DSEVIP4f3cLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z1BNu/btsFC0Tsmyv/RFWHOjzfb4DSEVIP4f3cLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz1BNu%2FbtsFC0Tsmyv%2FRFWHOjzfb4DSEVIP4f3cLK%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;short_circuit Task의 조건이 거짓(False)이면, 모든 다운스트림을 건너뜁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  @task.short_circuit(ignore_downstream_trigger_rules=True)
  def step_1():
      return False

  @task
  def step_2():
      return &amp;quot;step_2&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sNFEQ/btsFDEJwMVh/PLJG2ecDOjiKDcmP8J8SF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sNFEQ/btsFDEJwMVh/PLJG2ecDOjiKDcmP8J8SF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sNFEQ/btsFDEJwMVh/PLJG2ecDOjiKDcmP8J8SF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsNFEQ%2FbtsFDEJwMVh%2FPLJG2ecDOjiKDcmP8J8SF1%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ignore_downstream_trigger_rules=False를 적용한 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;short_circuit Task의 조건이 참(True)이면, ShortCircuitOperator 뒤에 오는 모든 다운스트림 작업을 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  @task.short_circuit(ignore_downstream_trigger_rules=False)
  def step_1():
      return True

  @task
  def step_2():
      return &amp;quot;step_2&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB862c/btsFEuGjtlC/L2SR58QYK4mXZWBfhETZOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB862c/btsFEuGjtlC/L2SR58QYK4mXZWBfhETZOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB862c/btsFEuGjtlC/L2SR58QYK4mXZWBfhETZOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB862c%2FbtsFEuGjtlC%2FL2SR58QYK4mXZWBfhETZOk%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;short_circuit Task의 조건이 거짓(False)이면, 특정 트리거 룰이 있는 다운스트림을 제외한 모든 다운스트림을 건너뜁니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  @task.short_circuit(ignore_downstream_trigger_rules=False)
  def step_1():
      return False

  @task
  def step_2():
      return &amp;quot;step_2&amp;quot;

  # NONE_FAILED = 이전 단계들이 모두 성공 또는 스킵일 경우에만 실행
  @task(trigger_rule=TriggerRule.NONE_FAILED) 
  def step_3():
      return &amp;quot;step_3&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m8u7A/btsFCybMJKE/rKLY8lBnxXMuV8goIyWHRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m8u7A/btsFCybMJKE/rKLY8lBnxXMuV8goIyWHRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m8u7A/btsFCybMJKE/rKLY8lBnxXMuV8goIyWHRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm8u7A%2FbtsFCybMJKE%2FrKLY8lBnxXMuV8goIyWHRK%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/_modules/airflow/operators/python.html#ShortCircuitOperator&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/_modules/airflow/operators/python.html#ShortCircuitOperator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data/Airflow</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/775</guid>
      <comments>https://wooono.tistory.com/775#entry775comment</comments>
      <pubDate>Fri, 8 Mar 2024 11:09:45 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] GCP Load Balancer에 특정 IP만 접근 허용하는 방법</title>
      <link>https://wooono.tistory.com/774</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 Terraform을 통해, GCP Load Balancer에 특정 IP 접근만 허용하는 방법에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결론부터 말씀드리면, Google Cloud Armor security policy를 google_compute_backend_service에 연결하는 방식으로 접근 제어를 설정할 수 있습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;google_compute_backend_service는 Google Cloud Load Balancing 사용 시, 트래픽을 여러 인스턴스 그룹에 분산하는 데 사용되는 리소스이며, 해당 리소스의 추가적인 설명은 아래 링크를 참고하시면 됩니다.&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wooono.tistory.com/699&quot;&gt;https://wooono.tistory.com/699&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud Armor security policy는 google_compute_security_policy&lt;strong&gt;로 생성할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;먼저, Google Cloud Armor security policy를 생성합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  resource &amp;quot;google_compute_security_policy&amp;quot; &amp;quot;my_security_policy&amp;quot; {
    name = &amp;quot;my-security-policy&amp;quot;

    rule {
      action   = &amp;quot;allow&amp;quot;
      priority = 1000
      match {
        versioned_expr = &amp;quot;SRC_IPS_V1&amp;quot;
        config {
          src_ip_ranges = [&amp;quot;&amp;lt;YOUR_IP_RANGE&amp;gt;&amp;quot;]
        }
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;YOUR_IP_RANGE&amp;gt;&lt;/code&gt;&lt;/strong&gt;는 허용하려는 IP 범위입니다.&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;strong&gt;&lt;code&gt;[&amp;quot;192.168.1.0/24&amp;quot;]&lt;/code&gt;&lt;/strong&gt;처럼 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이 정책은 지정된 IP 범위에서의 접근만을 허용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이제 google_compute_backend_service를 생성하고, 앞서 생성한 security policy를 적용합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  resource &amp;quot;google_compute_backend_service&amp;quot; &amp;quot;my_backend_service&amp;quot; {
    name        = &amp;quot;my-backend-service&amp;quot;
    protocol    = &amp;quot;HTTP&amp;quot;
    port_name   = &amp;quot;http&amp;quot;
    timeout_sec = 10

    backend {
      group = google_compute_instance_group.my_instance_group.self_link
    }

    security_policy = google_compute_security_policy.my_security_policy.self_link
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;google_compute_instance_group.my_instance_group.self_link 부분은 실제 인스턴스 그룹의 Terraform 리소스 ID로 교체해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_backend_service#security_policy&quot;&gt;https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_backend_service#security_policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Terraform</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/774</guid>
      <comments>https://wooono.tistory.com/774#entry774comment</comments>
      <pubDate>Thu, 7 Mar 2024 17:10:53 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] Pub/Sub Topic에 Message를 Publish 하는 방법 with Python</title>
      <link>https://wooono.tistory.com/773</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 GCP Pub/Sub Topic에 Message를 Publish하는 간단한 예제 코드를 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트를 위한 간단한 코드이기 때문에, 필요에 따라 수정해서 사용하시면 됩니다 ㅎㅎ..&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from google.cloud import pubsub_v1
import json

# 프로젝트 ID 및 서비스 계정 키 파일 경로 설정
project_id = &amp;quot;your-project-id&amp;quot;
key_file_path = &amp;quot;path/to/your/service-account-file.json&amp;quot;

# Pub/Sub 클라이언트 초기화
pubsub_client = pubsub_v1.PublisherClient.from_service_account_file(key_file_path)

# 토픽 이름 설정
pub_name = &amp;quot;your-topic-name&amp;quot;

# 테스트 데이터 준비
push_test_data = {
    &amp;quot;key&amp;quot;: &amp;quot;value&amp;quot;
}

# 데이터를 JSON 형식으로 인코딩
json_data = json.dumps(push_test_data)
data = json_data.encode(&amp;#39;utf-8&amp;#39;)

# 토픽 경로 생성 및 메시지 게시
topic_path = pubsub_client.topic_path(project_id, pub_name)
future = pubsub_client.publish(topic_path, data=data)

# 게시 결과 확인
future.result()&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://cloud.google.com/pubsub/docs/publisher?hl=ko#publish-messages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>GCP/PubSub</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/773</guid>
      <comments>https://wooono.tistory.com/773#entry773comment</comments>
      <pubDate>Thu, 7 Mar 2024 16:46:38 +0900</pubDate>
    </item>
    <item>
      <title>[DP] 백준 2011번 &amp;ldquo;암호코드&amp;rdquo; Python 풀이</title>
      <link>https://wooono.tistory.com/772</link>
      <description>&lt;h3&gt;문제 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2011&quot;&gt;https://www.acmicpc.net/problem/2011&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;풀이&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;마지막 숫자까지 해석할 수 있는 경우의 수는 아래 두 가지 경우의 수의 합과 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;마지막 1개 숫자 이전의 숫자들로 가능했던 경우의 수&lt;/li&gt;
&lt;li&gt;마지막 2개 숫자 이전의 숫자들로 가능했던 경우의 수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;따라서, “251”을 해석할 수 있는 경우의 수는 아래 두가지 경우의 수의 합과 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;“마지막 1개 숫자 이전의 숫자들로 가능했던 조합” + “1”&lt;ul&gt;
&lt;li&gt;즉, “25”의 경우의 수와 동일합니다.&lt;/li&gt;
&lt;li&gt;마지막 1개의 숫자가 0 이상이기 때문에 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;“마지막 2개 숫자 이전의 숫자들로 가능했던 조합” + “51”&lt;ul&gt;
&lt;li&gt;즉, “2”의 경우의 수와 동일합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 경우엔 마지막 2개의 숫자가 10 이상 26 이하가 아니기 때문에 불가능합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다음으로, “2511”을 해석할 수 있는 경우의 수는 아래 두 가지 경우의 수의 합과 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;“마지막 1개 숫자 이전의 숫자들로 가능했던 조합” + “1”&lt;ul&gt;
&lt;li&gt;즉, “251”의 경우의 수와 동일합니다.&lt;/li&gt;
&lt;li&gt;마지막 1개의 숫자가 0 이상이기 때문에 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;“마지막 2개 숫자 이전의 숫자들로 가능했던 조합” + “11”&lt;ul&gt;
&lt;li&gt;즉, “25”의 경우의 수와 동일합니다.&lt;/li&gt;
&lt;li&gt;마지막 2개의 숫자가 10 이상 26 이하이기 때문에 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;마지막으로, “25114”를 해석할 수 있는 경우의 수는 아래 두 가지 경우의 수의 합과 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;“마지막 1개 숫자 이전의 숫자들로 가능했던 조합” + “4”&lt;ul&gt;
&lt;li&gt;즉, “2511”의 경우의 수와 동일합니다.&lt;/li&gt;
&lt;li&gt;마지막 1개의 숫자가 0 이상이기 때문에 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;“마지막 2개 숫자 이전의 숫자들로 가능했던 조합” + “14”&lt;ul&gt;
&lt;li&gt;즉, “251”의 경우의 수와 동일합니다.&lt;/li&gt;
&lt;li&gt;마지막 2개의 숫자가 10 이상 26 이하이기 때문에 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

number = list(map(int, sys.stdin.readline().rstrip()))

# dp[i] =&amp;gt; i번째 숫자까지 해석할 수 있는 경우의 수
dp = [0] * (len(number)+1)
dp[0] = 1
dp[1] = 1 # 1번째 숫자까지 해석할 수 있는 경우의 수는 1이므로 미리 계산

# 암호가 잘못되어 해석할 수 없는 경우 (첫 번째 숫자가 0일 경우)
if (number[0] == 0):
    print(0)
else:
    for i in range(1, len(number)):

        # dp[0]이 사용되고 있으므로 dp 접근용 idx 재선언
        j = i+1

        if (number[i] &amp;gt; 0):     # 마지막 1개의 숫자가 0이면 대응되는 문자가 없음
            dp[j] += dp[j-1]    # 마지막 1개 숫자 이전의 숫자들로 가능했던 경우의 수와 동일하기 때문에, 해당 경우의 수를 더해주기

        if (10 &amp;lt;= 10 * number[i-1] + number[i] &amp;lt;= 26): # 마지막 2개의 숫자가 10 이상 26 이하라면
            dp[j] += dp[j-2]    # 마지막 2개 숫자 이전의 숫자들로 가능했던 경우의 수와 동일하기 때문에, 해당 경우의 수를 더해주기

    print(dp[-1]%1000000)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@jxlhe46/%EB%B0%B1%EC%A4%80-2293%EB%B2%88.-%EB%8F%99%EC%A0%84-1-bfi120m5&quot;&gt;https://velog.io/@jxlhe46/백준-2293번.-동전-1-bfi120m5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Algorithm/Dynamic Programming</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/772</guid>
      <comments>https://wooono.tistory.com/772#entry772comment</comments>
      <pubDate>Sun, 3 Dec 2023 16:10:30 +0900</pubDate>
    </item>
    <item>
      <title>[DP] 백준 2293번 &amp;ldquo;동전 1&amp;rdquo; Python 풀이</title>
      <link>https://wooono.tistory.com/771</link>
      <description>&lt;h3&gt;문제 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2293&quot;&gt;https://www.acmicpc.net/problem/2293&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;풀이&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;우선, 동전 1원으로 10을 만들 수 있는 경우의 수와&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;동전 1, 2원으로 10을 만들 수 있는 경우의 수를 구함으로써 규칙을 찾을 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxFwe/btsBl6vOJw4/ZfzLkBVK937QxS2OJQ4kkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxFwe/btsBl6vOJw4/ZfzLkBVK937QxS2OJQ4kkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxFwe/btsBl6vOJw4/ZfzLkBVK937QxS2OJQ4kkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxFwe%2FbtsBl6vOJw4%2FZfzLkBVK937QxS2OJQ4kkK%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;즉, 새로 갱신되는 dp[n]의 값은 이전의 dp[n]의 값에 dp[n - 현재 사용한 동전의 종류 크기] 가 됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;해당 규칙을 통해 1, 2, 5원으로 10을 만들 수 있는 경우의 수를 계산할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;80%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLcS1L/btsBjq2UFax/XNeOl8eAPK0vYdRI1BzjJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLcS1L/btsBjq2UFax/XNeOl8eAPK0vYdRI1BzjJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLcS1L/btsBjq2UFax/XNeOl8eAPK0vYdRI1BzjJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLcS1L%2FbtsBjq2UFax%2FXNeOl8eAPK0vYdRI1BzjJ0%2Fimg.png&quot; width=&quot;80%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;추가적으로, dp[0]은 주어진 동전을 통해 0원을 만들 수 있는 경우의 수인데,&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;아무 동전을 사용하지 않았을 때 0을 만들 수 있으므로, 경우의 수는 1입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

n, k = map(int, sys.stdin.readline().split())

coins = []
for _ in range(n):
    coins.append(int(sys.stdin.readline()))

dp = [0] * (k+1)
dp[0] = 1

for coin in coins:
    for j in range(coin, k+1):
        dp[j] = dp[j] + dp[j-coin]

print(dp[k])&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@jxlhe46/%EB%B0%B1%EC%A4%80-2293%EB%B2%88.-%EB%8F%99%EC%A0%84-1-bfi120m5&quot;&gt;https://velog.io/@jxlhe46/백준-2293번.-동전-1-bfi120m5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Algorithm/Dynamic Programming</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/771</guid>
      <comments>https://wooono.tistory.com/771#entry771comment</comments>
      <pubDate>Sat, 2 Dec 2023 19:38:42 +0900</pubDate>
    </item>
    <item>
      <title>[Greedy] 백준 17609번 &amp;ldquo;회문&amp;rdquo; Python 풀이</title>
      <link>https://wooono.tistory.com/770</link>
      <description>&lt;h3&gt;문제 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17609&quot;&gt;https://www.acmicpc.net/problem/17609&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;풀이&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;왼쪽 포인터 값과 오른쪽 포인터 값이 동일할 경우, 양쪽 포인터를 그 다음 포인터로 이동&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;왼쪽 포인터 값과 오른쪽 포인터 값이 동일하지 않을 경우&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;왼쪽 포인터를 오른쪽으로 이동했을 때, 양쪽 포인터 내부 문자열이 회문이 되는지 확인&lt;/li&gt;
&lt;li&gt;오른쪽 포인터를 왼쪽으로 이동했을 때, 양쪽 포인터 내부 문자열이 회문이 되는지 확인&lt;/li&gt;
&lt;li&gt;나머지 경우(회문이나 유사회문도 아닌 일반 문자열인 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

t = int(sys.stdin.readline())

for i in range(t):

    result = 0

    str = list(sys.stdin.readline().rstrip())

    left_idx = 0
    right_idx = len(str)-1

    while (left_idx &amp;lt; right_idx):

        # 왼쪽 포인터 값과 오른쪽 포인터 값이 동일할 경우
        if (str[left_idx] == str[right_idx]):
            left_idx += 1
            right_idx -= 1

        # 왼쪽 포인터 값과 오른쪽 포인터 값이 동일하지 않을 경우
        else:
            # 왼쪽 포인터를 오른쪽으로 이동했을 때, 양쪽 포인터 내부 문자열이 회문이 되는지 확인
            if (str[left_idx+1:right_idx+1] == str[left_idx+1:right_idx+1][::-1]):
                result = 1
                break

            # 오른쪽 포인터를 왼쪽으로 이동했을 때, 양쪽 포인터 내부 문자열이 회문이 되는지 확인
            elif (str[left_idx:right_idx] == str[left_idx:right_idx][::-1]):
                result = 1
                break

            # 회문이나 유사회문도 아닌 일반 문자열인 경우
            else:
                result = 2
                break

    print(result)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/Greedy</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/770</guid>
      <comments>https://wooono.tistory.com/770#entry770comment</comments>
      <pubDate>Sun, 26 Nov 2023 16:03:11 +0900</pubDate>
    </item>
    <item>
      <title>[Greedy] 백준 2812번 &amp;ldquo;크게 만들기&amp;rdquo; Python 풀이</title>
      <link>https://wooono.tistory.com/769</link>
      <description>&lt;h3&gt;문제 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2812&quot;&gt;https://www.acmicpc.net/problem/2812&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;풀이&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;큰 수가 되려면 높은 자리수의 숫자가 커야합니다.&lt;/li&gt;
&lt;li&gt;그래서 스택을 이용합니다.&lt;/li&gt;
&lt;li&gt;예를 들어,  4177252841 이고 k = 4이면 775841이 최대값이겠죠?&lt;/li&gt;
&lt;li&gt;먼저 4를 stack에 집어넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [4]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 1을 stack에 넣어야 하는데, stack의 최근 값인 4보다 1이 작거나 같죠? 그래서 그냥 stack에 1을 넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [4, 1]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 7을 stack에 넣어야 하는데, stack의 최근 값인 1보다 7이 크죠? 그래서 1을 pop 합니다.&lt;ul&gt;
&lt;li&gt;stack = [4], k = 3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아직 stack의 최근 값인 4보다 7이 크죠? 그래서 4를 pop 합니다.&lt;ul&gt;
&lt;li&gt;stack = [], k = 2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 stack에 7을 집어넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [7]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 7을 stack에 넣어야 하는데, stack의 최근 값인 7보다 7이 작거나 같죠? 그래서 그냥 stack에 7을 넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [7, 7]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 2를 stack에 넣어야 하는데, stack의 최근 값인 7보다 2가 작거나 같죠? 그래서 그냥 stack에 2를 넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [7, 7, 2]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 5를 stack에 넣어야 하는데, stack의 최근 값인 2보다 5가 크죠? 그래서 2를 pop하고 5를 넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [7, 7, 5], k = 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 2를 stack에 넣어야 하는데, stack의 최근 값인 5보다 2가 작거나 같죠? 그래서 그냥 2를 넣습니다.&lt;ul&gt;
&lt;li&gt;stack = [7, 7, 5, 2]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 8을 stack에 넣어야 하는데, stack의 최근 값인 2보다 8이 크죠? 그래서 2를 pop 합니다.&lt;ul&gt;
&lt;li&gt;stack = [7, 7, 5], k = 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 k=0 이므로 나머지 숫자들은 그냥 추가합니다.&lt;ul&gt;
&lt;li&gt;stack = [7, 7, 5, 8, 4, 1]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

N, K = map(int, sys.stdin.readline().split())
num = list(sys.stdin.readline())
k = K # 기존 K 값 저장
stack = []

for i in range(len(num)):
    # 뺄 수 있는 횟수가 남아있고, 스택의 값이 남아있고, 현재 값이 stack의 최근값보다 클 경우 POP
    while (k &amp;gt; 0 and stack and num[i] &amp;gt; stack[-1]):
        stack.pop()
        k -= 1
    # 그 외 경우엔 현재 값을 stack에 삽입
    stack.append(num[i])

# 예외 케이스) 5 2 / 19876 일 경우, 답은 987
print(&amp;quot;&amp;quot;.join(stack[:N-K]))&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/Greedy</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/769</guid>
      <comments>https://wooono.tistory.com/769#entry769comment</comments>
      <pubDate>Sat, 25 Nov 2023 20:08:53 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] 리소스를 생성하지 않고 리소스 구성 파일만 출력</title>
      <link>https://wooono.tistory.com/768</link>
      <description>&lt;h3&gt;리소스를 생성하지 않고 리소스 구성 파일만 출력&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;kubectl 시, 리소스를 생성하지 않고 리소스 구성 파일만 출력할 수 있는 옵션이 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  kubectl run redis \
  --image=redis \
  --dry-run=client \
  -o yaml \
  &amp;gt; redis-definition.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;--dry-run=client&lt;/strong&gt; : 명령어를 실행하지 않고 구성만을 출력합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-o yaml&lt;/strong&gt; : 출력 형식을 YAML로 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;gt; redis-definition.yaml&lt;/strong&gt; : 명령어의 출력을 파일에 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/768</guid>
      <comments>https://wooono.tistory.com/768#entry768comment</comments>
      <pubDate>Tue, 21 Nov 2023 17:15:09 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] StatsD Exporter + Prometheus + Grafana On GKE</title>
      <link>https://wooono.tistory.com/767</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Airflow 운영에 있어서 Airflow 모니터링은 중요한 요소이며, 모니터링은 크게 두 가지 측면으로 구분할 수 있습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;서비스 측면 (Airflow 컨테이너 동작 여부, DAG 정상 실행 여부 등)&lt;/li&gt;
&lt;li&gt;시스템 측면 (CPU, Memory 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 Airflow의 서비스 측면 모니터링을 위해, StatsD Exporter + Prometheus + Grafana를 구성하는 방법에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StatsD Exporter&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;StatsD는 애플리케이션에서 발생하는 이벤트 및 성능 지표를 수집하고, 이를 모니터링 시스템에 전송하는 데 사용되는 네트워크 데몬 및 프로토콜입니다.&lt;/li&gt;
&lt;li&gt;Airflow 내부적으로 생성된 Metric은, StatsD를 통해 StatsD Exporter에 Push 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StatsD Exporter는 StatsD에서 전송된 메트릭을 Prometheus가 이해할 수 있는 형식으로 변환하여 제공하는 중간자 역할입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prometheus&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prometheus는 모니터링 시스템 및 Timeseries 데이터용 데이터베이스입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;오픈소스로, 메트릭 수집 및 대시보드를 구축하는데 Grafana와 함께 많이 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grafana&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Grafana는 대시보드 툴입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;시계열 데이터에 대한 대시보드에 특화되어 있는 대시보드 툴이며 Graphite, InfluxDB, Prometheus 등을 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;사전 구성 환경&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Airflow와 Grafana는 GKE에 Helm Chart를 통해 설치되어 있으며, 설치 과정은 생략하겠습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Airflow Helm Chart : &lt;a href=&quot;https://github.com/airflow-helm/charts&quot;&gt;https://github.com/airflow-helm/charts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Grafana Helm Chart : &lt;a href=&quot;https://grafana.github.io/helm-charts&quot;&gt;https://grafana.github.io/helm-charts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;StatsD Exporter + Prometheus + Grafana 구조&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tjTwL/btsAs7qOLCE/6sOTtnOTdzSq2acCK6ZKH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tjTwL/btsAs7qOLCE/6sOTtnOTdzSq2acCK6ZKH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tjTwL/btsAs7qOLCE/6sOTtnOTdzSq2acCK6ZKH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtjTwL%2FbtsAs7qOLCE%2F6sOTtnOTdzSq2acCK6ZKH1%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Airflow 내부적으로 생성된 Metric은, StatsD를 통해 StatsD Exporter에 Push 됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StatsD Exporter는 StatsD로부터 받은 Metric을 저장해둡니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prometheus는 StatsD Exporter가 저장하고 있는 Airflow Metric을 주기적으로 Scrape(Pull) 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grafana는 Prometheus에 저장된 Airflow Metric을 Pull 하여 대시보드를 구성합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;진행 순서&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StatsD Exporter 사용을 위한 Airflow Helm Chart Value 설정&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prometheus 관련 자원 생성 및 StatsD Exporter Scrape 설정&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Prometheus 자원 생성은 Terraform을 통해 진행하겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grafana의 Datasource로 Prometheus 추가&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;StatsD Exporter 사용을 위한 Airflow Helm Chart Value 설정&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Airflow가 StatsD Exporter에 Metric을 보내도록 설정합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  airflow:
    config:
      AIRFLOW__METRICS__STATSD_ON: &amp;#39;true&amp;#39;
      AIRFLOW__METRICS__STATSD_HOST: &amp;#39;localhost&amp;#39;
      AIRFLOW__METRICS__STATSD_PORT: &amp;#39;9125&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Airflow의 Webserver, Scheduler, Worker Pod에 StatsD Exporter가 extracontainer(sidecar)로 동작하도록 설정합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  airflow:
    extraContainers:
      - name: prometheus-statsd-exporter
        image: prom/statsd-exporter:latest
        imagePullPolicy: Always
        args:
          - --web.listen-address=:9102
          - --statsd.listen-udp=:9125
        ports:
          - containerPort: 9102
            protocol: TCP
          - containerPort: 9125
            protocol: UDP
          - containerPort: 9125
            protocol: TCP&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prometheus는 StatsD Exporter의 URL 주소(K8S Service DNS)를 통해 Metric을 Scrape(Pull) 해야하므로, extramanifests를 사용해 Webserver, Scheduler, Worker의 StatsD Exporter 용 Service를 생성합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  extraManifests:
    - apiVersion: v1
      kind: Service
      metadata:
        name: scheduler-statsd-exporter
      spec:
        selector:
          app: airflow
          component: scheduler
        ports:
          - port: 9102
            targetPort: 9102&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Webserver와 Worker의 Service 생성 코드는 생략하겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이후, 생성한 StatsD Exporter의 Service DNS에 /metrics 경로를 추가하여, StatsD Exporter가 Airflow의 Metric을 잘 받아오고 있는지 확인할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAlLNr/btsAw2V0KjT/d41u1y6LcQh7yvWK7EJGkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAlLNr/btsAw2V0KjT/d41u1y6LcQh7yvWK7EJGkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAlLNr/btsAw2V0KjT/d41u1y6LcQh7yvWK7EJGkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAlLNr%2FbtsAw2V0KjT%2Fd41u1y6LcQh7yvWK7EJGkk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해당 예제에선 “&lt;a href=&quot;http://scheduler-statsd-exporter.default.svc.cluster.local:9102/metrics%E2%80%9D&quot;&gt;http://scheduler-statsd-exporter.default.svc.cluster.local:9102/metrics”&lt;/a&gt; 경로를 통해 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Airflow Helm Chart Value는 아래 링크를 통해 참조하실 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/airflow-helm/charts/blob/main/charts/airflow/values.yaml&quot;&gt;https://github.com/airflow-helm/charts/blob/main/charts/airflow/values.yaml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Prometheus 관련 자원 생성 및 StatsD Exporter Scrape 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;kubernetes_config_map 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  resource &amp;quot;kubernetes_config_map&amp;quot; &amp;quot;prometheus_config&amp;quot; {
    metadata {
      name = &amp;quot;prometheus-config&amp;quot;
    }

    data = {
      &amp;quot;prometheus.yml&amp;quot; = &amp;lt;&amp;lt;EOF
  global:
    scrape_interval: 30s
    evaluation_interval: 30s
    scrape_timeout: 10s

  scrape_configs:
    - job_name: &amp;#39;airflow-scheduler-metric&amp;#39;
      static_configs:
        - targets: [&amp;#39;scheduler-statsd-exporter.default.svc.cluster.local:9102&amp;#39;]
  EOF
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Deployment를 통해 Prometheus Container가 시작될 때,&lt;/li&gt;
&lt;li&gt;Prometheus가 기본 설정 파일(prometheus.yml)을 Container 내부에서 찾아서 실행될 수 있도록,&lt;/li&gt;
&lt;li&gt;Prometheus Container 내부에 마운트할 파일을 kubernetes_config_map을 통해 정의합니다.&lt;/li&gt;
&lt;li&gt;Prometheus 기본 설정 파일(prometheus.yml) 작성 방법은 아래 링크를 참조하실 수 있습니다.&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/configuration/configuration/&quot;&gt;https://prometheus.io/docs/prometheus/latest/configuration/configuration/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위 예제의 scrape_configs에는 Scheduler의 StatsD Exporter만 scrape하도록 설정되어있으며,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webserver와 Worker의 StatsD Exporter를 Scrape하는 코드는 생략하겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prometheus Deployment 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  resource &amp;quot;kubernetes_deployment&amp;quot; &amp;quot;prometheus&amp;quot; {
    metadata {
      name = &amp;quot;prometheus&amp;quot;
    }
    spec {
      replicas = 1
      selector {
        match_labels = {
          app = &amp;quot;prometheus&amp;quot;
        }
      }
      template {
        metadata {
          labels = {
            app = &amp;quot;prometheus&amp;quot;
          }
        }
        spec {
          container {
            image = &amp;quot;prom/prometheus&amp;quot;
            name  = &amp;quot;prometheus&amp;quot;
            args = [
              &amp;quot;--config.file=/etc/prometheus/prometheus.yml&amp;quot;
            ]
            port {
              container_port = 9090
            }
            volume_mount {
              mount_path = &amp;quot;/etc/prometheus/&amp;quot;
              name       = &amp;quot;prometheus-config-volume&amp;quot;
            }
          }
          volume {
            name = &amp;quot;prometheus-config-volume&amp;quot;
            config_map {
              name = kubernetes_config_map.prometheus_config.metadata[0].name
            }
          }
        }
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;kubernetes_config_map이 Prometheus Container 내부로 volume mount 될 수 있도록 설정한 뒤,&lt;/li&gt;
&lt;li&gt;Prometheus가 기본 설정 파일을 Container 내부에서 찾아 실행될 수 있도록,&lt;/li&gt;
&lt;li&gt;Prometheus Container 실행 인자로 기본 설정 파일의 경로를 넣어줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prometheus Service 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  resource &amp;quot;kubernetes_service&amp;quot; &amp;quot;prometheus&amp;quot; {

    metadata {
      name = &amp;quot;prometheus&amp;quot;
    }
    spec {
      type = &amp;quot;ClusterIP&amp;quot;
      selector = {
        app = &amp;quot;prometheus&amp;quot;
      }
      port {
        port        = 9090
        target_port = 9090
      }
      session_affinity = &amp;quot;None&amp;quot;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Grafana가 Prometheus의 Service DNS 주소를 통해 Prometheus를 Datasource로 등록할 수 있도록 Prometheus 용 Service를 생성합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Grafana의 Datasource로 Prometheus 추가&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Grafana 대시보드 접근 → 좌측 톱니바퀴 클릭&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data sources 카테고리에서 “Add new data source” 클릭 → “Prometheus” 선택&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus Data source의 Name을 지정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP URL에 Prometheus의 Service DNS를 삽입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해당 예제에선 “&lt;a href=&quot;http://prometheus.default.svc.cluster.local:9090%E2%80%9D&quot;&gt;http://prometheus.default.svc.cluster.local:9090”&lt;/a&gt; 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save &amp;amp; test 클릭&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이후, 대시보드를 생성한 뒤 Data source를 Prometheus로 지정하고 원하는 패널을 만들면 됩니다.&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdb28k/btsAxJPHp9W/x54hqR9kKS3he18dwGyAV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdb28k/btsAxJPHp9W/x54hqR9kKS3he18dwGyAV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdb28k/btsAxJPHp9W/x54hqR9kKS3he18dwGyAV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdb28k%2FbtsAxJPHp9W%2Fx54hqR9kKS3he18dwGyAV0%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://magaetube.github.io/data/Airflow-Setup-Step-Monitoring/&quot;&gt;https://magaetube.github.io/data/Airflow-Setup-Step-Monitoring/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kodegeek-com.medium.com/monitoring-airflow-with-prometheus-statsd-grafana-and-spicing-the-game-with-b-tree-filesystem-37a6cc072a38&quot;&gt;https://kodegeek-com.medium.com/monitoring-airflow-with-prometheus-statsd-grafana-and-spicing-the-game-with-b-tree-filesystem-37a6cc072a38&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://swalloow.github.io/airflow-on-kubernetes-3/&quot;&gt;https://swalloow.github.io/airflow-on-kubernetes-3/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://databand.ai/blog/everyday-data-engineering-monitoring-airflow-with-prometheus-statsd-and-grafana/&quot;&gt;https://databand.ai/blog/everyday-data-engineering-monitoring-airflow-with-prometheus-statsd-and-grafana/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.redhat.com/en/blog/monitoring-apache-airflow-using-prometheus&quot;&gt;https://www.redhat.com/en/blog/monitoring-apache-airflow-using-prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://towardsdatascience.com/airflow-in-docker-metrics-reporting-83ad017a24eb&quot;&gt;https://towardsdatascience.com/airflow-in-docker-metrics-reporting-83ad017a24eb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://morioh.com/a/b2881fbbd589/airflow-in-docker-metrics-reporting&quot;&gt;https://morioh.com/a/b2881fbbd589/airflow-in-docker-metrics-reporting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/metrics.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/metrics.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/prometheus/statsd_exporter&quot;&gt;https://github.com/prometheus/statsd_exporter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data/Airflow</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/767</guid>
      <comments>https://wooono.tistory.com/767#entry767comment</comments>
      <pubDate>Fri, 17 Nov 2023 17:27:25 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 특정 리소스의 변경사항만 apply 하는 방법</title>
      <link>https://wooono.tistory.com/766</link>
      <description>&lt;h3&gt;terraform apply -target=resource_type.resource_name&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;terraform은 apply 시, -target 옵션을 통해 특정 리소스의 변경사항만 apply 할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;예를 들어, 만약 AWS의 S3 버킷을 정의하는 Terraform 코드가 있다면&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  resource &amp;quot;aws_s3_bucket&amp;quot; &amp;quot;example_bucket&amp;quot; {
    bucket = &amp;quot;example-bucket&amp;quot;
    acl    = &amp;quot;private&amp;quot;
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;아래 명령어를 통해 해당 리소스에 대한 변경사항만을 apply 할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  terraform apply -target=aws_s3_bucket.example_bucket&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;‘aws_s3_bucket’이 Terraform에서 정의한 리소스의 타입이고,&lt;/li&gt;
&lt;li&gt;‘example_bucket’이 해당 리소스의 이름입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Terraform</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/766</guid>
      <comments>https://wooono.tistory.com/766#entry766comment</comments>
      <pubDate>Fri, 17 Nov 2023 14:03:01 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] Resource Request, Limit이란?</title>
      <link>https://wooono.tistory.com/765</link>
      <description>&lt;h3&gt;요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;K8S에서 Resource의 Request와 Limit은 컨테이너에 할당되는 CPU, Memory의 자원량을 의미합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request는, 컨테이너 생성 시 필요한 자원량을 의미합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limit은, 컨테이너가 이미 실행 중인 상태에서 추가 리소스가 필요한 경우에 사용할 수 있는 최대 자원량을 의미합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이때 주의할 점은, 반드시 Limit은 Request보다 같거나 커야한다는 것입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limit은 추가로 얼만큼의 리소스를 더 사용할 것인지가 아닌,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컨테이너 생성 시 필요한 자원량과 추가로 사용할 자원량을 더한 최대 자원량을 의미하기 때문입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU와 Memory의 자원 단위는 아래와 같습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;CPU : Milicore (1,000 Milicore = 1Core)&lt;/li&gt;
&lt;li&gt;Memory : Mbyte&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;K8S의 스케줄링이란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes에서, Pod를 어느 Node에 배포할지 결정하는 것을 스케줄링이라고 합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes 입장에서는 Pod에 필요한 자원의 양을 알아야,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 자원을 사용할 수 있는 Node에 Pod를 배포할 수 있게 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Pod는 하나 이상의 Container로 구성되므로,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스케줄링은 Pod의 모든 Container Request 양을 합친 값을 기준으로 진행됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Request 양을 초과 사용하면 어떻게 되는가?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pod가 Request 양을 초과하더라도 Limit 양을 넘지 않으면 문제가 발생하지 않습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다만, Node의 자원이 부족할 경우에는 Request 양을 초과한 Pod는 퇴거(Eviction)의 대상이 되어, 다른 Node에 재배치됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;만약 모든 Node의 자원이 부족하다면 이 Pod는 Pending 상태로 지속 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;따라서, 서비스 운영 시 자원 부족에 따른 Pod의 재배치나 Pending 상태에 대응하기 위해&lt;/li&gt;
&lt;li&gt;Replication을 설정하거나, 특정 Node에 Pod를 한정하여 배치하는 전략 등을 고려해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Limit 양을 초과하여 사용하면 어떻게 되는가?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pod가 Limit 양을 초과하면, Overcommitted 상태에 진입하게 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Overcommitted 상태란, 실제 사용 가능한 물리적인 자원량보다 해당 Pod에 할당된 자원량이 많은 상태를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU가 Overcommitted 될 경우, CPU는 throttling을 통해 실제 CPU 사용량을 Request에 정의된 상태까지 낮추게 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;예를 들어, Request가 100, Limit이 500인 경우, 현재 500으로 가동되고 있는 컨테이너의 CPU할당량을 100까지 낮추게 됩니다.&lt;/li&gt;
&lt;li&gt;그래도 Overcommitted 상태가 해결되지 않을 경우, 우선 순위에 따라서 운영중인 컨테이너를 강제 종료 시키게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory가 Overcommitted 될 경우, 사용중인 메모리의 크기를 줄일 수는 없기 때문에, 우선 순위에 따라서 운영 중인 컨테이너를 강제 종료 시키게 됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Request와 Limit 양을 설정하지 않으면 어떻게 되는가?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Request와 Limit 양을 설정하지 않으면, Node의 자원 전체를 사용할 수 있다는 의미와 같습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;개발 중에는 자원량을 설정하지 않아도 문제는 없지만,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;운영 시에는 Request, Limit 양이 설정되지 않은 Pod는 정확한 스케줄링이 어렵습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;한 Node에 Pod가 초과 배치될 수도 있고,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node 자원 부족 시 퇴거 대상이 증가할 수도 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;&lt;strong&gt;ResourceQuota &amp;amp; LimitRange&lt;/strong&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;쿠버네티스에서는 ResourceQuota를 사용해 네임스페이스별로 사용할 수 있는 리소스의 양을 지정하거나,&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LimitRange를 사용해 컨테이너마다 사용할 수 있는 리소스의 양을 지정할 수 있습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;ResourceQuota&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: example-resourcequota
  spec:
    hard:
      requests.cpu: &amp;quot;1&amp;quot;
      requests.memory: 1Gi
      limits.cpu: &amp;quot;2&amp;quot;
      limits.memory: 2Gi&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resource Quota는 네임스페이스별로 사용할 수 있는 리소스의 양을 지정합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;위 yaml 파일은, example-resourcequota라는 네임스페이스의 리소스 양을 지정한 예제입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;requests.cpu : 네임스페이스 전체의 CPU Request 리소스 제한 값입니다.&lt;/li&gt;
&lt;li&gt;requests.memory : 네임스페이스 전체의 Memory Request 리소스 제한 값입니다.&lt;/li&gt;
&lt;li&gt;limits.cpu : 네임스페이스 전체의 CPU Limit 리소스 제한 값입니다.&lt;/li&gt;
&lt;li&gt;limits.memory : 네임스페이스 전체의 Memory Limit 리소스 제한 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;LimitRange&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apiVersion: v1
  kind: LimitRange
  metadata:
    name: example-limitrange
  spec:
    limits:
    - type: Container
      max:
        memory: 512Mi
        cpu: 1
      min:
        memory: 50Mi
        cpu: 100m
      default:
        memory: 128Mi
        cpu: 200m
      defaultRequest:
        memory: 64Mi
        cpu: 100m&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Limit Range는 컨테이너 개별 자원의 사용 가능 범위를 지정합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;type : Container : 이 LimitRange는 컨테이너에 대한 제한을 정의합니다.&lt;/li&gt;
&lt;li&gt;max : 컨테이너의 Limit을 지정할 경우, 설정 가능한 최대 값입니다.&lt;/li&gt;
&lt;li&gt;min : 컨테이너의 Limit을 지정할 경우, 설정 가능한 최소 값입니다.&lt;/li&gt;
&lt;li&gt;default : 컨테이너의 Limit 값에 대한 기본값을 정의합니다.&lt;/li&gt;
&lt;li&gt;defaultRequest : 컨테이너의 Request 값에 대한 기본값을 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://itchain.wordpress.com/2018/05/16/kubernetes-resource-request-limit/&quot;&gt;https://itchain.wordpress.com/2018/05/16/kubernetes-resource-request-limit/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bcho.tistory.com/1291&quot;&gt;https://bcho.tistory.com/1291&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/765</guid>
      <comments>https://wooono.tistory.com/765#entry765comment</comments>
      <pubDate>Thu, 16 Nov 2023 17:18:17 +0900</pubDate>
    </item>
    <item>
      <title>[Prometheus] Config의 scrape_configs 설정 시 static_configs의 targets 설정 방법</title>
      <link>https://wooono.tistory.com/764</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선, Prometheus Config의 scrape_configs 설정 시 static_configs의 targets을 어떻게 설정해야하는지에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Prometheus Config File 예제 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;global:
  scrape_interval: 30s
  evaluation_interval: 30s
  scrape_timeout: 10s

scrape_configs:
  - job_name: &amp;#39;Test&amp;#39;
    static_configs:
      - targets: [&amp;#39;12.34.567.890:8086&amp;#39;]&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“&lt;a href=&quot;http://12.34.567.890:8086/metrics%E2%80%9D%EB%9D%BC%EB%8A%94&quot;&gt;http://12.34.567.890:8086/metrics”라는&lt;/a&gt; 경로에서 metrics를 scrape 해오고 싶다면,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;targets에는 “http://”와 “/metrics”를 제외한 “12.34.567.890:8086”만 적어도 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;scheme의 기본값은 “http”이며, metrics_path의 기본 경로는 “/metrics”이기 때문입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Prometheus</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/764</guid>
      <comments>https://wooono.tistory.com/764#entry764comment</comments>
      <pubDate>Wed, 15 Nov 2023 15:34:38 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] Replicaset Desired, Current, Ready란?</title>
      <link>https://wooono.tistory.com/763</link>
      <description>&lt;h3&gt;K8S Replicaset 상태 확인 명령어&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get replicaset&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;Desired (원하는 수)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ReplicaSet으로 관리하길 원하는 Pod의 수를 나타냅니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;사용자가 설정한 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Current (현재 수)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;실제 동작 중인 Pod의 수를 나타냅니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Ready (준비된 수)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용할 준비가 완료된 Pod의 수를 나타냅니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정상적인 환경이라면 Desired, Current, Ready 개수가 모두 동일해야합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/763</guid>
      <comments>https://wooono.tistory.com/763#entry763comment</comments>
      <pubDate>Wed, 15 Nov 2023 14:58:06 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] Container Sleep 방법</title>
      <link>https://wooono.tistory.com/762</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pod를 띄웠을 때, 에러로 인해 Pod가 지속적으로 재시작되는 경우가 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이때 트러블 슈팅을 위해 일시적으로 Container를 유지하는 방법에 대해서 포스팅하겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Container Sleep 적용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apiVersion: v1
kind: Pod
metadata:
  name: ubuntu
spec:
  containers:
  - name: ubuntu
    image: ubuntu:latest
    # Just spin &amp;amp; wait forever
    command: [ &amp;quot;/bin/bash&amp;quot;, &amp;quot;-c&amp;quot;, &amp;quot;--&amp;quot; ]
    args: [ &amp;quot;while true; do sleep 3600; done;&amp;quot; ]&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정석적인 방법은 아니며, 이외에도 다양한 방법이 사용될 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/31870222/how-can-i-keep-a-container-running-on-kubernetes&quot;&gt;https://stackoverflow.com/questions/31870222/how-can-i-keep-a-container-running-on-kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/762</guid>
      <comments>https://wooono.tistory.com/762#entry762comment</comments>
      <pubDate>Wed, 15 Nov 2023 14:28:09 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] Persistent Volume, Persistent Volume Claim, StorageClass란?</title>
      <link>https://wooono.tistory.com/761</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIfR2s/btsArmmXMVy/kZq6m4mN9h4BjM4TELMEHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIfR2s/btsArmmXMVy/kZq6m4mN9h4BjM4TELMEHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIfR2s/btsArmmXMVy/kZq6m4mN9h4BjM4TELMEHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIfR2s%2FbtsArmmXMVy%2FkZq6m4mN9h4BjM4TELMEHk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes에서 Volume은, 3가지 구조로 사용됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PersistentVolume(PV)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PersistentVolumeClaim(PVC)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StorageClass&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 PV와 PVC에 대해서 먼저 이해한 뒤, StorageClass에 대해서 이해해보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;PV와 PVC 요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PV&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Volume 자체를 의미합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클러스터 내에서 독립적으로 관리되며 별도의 생명 주기를 가집니다.&lt;/li&gt;
&lt;li&gt;클라우드 서비스 또는 직접 구축한 Storage와 같은 다양한 소스에서 제공되는 스토리지를 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PVC&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용자가 PV에 요청한 정보를 의미합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;필요한 용량, 액세스 모드 등을 지정하여 PV를 요청합니다.&lt;/li&gt;
&lt;li&gt;Pod는 직접 PV에 연결되는 대신, PVC를 통해 스토리지에 액세스합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PV는 Pod에 직접 연결되지 않고, PVC를 통해 연결됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;PV와 PVC는 클러스터 관리자가 수동으로 설정하거나 StorageClass를 사용하여 동적으로 프로비저닝할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;PV와 PVC의 생명 주기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y3bNP/btsAm50WBc3/Ld8bf98kDFCygenT6zc051/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y3bNP/btsAm50WBc3/Ld8bf98kDFCygenT6zc051/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y3bNP/btsAm50WBc3/Ld8bf98kDFCygenT6zc051/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY3bNP%2FbtsAm50WBc3%2FLd8bf98kDFCygenT6zc051%2Fimg.png&quot; width=&quot;50%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Provisioning&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PV를 생성하는 단계로, 두 가지 방법으로 이루어집니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정적(Static) 프로비저닝&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;미리 적정 용량의 PV를 생성해두는 방법입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용 가능한 스토리지 용량에 제한이 있을 때 유용합니다.&lt;/li&gt;
&lt;li&gt;만약, 100GB의 용량을 가진 PV를 미리 생성해두었을 때, 150GB를 요청하는 요청은 실패하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동적(Dynamic) 프로비저닝&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용자가 PVC를 통해 PV를 요청할 때, 사용자가 필요한 용량 만큼의 PV를 동적으로 생성하는 방법입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;필요에 따라 다양한 용량의 PV를 동적으로 생성하고 싶을 때 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동적 프로비저닝을 위해선 StorageClass라는 K8S 리소스가 사용되며,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어떤 종류의 Storage를 프로비저닝할지에 대한 규칙과 설정을 StorageClass에 정의한 뒤,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PVC에서 해당 StorageClass를 지정하면,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PVC가 생성될 때, 사용자가 필요한 용량 만큼의 PV가 동적으로 프로비저닝됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Binding&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;프로비저닝으로 만든 PV를 PVC와 연결하는 단계입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PVC에서 필요한 스토리지의 용량과 접근 방법을 명시하여 PV를 요청하면, 해당 요청에 맞는 PV가 바인딩됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;만약 PVC에서 요청한 조건을 만족하는 PV가 없다면, PVC는 해당 PV가 생성될 때까지 대기 상태에 있습니다.&lt;/li&gt;
&lt;li&gt;PV와 PVC의 매핑은 1대1 관계이며, PVC는 여러 개의 PV에 할당될 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Using&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pod는 PVC를 볼륨으로 인식해서 사용합니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pod에 할당된 PVC는 Pod를 유지하는 동안 계속 사용되며, 시스템에서 임의로 삭제할 수 없습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이 기능은 &amp;#39;Storage Object in Use Protection&amp;#39; (사용 중인 스토리지 오브젝트 보호)로 알려져 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용 중인 데이터 스토리지를 임의로 삭제하는 경우 치명적인 결과가 발생할 수 있으므로 이런 보호 기능을 사용하는 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Reclaiming&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용이 끝난 PVC를 삭제하고, 해당 PVC를 사용하던 PV를 초기화(reclaim)하는 과정을 나타냅니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;초기화 정책에는 Retain, Delete가 있습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Retain&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PV가 삭제되어도 PV의 데이터는 클러스터 내에 보존됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;관리자가 PV를 수동으로 정리하고 데이터를 보존하거나 백업하는 데 활용됩니다.&lt;/li&gt;
&lt;li&gt;PV를 다시 사용하기 위해서는 수동으로 다시 바인딩해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PV가 삭제되면 PV와 관련된 데이터도 함께 삭제됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;테스트 및 개발 환경에서 주로 사용되며 데이터 보존이 필요하지 않을 때 유용합니다.&lt;/li&gt;
&lt;li&gt;동적으로 프로비저닝된 PV의 반환 정책은 기본적으로 Delete이며, 필요에 따라 수정이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;정적 프로비저닝 예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistent Volume&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apiVersion: v1
  kind: PersistentVolume
  metadata:
    name: example-pv
  spec:
    capacity:         # Volume 용량
      storage: 1Gi    
    accessModes:      # 접근 옵션
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Retain  # PV가 삭제되었을 때의 초기화 옵션
    hostPath:                              # 마운트시킬 로컬 서버의 경로
      path: /data/example-pv&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistent Volume Claim&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: example-pvc
  spec:
    accessModes:             # 접근 옵션
      - ReadWriteOnce
    resources:               # 자원을 얼마나 사용할 것인지 요청 (PV의 용량을 초과하면 안됨)
      requests:
        storage: 500Mi&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;동적 프로비저닝 예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;StorageClass&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apiVersion: storage.k8s.io/v1
  kind: StorageClass
  metadata:                            
    name: example-storageclass        # StorageClass의 이름을 지정합니다.
  provisioner: kubernetes.io/gce-pd   # Google Compute Engine Persistent Disk 를 스토리지 플러그인으로 사용합니다.
  reclaimPolicy: Retain               # PV가 삭제되어도 데이터를 보존합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistent Volume Claim&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: example-pvc
  spec:
    accessModes:            # PV에 대한 액세스 모드를 나타냅니다.
      - ReadWriteOnce
    resources:
      requests:
        storage: 1Gi        # PVC가 요청하는 스토리지의 크기를 나타냅니다.
    storageClassName: example-storageclass  # PV가 동적으로 프로비저닝될 때 사용될 StorageClass를 나타냅니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Persistent Volume, Persistent Volume Claim 연결 확인 명령어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistent Volume Claim 연결 확인 명령어&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  $ kubectl get pvc
  NAME          STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS   AGE
  example-pvc   Bound    example-pv    2Gi        RWO            manual         2m&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;“example-pv”라는 VOLUME에 Bound 된 것을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistent Volume 연결 확인 명령어&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  $ kubectl get pv
  NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
  example-pv  5Gi        RWO            Retain           Bound    default/example-pvc manual                  3m&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;STATUS 종류&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Available : PVC에서 사용할 수 있도록 준비된 상태&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bound : 특정 PVC에 연결된 상태&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Released : PVC는 삭제되었고 PV는 아직 초기화 되지 않은 상태&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed : 자동 초기화를 실패한 상태&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kimjingo.tistory.com/153&quot;&gt;https://kimjingo.tistory.com/153&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/761</guid>
      <comments>https://wooono.tistory.com/761#entry761comment</comments>
      <pubDate>Sun, 5 Nov 2023 15:48:53 +0900</pubDate>
    </item>
    <item>
      <title>[Istio] AuthorizationPolicy란?</title>
      <link>https://wooono.tistory.com/760</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선, Istio의 AuthorizationPolicy에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AuthorizationPolicy란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Istio의 AuthorizationPolicy는 서비스 메시 내에서 네트워크 트래픽에 대한 엑세스 제어를 정의하는 데 사용되는 Kubernetes Custom Resource Definition(CRD) 중 하나입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AuthorizationPolicy를 사용하면 서비스 간 또는 서비스와 클라이언트 간의 통신을 제한하거나 규제할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AuthorizationPolicy 예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AuthorizationPolicy 리소스는 Terraform을 사용해 정의하겠습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;example_authorization_policy&amp;quot; {
    manifest = {
      apiVersion = &amp;quot;security.istio.io/v1beta1&amp;quot;
      kind       = &amp;quot;AuthorizationPolicy&amp;quot;
      metadata = {
        name      = &amp;quot;example-auth-policy&amp;quot;
        namespace = &amp;quot;your-namespace&amp;quot;
      }
      spec = {
        selector = {
          matchLabels = {
            app = &amp;quot;your-app-label&amp;quot;   # 어떤 서비스에 규칙을 적용할지 설정합니다.
          }
        }
        action = &amp;quot;ALLOW&amp;quot;  # 규칙이 일치하는 경우 어떤 동작을 취할지 설정합니다.
        rules = [
          {
            when = [
              {
                key = &amp;quot;remote.ip&amp;quot;
                values = [&amp;quot;192.168.1.100&amp;quot;]  # 허용할 IP 주소를 지정합니다.
              }
            ]
          }
        ]
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이 AuthorizationPolicy는 Istio 서비스 메시 내에서 “your-app-label” Label을 가진 Service에 대한 액세스를 제어하며,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;remote.ip가 “192.168.1.100”인 경우에만 액세스를 허용하게 됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://istio.io/latest/docs/reference/config/security/authorization-policy/&quot;&gt;https://istio.io/latest/docs/reference/config/security/authorization-policy/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Istio</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/760</guid>
      <comments>https://wooono.tistory.com/760#entry760comment</comments>
      <pubDate>Sun, 5 Nov 2023 14:37:15 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] ExternalName이란?</title>
      <link>https://wooono.tistory.com/759</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes(K8s)에서 &amp;quot;ExternalName&amp;quot;은 서비스(Service) 리소스의 유형 중 하나로,&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내부 클러스터의 애플리케이션들은 &amp;quot;ExternalName&amp;quot; 유형으로 선언된 서비스(Service)의 DNS를 사용해, 외부 서비스 또는 외부 도메인을 간단하게 참조할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;클러스터 내부 Pod에서 외부 도메인인 “external-service.example.com”에 접근해야 한다고 가정해 봅시다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이때, 아래와 같이 ExternalName 유형의 Service를 생성한 뒤,&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  apiVersion: v1
  kind: Service
  metadata:
    name: my-external-service
  spec:
    type: ExternalName
    externalName: external-service.example.com&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;클러스터 내부 Pod에서 해당 Service의 DNS를 아래와 같이 참조하면,&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;my-external-service.namespace.svc.cluster.local&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;my-external-service : Service의 이름&lt;/li&gt;
&lt;li&gt;namespace : Service의 Namespace&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;external-service.example.com 로 라우팅할 수 있게 됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/759</guid>
      <comments>https://wooono.tistory.com/759#entry759comment</comments>
      <pubDate>Wed, 1 Nov 2023 18:03:14 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] Node, Pod, Container 리소스 사용량 확인 방법</title>
      <link>https://wooono.tistory.com/758</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;K8S에서 CPU는 Milicore단위(1,000 Milicore = 1Core)로, Memory는 Mbyte단위로 할당됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Node 리소스 사용량 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;kubectl top node&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
node-1     50m          2%     800Mi           40%
node-2     75m          3%     900Mi           45%&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pod 리소스 사용량 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;kubectl top pod&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;NAME             CPU(cores)   MEMORY(bytes)
my-pod-1         100m         50Mi
my-pod-2         200m         75Mi
another-pod      50m          30Mi&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pod 내부 Container 별 리소스 사용량 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;kubectl top pod --containers=true&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;POD                NAME                   CPU(cores)   MEMORY(bytes)
my-pod-1           my-container-1          100m         50Mi
my-pod-2           my-container-2          200m         75Mi
another-pod        another-container       50m          30Mi&lt;/code&gt;&lt;/pre&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/758</guid>
      <comments>https://wooono.tistory.com/758#entry758comment</comments>
      <pubDate>Fri, 20 Oct 2023 14:43:36 +0900</pubDate>
    </item>
    <item>
      <title>[Prometheus] Dashboard에 Basic Auth 적용하는 방법</title>
      <link>https://wooono.tistory.com/757</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선, K8S Deployment로 정의한 Prometheus Container를&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기본 설정 파일 및 Basic Auth가 적용된 웹 설정 파일을 기반으로 실행하는 방법에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;관련 리소스는 Terraform으로 관리하고 있기 때문에, HCL 코드를 기반으로 설명하겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Basic Auth에 사용할 &lt;strong&gt;&lt;strong&gt;비밀번호 해싱&lt;/strong&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;python3-bcrypt를 사용해 Basic Auth에 사용할 비밀번호를 해싱합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;  import getpass
  import bcrypt

  password = getpass.getpass(&amp;quot;password: &amp;quot;)
  hashed_password = bcrypt.hashpw(password.encode(&amp;quot;utf-8&amp;quot;), bcrypt.gensalt())
  print(hashed_password.decode())&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이후, 비밀번호를 test로 입력한다면 아래와 같은 결과값이 출력됩니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;  password:
  $2b$12$hNf2lSsxfm0.i4a.1kVpSOVyBCfIB51VRjgBUyv6kdnyTlgWj81Ay&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;kubernetes_config_map 예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deployment를 통해 Prometheus Container가 시작될 때,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus가 기본 설정 파일 및 웹 설정 파일을 Container 내부에서 찾아 실행될 수 있도록,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus Container 내부에 마운트할 파일들을 kubernetes_config_map을 통해 정의합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;  resource &amp;quot;kubernetes_config_map&amp;quot; &amp;quot;prometheus_config&amp;quot; {
    metadata {
      name = &amp;quot;prometheus-config&amp;quot;
    }

    data = {
      &amp;quot;prometheus.yml&amp;quot; = &amp;lt;&amp;lt;EOF
  global:
    scrape_interval: 30s
    evaluation_interval: 30s
    scrape_timeout: 10s

  scrape_configs:
    - job_name: &amp;#39;statsd-exporter&amp;#39;
      static_configs:
        - targets: [&amp;#39;prometheus-statsd-exporter.default.svc.cluster.local:9102&amp;#39;]
  EOF

      &amp;quot;web.yml&amp;quot; = &amp;lt;&amp;lt;EOF
  basic_auth_users:
    admin: $2b$12$hNf2lSsxfm0.i4a.1kVpSOVyBCfIB51VRjgBUyv6kdnyTlgWj81Ay
  EOF
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;kubernetes_deployment 예제 코드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;kubernetes_config_map이 Prometheus Container 내부로 volume mount 될 수 있도록 설정한 뒤,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus가 기본 설정 파일 및 웹 설정 파일을 Container 내부에서 찾아 실행될 수 있도록,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus Container 실행 인자로 기본 설정 파일 및 웹 설정 파일의 경로를 넣어줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;  resource &amp;quot;kubernetes_deployment&amp;quot; &amp;quot;prometheus&amp;quot; {
    metadata {
      name = &amp;quot;prometheus&amp;quot;
    }

    spec {
      replicas = 1

      selector {
        match_labels = {
          app = &amp;quot;prometheus&amp;quot;
        }
      }

      template {
        metadata {
          labels = {
            app = &amp;quot;prometheus&amp;quot;
          }
        }

        spec {
          container {
            image = &amp;quot;prom/prometheus&amp;quot;
            name  = &amp;quot;prometheus&amp;quot;
            args = [
                          &amp;quot;--config.file=/etc/prometheus/prometheus.yml&amp;quot;,
              &amp;quot;--web.config.file=/etc/prometheus/web.yml&amp;quot;
            ]
            port {
              container_port = 9090
            }

            volume_mount {
              mount_path = &amp;quot;/etc/prometheus/&amp;quot;
              name       = &amp;quot;prometheus-config-volume&amp;quot;
            }
          }
          volume {
            name = &amp;quot;prometheus-config-volume&amp;quot;
            config_map {
              name = kubernetes_config_map.prometheus_config.metadata[0].name
            }
          }
        }
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://prometheus.io/docs/guides/basic-auth/&quot;&gt;https://prometheus.io/docs/guides/basic-auth/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/configuration/https/&quot;&gt;https://prometheus.io/docs/prometheus/latest/configuration/https/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Prometheus</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/757</guid>
      <comments>https://wooono.tistory.com/757#entry757comment</comments>
      <pubDate>Fri, 20 Oct 2023 14:09:26 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] kubernetes_manifest란?</title>
      <link>https://wooono.tistory.com/756</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terraform에서, 공식 K8S Provider가 지원하지 않는 리소스는 ‘kubernetes_manifest’를 사용해 정의할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;물론, yaml 파일을 그대로 사용할 수는 없으며 yaml의 모든 필드를 HCL 양식에 맞게 수정해야합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;다행히도 built-in function인 &amp;#39;yamldecode()&amp;#39;나 ‘tfk8s’ 같은 Tool을 사용해 YAML에서 HCL로 자동 변환할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;yaml 예제&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: test-config
  namespace: default
data:
  foo: bar&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Terraform kubernetes_manifest 예제&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;test-configmap&amp;quot; {
  manifest = {
    &amp;quot;apiVersion&amp;quot; = &amp;quot;v1&amp;quot;
    &amp;quot;kind&amp;quot;       = &amp;quot;ConfigMap&amp;quot;
    &amp;quot;metadata&amp;quot; = {
      &amp;quot;name&amp;quot;      = &amp;quot;test-config&amp;quot;
      &amp;quot;namespace&amp;quot; = &amp;quot;default&amp;quot;
    }
    &amp;quot;data&amp;quot; = {
      &amp;quot;foo&amp;quot; = &amp;quot;bar&amp;quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest&quot;&gt;https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cwal.tistory.com/89&quot;&gt;https://cwal.tistory.com/89&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Terraform</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/756</guid>
      <comments>https://wooono.tistory.com/756#entry756comment</comments>
      <pubDate>Fri, 20 Oct 2023 11:46:21 +0900</pubDate>
    </item>
    <item>
      <title>[K8S] labels, selector, match_labels의 차이</title>
      <link>https://wooono.tistory.com/755</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes의 Service는 특정 Pod에 네트워크 트래픽을 라우팅하는 역할을 합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Service는 라벨 셀렉터(spec.selector)를 통해,&lt;/li&gt;
&lt;li&gt;해당 라벨과 동일한 라벨(spec.template.metadata.labels)을 가지고 있는 Pod에게 네트워크 트래픽을 라우팅하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하지만, 라우팅 설정에 사용되는 라벨 관련 필드는 다양하기 때문에 헷갈릴 수 있습니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;service&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;spec.selector (Service가 어떤 Pod에 라우팅할지 정하기 위한 라벨 셀렉터)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deployment&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;metadata.labels (Deployment에 할당되는 라벨)&lt;/li&gt;
&lt;li&gt;spec.selector.match_labels (Deployment가 어떤 Pod를 관리할지 정하기 위한 라벨 셀렉터)&lt;/li&gt;
&lt;li&gt;spec.template.metadata.labels (Pod에 할당되는 라벨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;따라서, 해당 포스팅에선 Service, Deployment 생성 예제를 통해 labels, selector, match_labels의 차이에 대해서 이해해보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자원 생성 예제는 Terraform을 기반으로 설명하겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Service 생성 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;resource &amp;quot;kubernetes_service&amp;quot; &amp;quot;nginx&amp;quot; {
  metadata {
    name = &amp;quot;nginx-service&amp;quot;
  }

  spec {
    # Service가 어떤 Pod에 라우팅할지 정하기 위한 라벨 셀렉터
    selector = {
      app = &amp;quot;nginx&amp;quot;
    }

    port {
      port        = 80  # 서비스로 라우팅할 포트
      target_port = 80  # 선택한 Pod의 어떤 포트로 트래픽을 전달할지 설정
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Deployment 생성 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;resource &amp;quot;kubernetes_deployment&amp;quot; &amp;quot;nginx&amp;quot; {
  metadata {
    name = &amp;quot;nginx-deployment&amp;quot;
    labels = {
      app = &amp;quot;nginx&amp;quot;  # Deployment에 할당되는 라벨
    }
  }

  spec {
    replicas = 2

    # Deployment가 어떤 Pod를 관리할지 정하기 위한 라벨 셀렉터
    selector {
      match_labels = {
        app = &amp;quot;nginx&amp;quot;
      }
    }

    template {
      metadata {
        labels = {
          app = &amp;quot;nginx&amp;quot;  # Pod에 할당되는 라벨
        }
      }

      spec {
        container {
          image = &amp;quot;nginx:latest&amp;quot;
          name  = &amp;quot;nginx&amp;quot;
          port {
            container_port = 80
          }
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service, Pod 라우팅을 위해선 Service의 spec.selector와 Deployment의 spec.template.metadata.labels가 일치해야합니다!&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;또한, Deployment가 Pod를 관리하기 위해선 Deployment의 spec.selector.match_labels와 spec.template.metadata.labels가 일치해야합니다!&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;즉, 아래 3가지 구성요소가 모두 일치해야합니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;spec.selector (Service가 어떤 Pod에 라우팅할지 정하기 위한 라벨 셀렉터)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;spec.selector.match_labels (Deployment가 어떤 Pod를 관리할지 정하기 위한 라벨 셀렉터)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spec.template.metadata.labels (Pod에 할당되는 라벨)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/755</guid>
      <comments>https://wooono.tistory.com/755#entry755comment</comments>
      <pubDate>Wed, 18 Oct 2023 18:20:02 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] DAG에서 Airflow REST API 호출하는 방법</title>
      <link>https://wooono.tistory.com/754</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Airflow 환경&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;airflow-stable helm chart를 사용해 GKE에 airflow가 설치된 상황&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문제 상황&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;DAG에서 Airflow REST API를 호출하고 싶음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;DAG는 Worker에서 동작하고, Airflow REST API 서버는 Webserver에서 동작하기 때문에&lt;/li&gt;
&lt;li&gt;DAG에서 Airflow REST API를 호출하고 싶다면&lt;/li&gt;
&lt;li&gt;Worker에서 Webserver로 REST API 내부 호출이 가능해야합니다.&lt;/li&gt;
&lt;li&gt;따라서, basic auth를 사용해 Pod간 내부 호출을 할 수 있도록 API 관련 Airflow Configuration을 변경했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기타&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Worker Pod에서 Webserver Pod에 접근하기 위해&lt;/li&gt;
&lt;li&gt;Webserver Pod의 Service를 생성한 뒤, Service DNS를 사용해 접근하도록 설정했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Helm Chart - Basic Auth를 사용해 Airflow REST API를 호출할 수 있도록 Airflow Configuration 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;기본 값&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;  airflow:
    config:
          AIRFLOW__API__AUTH_BACKENDS : &amp;#39;airflow.api.auth.backend.session&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;수정&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;  airflow:
    config:
          AIRFLOW__API__AUTH_BACKENDS : &amp;#39;airflow.api.auth.backend.basic_auth&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Airflow REST API 호출 예제&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;curl -X GET  \
    --user &amp;quot;username:password&amp;quot; \
    &amp;quot;http://airflow-web-custom.default.svc.cluster.local:8080/api/v1/importErrors&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Basic Auth를 사용하기 위한 Airflow User는 이미 생성되어있다고 가정하겠습니다.&lt;ul&gt;
&lt;li&gt;Airflow Dashboard → Security → List Users에서 생성되어 있는 User를 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#auth-backends&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#auth-backends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/security/api.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/security/api.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data/Airflow</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/754</guid>
      <comments>https://wooono.tistory.com/754#entry754comment</comments>
      <pubDate>Mon, 16 Oct 2023 10:02:58 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] kubernetes_config_map 사용 방법</title>
      <link>https://wooono.tistory.com/753</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 Terraform을 통해 kubernetes_config_map 자원 생성 후&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kubernetes_deployment 내부에 kubernetes_config_map 파일을 마운팅하는 방법에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;kubernetes_config_map 자원 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;resource &amp;quot;kubernetes_config_map&amp;quot; &amp;quot;example_config_map&amp;quot; {
  metadata {
    name = &amp;quot;example-config-map&amp;quot;
  }

  data = {
    &amp;quot;config-file.txt&amp;quot; = &amp;lt;&amp;lt;EOF
This is a sample config file.
You can customize it as needed.
EOF
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;kubernetes_deployment 내부 kubernetes_config_map 파일 마운팅&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;resource &amp;quot;kubernetes_deployment&amp;quot; &amp;quot;example_app&amp;quot; {
  metadata {
    name = &amp;quot;example-app&amp;quot;
    labels = {
      app = &amp;quot;example-app&amp;quot;
    }
  }

  spec {
    replicas = 3

    selector {
      match_labels = {
        app = &amp;quot;example-app&amp;quot;
      }
    }

    template {
      metadata {
        labels = {
          app = &amp;quot;example-app&amp;quot;
        }
      }

      spec {
        container {
          name  = &amp;quot;example-container&amp;quot;
          image = &amp;quot;nginx:latest&amp;quot;

          port {
            container_port = 80
            protocol = &amp;quot;TCP&amp;quot;
          }

          volume_mount {
            mount_path = &amp;quot;/etc/config&amp;quot;   # 컨테이너 내부 파일 저장 경로
            name       = &amp;quot;config-volume&amp;quot; # volumes 이름
          }
        }

        volumes {
          name = &amp;quot;config-volume&amp;quot;
          config_map {
            # kubernetes_config_map 이름
            name = kubernetes_config_map.example_config_map.metadata[0].name 
          }
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;nginx container 내부 /etc/config 경로에 config-file.txt 파일이 저장됩니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Terraform</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/753</guid>
      <comments>https://wooono.tistory.com/753#entry753comment</comments>
      <pubDate>Tue, 26 Sep 2023 17:45:54 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Configuration 확인 명령어</title>
      <link>https://wooono.tistory.com/752</link>
      <description>&lt;h3&gt;들어가기앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Airflow는 Airflow CLI를 사용해, Airflow에 설정된 Configuration을 확인할 수 있습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Airflow CLI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;전체 Configuration 목록 확인&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  $ airflow config list&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특정 Configuration Option 확인&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # airflow config get-value [Section] [Option]

  $ airflow config get-value core executor
  SequentialExecutor

  $ airflow config get-value metrics statsd_host
  localhost&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고 사이트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/cli-and-env-variables-ref.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/cli-and-env-variables-ref.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/howto/set-config.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/howto/set-config.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data/Airflow</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/752</guid>
      <comments>https://wooono.tistory.com/752#entry752comment</comments>
      <pubDate>Tue, 26 Sep 2023 17:23:59 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] generic::permission_denied: user does not have impersonation permission on the trigger service account specified: 에러 해결 방법</title>
      <link>https://wooono.tistory.com/751</link>
      <description>&lt;h3&gt;에러 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;generic::permission_denied: user does not have impersonation permission on the trigger service account specified:&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;문제 상황&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특정 사용자가 Cloud Build에 할당되어 있는 Service Account를 대신해 수동 실행할 수 있는 권한이 없어서 에러가 발생하는 상황입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;해결 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용자 계정에 아래 역할을 할당하면 됩니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;“&lt;strong&gt;roles/iam.serviceAccountUser”&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;해당 역할은 “iam.serviceAccounts.actAs” 권한을 포함하고 있으며,&lt;/li&gt;
&lt;li&gt;“iam.serviceAccounts.actAs” 권한은 사용자가 다른 사용자 계정처럼 다른 역할을 수행할 수 있도록 허용하는 권한입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>GCP/Authentication</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/751</guid>
      <comments>https://wooono.tistory.com/751#entry751comment</comments>
      <pubDate>Thu, 21 Sep 2023 11:18:09 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] &amp;ldquo;gcloud auth login&amp;quot;과 &amp;ldquo;gcloud auth application-default login&amp;quot;의 차이</title>
      <link>https://wooono.tistory.com/750</link>
      <description>&lt;h3&gt;들어가기 앞서,&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“gcloud auth login”과 “gcloud auth application-default login”은 Google Cloud SDK의 두 가지 다른 인증 방법을 설정하는 명령어입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해당 포스팅에선 각각의 사용 목적에 대해서 다뤄보겠습니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;gcloud auth login&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;직접 GCP 리소스와 상호작용할 때 사용하는 명령어입니다.&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;리소스 직접 액세스, Google Cloud SDK를 통한 GCP API 호출 등&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;gcloud auth application-default login&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;로컬 개발 환경에서 애플리케이션과 Google Cloud 서비스가 상호작용할 때 사용하는 명령어입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;동작&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;해당 명령어를 실행하면 브라우저 창이 열리고 Google 로그인 페이지로 리디렉션됩니다.&lt;/li&gt;
&lt;li&gt;여기서 GCP 계정으로 로그인히면 로컬 환경에 인증 정보가 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>GCP/Authentication</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/750</guid>
      <comments>https://wooono.tistory.com/750#entry750comment</comments>
      <pubDate>Thu, 21 Sep 2023 11:00:18 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Mac OS 환경에서 IntelliJ의 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 해결하기</title>
      <link>https://wooono.tistory.com/749</link>
      <description>&lt;h3&gt;문제 상황&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Cannot connect to the Docker daemon at unix:///var/run/docker.sock. 
Is the docker daemon running? (Details: [2] No such file or directory)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;docker ps 명령어를 통해 docker 정상 작동을 확인했음에도 불구하고, IntelliJ에서 Docker 연결이 불가능한 상황&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;해결 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;터미널에서 아래 명령어를 실행합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # {name}에는 자신이 macOS에 설정한 값을 넣는다.
  sudo ln -s /Users/{name}/.docker/run/docker.sock /var/run/&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이후, Settings → 빌드, 실행, 배포 → Docker → 아래와 같이 재설정합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1tNdQ/btsuSlfMZMA/CKtQ1bObqhk3PWiaDgKTkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1tNdQ/btsuSlfMZMA/CKtQ1bObqhk3PWiaDgKTkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1tNdQ/btsuSlfMZMA/CKtQ1bObqhk3PWiaDgKTkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1tNdQ%2FbtsuSlfMZMA%2FCKtQ1bObqhk3PWiaDgKTkK%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@profoundsea25/Mac-OS-Apple-Silicon-%ED%99%98%EA%B2%BD-IntelliJ%EC%97%90%EC%84%9C-Cannot-connect-to-the-Docker-daemon-at-unixvarrundocker.sock.-Is-the-docker-daemon-running-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0&quot;&gt;https://velog.io/@profoundsea25/Mac-OS-Apple-Silicon-환경-IntelliJ에서-Cannot-connect-to-the-Docker-daemon-at-unixvarrundocker.sock.-Is-the-docker-daemon-running-해결하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Docker</category>
      <author>운호(Noah)</author>
      <guid isPermaLink="true">https://wooono.tistory.com/749</guid>
      <comments>https://wooono.tistory.com/749#entry749comment</comments>
      <pubDate>Wed, 20 Sep 2023 15:29:53 +0900</pubDate>
    </item>
  </channel>
</rss>