<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발 노트</title>
    <link>https://kchanguk.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 22:07:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>창욱씨</managingEditor>
    <item>
      <title>Java 23</title>
      <link>https://kchanguk.tistory.com/entry/Java-23</link>
      <description>&lt;h2&gt;1.   Markdown Documentation Comments (JEP 467) - 정식 출시&lt;/h2&gt;
&lt;p&gt;Javadoc 주석을 HTML 태그 없이 Markdown 문법으로 작성할 수 있습니다. &lt;code&gt;///&lt;/code&gt; 으로 시작하는 새로운 주석 스타일을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ 기존 HTML 방식
/**
 * &amp;lt;h2&amp;gt;두 수를 더합니다.&amp;lt;/h2&amp;gt;
 * &amp;lt;p&amp;gt;두 정수를 받아 합계를 반환합니다.&amp;lt;/p&amp;gt;
 * &amp;lt;ul&amp;gt;
 *   &amp;lt;li&amp;gt;{@code a} - 첫 번째 정수&amp;lt;/li&amp;gt;
 *   &amp;lt;li&amp;gt;{@code b} - 두 번째 정수&amp;lt;/li&amp;gt;
 * &amp;lt;/ul&amp;gt;
 * @return 두 수의 합
 */
public int add(int a, int b) {
    return a + b;
}

// ✅ Java 23 Markdown 방식
/// ## 두 수를 더합니다.
///
/// 두 정수를 받아 합계를 반환합니다.
///
/// - `a` - 첫 번째 정수
/// - `b` - 두 번째 정수
///
/// @return 두 수의 합
public int add(int a, int b) {
    return a + b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2.   Primitive Types in Patterns (JEP 455 - Preview)&lt;/h2&gt;
&lt;p&gt;기존 패턴 매칭(&lt;code&gt;instanceof&lt;/code&gt;, &lt;code&gt;switch&lt;/code&gt;)은 참조 타입만 지원했지만, Java 23부터 &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt; 등 &lt;strong&gt;primitive 타입&lt;/strong&gt;도 패턴 매칭에 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ✅ instanceof에서 primitive 타입 패턴 매칭
Object obj = 42;

// 기존 방식
if (obj instanceof Integer i &amp;amp;&amp;amp; i &amp;gt; 10) {
    System.out.println(&amp;quot;10보다 큰 정수: &amp;quot; + i);
}

// Java 23 - primitive 직접 사용
if (obj instanceof int i &amp;amp;&amp;amp; i &amp;gt; 10) {
    System.out.println(&amp;quot;10보다 큰 int: &amp;quot; + i);
}

// ✅ switch에서 primitive 타입 패턴 매칭
double value = 3.14;

String result = switch (value) {
    case double d when d &amp;lt; 0   -&amp;gt; &amp;quot;음수&amp;quot;;
    case double d when d == 0  -&amp;gt; &amp;quot;영&amp;quot;;
    case double d when d &amp;lt; 1   -&amp;gt; &amp;quot;0과 1 사이&amp;quot;;
    default                    -&amp;gt; &amp;quot;1 이상&amp;quot;;
};
System.out.println(result); // 출력: 1 이상

// ✅ 숫자 범위에 따른 분기 처리
int score = 85;

String grade = switch (score) {
    case int s when s &amp;gt;= 90 -&amp;gt; &amp;quot;A&amp;quot;;
    case int s when s &amp;gt;= 80 -&amp;gt; &amp;quot;B&amp;quot;;
    case int s when s &amp;gt;= 70 -&amp;gt; &amp;quot;C&amp;quot;;
    default                 -&amp;gt; &amp;quot;F&amp;quot;;
};
System.out.println(grade); // 출력: B&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3.   Stream Gatherers (JEP 473 - 2nd Preview)&lt;/h2&gt;
&lt;p&gt;커스텀 중간 스트림 연산을 직접 정의할 수 있습니다. 기존의 고정된 &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt; 등의 연산 외에 원하는 중간 처리 로직을 자유롭게 만들 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.stream.Gatherers;

// ✅ 내장 Gatherer - 고정 크기 윈도우
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowFixed(2))
    .forEach(System.out::println);
// 출력: [1, 2], [3, 4], [5]

// ✅ 내장 Gatherer - 슬라이딩 윈도우
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowSliding(3))
    .forEach(System.out::println);
// 출력: [1, 2, 3], [2, 3, 4], [3, 4, 5]

// ✅ 내장 Gatherer - 누적 집계 (scan)
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.scan(() -&amp;gt; 0, Integer::sum))
    .forEach(System.out::println);
// 출력: 1, 3, 6, 10, 15

// ✅ 커스텀 Gatherer - 중복 제거 후 처음 N개만 추출
Gatherer&amp;lt;Integer, ?, Integer&amp;gt; distinctLimit = Gatherer.ofSequential(
    () -&amp;gt; new HashSet&amp;lt;Integer&amp;gt;(),
    (seen, element, downstream) -&amp;gt; {
        if (seen.add(element)) {          // 중복이 아닌 경우만
            return downstream.push(element); // 다음 단계로 전달
        }
        return true;
    }
);

Stream.of(1, 2, 2, 3, 1, 4, 3, 5)
    .gather(distinctLimit)
    .forEach(System.out::println);
// 출력: 1, 2, 3, 4, 5&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4.   Implicitly Declared Classes &amp;amp; Instance Main Methods (JEP 477 - 3rd Preview)&lt;/h2&gt;
&lt;p&gt;Java 입문자의 진입장벽을 낮추기 위해 클래스 선언과 복잡한 &lt;code&gt;main&lt;/code&gt; 메서드 시그니처를 생략할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ 기존 방식
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println(&amp;quot;Hello, World!&amp;quot;);
    }
}

// ✅ Java 23 방식 - 클래스, static, args 모두 생략 가능
void main() {
    System.out.println(&amp;quot;Hello, World!&amp;quot;);
}

// ✅ 간단한 계산기 예제
void main() {
    int a = 10, b = 20;
    System.out.println(&amp;quot;합계: &amp;quot; + (a + b));       // 합계: 30
    System.out.println(&amp;quot;곱셈: &amp;quot; + (a * b));       // 곱셈: 200
    System.out.println(&amp;quot;평균: &amp;quot; + (a + b) / 2.0); // 평균: 15.0
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5.   Structured Concurrency (JEP 480 - 3rd Preview)&lt;/h2&gt;
&lt;p&gt;여러 비동기 작업의 생명주기를 하나의 블록에서 구조적으로 관리합니다. 작업 실패 시 자동으로 나머지 작업을 취소하고 리소스를 정리합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ✅ ShutdownOnFailure - 하나라도 실패하면 나머지 모두 취소
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future&amp;lt;String&amp;gt; user  = scope.fork(() -&amp;gt; fetchUser());   // 병렬 실행
    Future&amp;lt;String&amp;gt; order = scope.fork(() -&amp;gt; fetchOrder());  // 병렬 실행

    scope.join();           // 두 작업 모두 완료될 때까지 대기
    scope.throwIfFailed();  // 실패한 작업이 있으면 예외 전파

    return new Result(user.resultNow(), order.resultNow());
}
// try 블록을 벗어나면 모든 하위 스레드 자동 정리

// ✅ ShutdownOnSuccess - 가장 먼저 성공한 결과 사용 (경쟁 실행)
try (var scope = new StructuredTaskScope.ShutdownOnSuccess&amp;lt;String&amp;gt;()) {
    scope.fork(() -&amp;gt; fetchFromServerA()); // 서버 A에서 조회
    scope.fork(() -&amp;gt; fetchFromServerB()); // 서버 B에서 조회 (더 빠른 쪽 사용)

    scope.join();
    return scope.result(); // 먼저 성공한 결과 반환, 나머지는 자동 취소
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6.   Scoped Values (JEP 481 - 3rd Preview)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ThreadLocal&lt;/code&gt;의 문제점(메모리 누수, 변경 가능성)을 해결하는 &lt;strong&gt;불변 데이터 공유&lt;/strong&gt; 메커니즘입니다. 특정 스코프 내에서만 유효한 값을 안전하게 전달합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ✅ ScopedValue 선언
static final ScopedValue&amp;lt;User&amp;gt; CURRENT_USER = ScopedValue.newInstance();
static final ScopedValue&amp;lt;String&amp;gt; REQUEST_ID = ScopedValue.newInstance();

// 요청 처리 진입점
void handleRequest(User user, String requestId) {
    // 스코프 내에서만 값 바인딩 (여러 값 동시 바인딩 가능)
    ScopedValue.where(CURRENT_USER, user)
               .where(REQUEST_ID, requestId)
               .run(() -&amp;gt; {
                   authenticate(); // CURRENT_USER 접근 가능
                   logRequest();   // REQUEST_ID 접근 가능
                   processData();  // 둘 다 접근 가능
               });
    // 블록을 벗어나면 자동 해제 - 메모리 누수 없음
}

void authenticate() {
    User user = CURRENT_USER.get();
    System.out.println(&amp;quot;인증 확인: &amp;quot; + user.name());
}

void logRequest() {
    String id = REQUEST_ID.get();
    System.out.println(&amp;quot;요청 ID 로깅: &amp;quot; + id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;&lt;code&gt;ThreadLocal&lt;/code&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;&lt;code&gt;ScopedValue&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;값 변경&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ 가능&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 불변&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유효 범위&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;스레드 전체 생존 기간&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;명시적 블록 내부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 누수 위험&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;⚠️ 있음&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virtual Thread 성능&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌ 비효율&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅ 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;7.  ️ Flexible Constructor Bodies (JEP 482 - 2nd Preview)&lt;/h2&gt;
&lt;p&gt;기존에는 생성자에서 &lt;code&gt;super()&lt;/code&gt; 또는 &lt;code&gt;this()&lt;/code&gt; 호출이 &lt;strong&gt;반드시 첫 번째 줄&lt;/strong&gt;에 있어야 했습니다. Java 23부터는 &lt;code&gt;super()&lt;/code&gt; 호출 이전에도 &lt;strong&gt;인스턴스 필드에 영향을 주지 않는 코드&lt;/strong&gt;를 작성할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;class Animal {
    String name;
    Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {

    // ❌ 기존 방식 - super() 전에 유효성 검사 불가
    Dog(String name) {
        super(name); // 반드시 첫 줄이어야 함
        if (name == null || name.isBlank()) { // super() 이후에만 검사 가능
            throw new IllegalArgumentException(&amp;quot;이름은 비어있을 수 없습니다.&amp;quot;);
        }
    }

    // ✅ Java 23 방식 - super() 호출 전에 유효성 검사 가능
    Dog(String name) {
        // super() 전에 검증 로직 작성 가능
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException(&amp;quot;이름은 비어있을 수 없습니다.&amp;quot;);
        }
        super(name); // 검증 후 부모 생성자 호출
    }
}

// ✅ 데이터 전처리 후 super() 호출
class Circle extends Shape {
    Circle(double radius) {
        // super() 전에 값 가공 가능
        double normalized = Math.abs(radius); // 음수 반지름 보정
        super(normalized);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java</category>
      <category>Java</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/255</guid>
      <comments>https://kchanguk.tistory.com/entry/Java-23#entry255comment</comments>
      <pubDate>Wed, 6 May 2026 18:34:48 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes HPA</title>
      <link>https://kchanguk.tistory.com/entry/Kubernetes-HPA</link>
      <description>&lt;h3&gt;1. HPA란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pod 수를 자동으로 조절하는 Kubernetes 리소스&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;직접 스케일링을 수행하지 않고&lt;/strong&gt;, 메트릭을 모니터링 후 Deployment의 &lt;code&gt;replicas&lt;/code&gt; 수를 업데이트하는 &lt;strong&gt;명령만&lt;/strong&gt; 내림&lt;/li&gt;
&lt;li&gt;실제 Pod 생성/삭제는 Deployment → ReplicaSet → Scheduler → kubelet이 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2. 스케일링 방향&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scale Up&lt;/strong&gt; (증가) / &lt;strong&gt;Scale Down&lt;/strong&gt; (감소) 모두 담당&lt;/li&gt;
&lt;li&gt;단, &lt;strong&gt;제약 안에서만&lt;/strong&gt; 동작&lt;/li&gt;
&lt;/ul&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;strong&gt;수량 제약&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;minReplicas&lt;/code&gt; ~ &lt;code&gt;maxReplicas&lt;/code&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;Scale Up은 즉시, Scale Down은 기본 5분 안정화 윈도우 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h3&gt;3. 구성 요소 및 단위&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[클러스터] - 전체 Kubernetes 환경
    ├── kube-controller-manager (클러스터에 1개)
    │     └── HPA Controller    (클러스터에 1개, 모든 HPA 관리)
    │
    ├── Metrics Server          (클러스터에 1개, 메트릭 집계 및 API 제공)
    │
    ├── [Node 1]                (실제 서버, 클러스터에 여러 개)
    │     ├── kubelet           (Node마다 1개, Pod 메트릭 수집)
    │     ├── Pod A
    │     └── Pod B
    │
    └── [Node 2]
          ├── kubelet
          └── Pod C&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;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HPA Controller&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;클러스터에 1개&lt;/td&gt;
&lt;td&gt;메트릭 모니터링 + replicas 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metrics Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;클러스터에 1개&lt;/td&gt;
&lt;td&gt;kubelet 데이터 집계 + API 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kubelet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node마다 1개&lt;/td&gt;
&lt;td&gt;해당 Node의 모든 Pod 메트릭 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h3&gt;4. 전체 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubelet (각 Node)
  → 메트릭 수집
Metrics Server
  → kubelet으로부터 집계
HPA Controller
  → Metrics Server에 질의 → replicas 수 계산
Deployment Controller → ReplicaSet Controller → Scheduler → kubelet
  → 실제 Pod 생성/삭제&lt;/code&gt;&lt;/pre&gt;</description>
      <category>DevOps/Kubernetes</category>
      <category>HPA</category>
      <category>kubernetes</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/254</guid>
      <comments>https://kchanguk.tistory.com/entry/Kubernetes-HPA#entry254comment</comments>
      <pubDate>Sun, 3 May 2026 20:10:57 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes Ingress</title>
      <link>https://kchanguk.tistory.com/entry/Kubernetes-Ingress</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Ingress란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 &lt;b&gt;외부 HTTP/HTTPS 트래픽을 내부 Service로 라우팅&lt;/b&gt;하는 쿠버네티스 리소스&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인/경로 기반 라우팅 지원 (L7)&lt;/li&gt;
&lt;li&gt;TLS/HTTPS 종료 처리&lt;/li&gt;
&lt;li&gt;단독 사용 불가 &amp;rarr; 반드시 &lt;b&gt;Ingress Controller&lt;/b&gt; 필요 (nginx, traefik 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Service vs Ingress&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Ingress&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;한 줄 정의&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Pod을 위한 로드밸런서&lt;/td&gt;
&lt;td&gt;Service를 위한 로드밸런서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;레이어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;L4 (TCP/UDP)&lt;/td&gt;
&lt;td&gt;L7 (HTTP/HTTPS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;라우팅 기준&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;랜덤/라운드로빈&lt;/td&gt;
&lt;td&gt;도메인, URL 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;독립 실행&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (Service 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 전체 구조 (2단계 로드밸런싱)&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;외부 사용자
    │
    ▼
[ Ingress ]          &amp;larr; L7 로드밸런서 (어느 Service로?)
    │
    ├──&amp;rarr; [ Service A ]  &amp;larr; L4 로드밸런서 (어느 Pod으로?)
    │      ├&amp;rarr; [Pod]
    │      └&amp;rarr; [Pod]
    │
    └──&amp;rarr; [ Service B ]  &amp;larr; L4 로드밸런서 (어느 Pod으로?)
           ├&amp;rarr; [Pod]
           └&amp;rarr; [Pod]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 2단계 구조의 이점&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&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;b&gt;관심사의 분리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Ingress는 라우팅만, Service는 Pod 관리만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Pod IP 변동 대응&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Pod 재시작 시 IP가 바뀌어도 Service IP는 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;재사용성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;같은 Service를 여러 Ingress에서 참조 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Ingress &amp;rarr; Pod 직접 연결은?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기술적으로는 가능 (일부 Controller에서 비표준 지원)&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;권장하지 않음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&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;Pod IP 변동&lt;/td&gt;
&lt;td&gt;재시작 시 IP 바뀌어 연결 끊김&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;헬스체크 부재&lt;/td&gt;
&lt;td&gt;죽은 Pod으로 트래픽 전달 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스케일링 어려움&lt;/td&gt;
&lt;td&gt;Pod 추가/삭제 시 실시간 감지 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>DevOps/Kubernetes</category>
      <category>ingress</category>
      <category>kubernetes</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/253</guid>
      <comments>https://kchanguk.tistory.com/entry/Kubernetes-Ingress#entry253comment</comments>
      <pubDate>Sat, 25 Apr 2026 16:56:25 +0900</pubDate>
    </item>
    <item>
      <title>Java에서 데이터 없음을 표현하는 방법</title>
      <link>https://kchanguk.tistory.com/entry/Java%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%97%86%EC%9D%8C%EC%9D%84-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p&gt;&lt;strong&gt;&amp;quot;Java에서 &amp;#39;데이터/값 없음&amp;#39;을 어떻게 표현할 것인가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;상황별로 최적의 선택이 다릅니다. 다음 영역을 단계별로 살펴봅니다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;null의 본질
   ↓
클래스 필드 (primitive vs wrapper)
   ↓
Optional (어디에 쓸까)
   ↓
메서드 반환
   ↓
Enum 탐색 (다양한 패턴)
   ↓
컬렉션 / 배열 / 예외 처리&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;1️⃣ null의 본질&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; = &lt;strong&gt;참조할 객체 자체가 없음&lt;/strong&gt; (참조 타입 전용)&lt;/li&gt;
&lt;li&gt;&amp;quot;참조 없음&amp;quot; ≠ &amp;quot;빈 데이터&amp;quot;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; → 주소 자체가 없음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;, &lt;code&gt;[]&lt;/code&gt; → 객체는 있는데 내용이 비어있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; 참조 사용 시 → &lt;strong&gt;NullPointerException&lt;/strong&gt; 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2️⃣ 클래스 필드 설계 원칙&lt;/h2&gt;
&lt;h3&gt;기본 원칙&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;primitive 우선, 단 &amp;quot;값 없음&amp;quot;이 의미를 가질 땐 wrapper + null&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;의사결정&lt;/h3&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;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt; (primitive) ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;값이 없을 수 있음&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;Long&lt;/code&gt; (wrapper) + null ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0, -1을 sentinel로&lt;/td&gt;
&lt;td&gt;❌ 안티패턴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Optional&lt;/code&gt; 필드&lt;/td&gt;
&lt;td&gt;❌ 안티패턴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;class User {
    private Long id;           // ✅ wrapper (저장 전 null)
    private String name;       // ✅ 참조 타입 (null 가능)
    private int loginCount;    // ✅ primitive (0이 자연스러움)
    private Integer age;       // ✅ wrapper (미입력 = null)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;왜 sentinel(0, -1)은 안 되나?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;0 = 없음&amp;quot;은 &lt;strong&gt;암묵적 약속&lt;/strong&gt; → 실수 유발&lt;/li&gt;
&lt;li&gt;JPA/Hibernate가 wrapper 권장&lt;/li&gt;
&lt;li&gt;JSON 직렬화 시 모호함&lt;/li&gt;
&lt;li&gt;미래에 0이 유효 값이 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3️⃣ Optional은 어디에 쓰나?&lt;/h2&gt;
&lt;h3&gt;✅ 사용 권장&lt;/h3&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;strong&gt;필드&lt;/strong&gt; (직렬화 문제, 메모리 오버헤드, null 역설)&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;h2&gt;4️⃣ 메서드 반환 - null vs Optional&lt;/h2&gt;
&lt;h3&gt;원칙&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;필드:        wrapper + null ✅,  Optional ❌
메서드 반환: Optional ✅,        null ⚠️&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Optional 제대로 쓰기&lt;/h3&gt;
&lt;p&gt;❌ &lt;strong&gt;&lt;code&gt;.get()&lt;/code&gt;, &lt;code&gt;.isPresent()&lt;/code&gt; 남용 = 안티패턴&lt;/strong&gt; (null 체크와 다를 게 없음)&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;선언적 메서드 활용&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 기본값
opt.orElse(defaultValue);
opt.orElseGet(() -&amp;gt; compute());

// 예외
opt.orElseThrow(() -&amp;gt; new MyException());

// 동작
opt.ifPresent(this::handle);
opt.ifPresentOrElse(this::handle, this::onEmpty);

// 변환
opt.map(User::getName).filter(...).orElse(&amp;quot;unknown&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;사고방식 전환&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;❌ &amp;quot;Optional에서 값을 꺼내자(unwrap)&amp;quot;&lt;/p&gt;
&lt;p&gt;✅ &amp;quot;Optional 안에서 작업을 처리하자&amp;quot;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;5️⃣ Enum 탐색 패턴 (정확한 구분)&lt;/h2&gt;
&lt;h3&gt;5가지 패턴 비교&lt;/h3&gt;
&lt;h4&gt;패턴 1: UNKNOWN 반환 (가장 흔함) ⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;못 찾으면 UNKNOWN을 기본값으로 반환&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum Status {
    ACTIVE, INACTIVE, PENDING, UNKNOWN;

    public static Status fromCode(String code) {
        return Arrays.stream(values())
            .filter(s -&amp;gt; s.name().equals(code))
            .findFirst()
            .orElse(UNKNOWN);
    }
}

// 호출자가 직접 분기
Status s = Status.fromCode(code);
if (s == Status.UNKNOWN) {
    log.warn(&amp;quot;알 수 없는 상태&amp;quot;);
    return;
}
switch (s) {
    case ACTIVE -&amp;gt; doActive();
    case INACTIVE -&amp;gt; doInactive();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;strong&gt;간단, 명확, 동작 많아도 enum이 비대해지지 않음&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;패턴 2: Null Object Pattern (동작 단순할 때)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;UNKNOWN이 무해한 동작을 직접 가짐&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum Status {
    ACTIVE   { @Override public void process() { /* 처리 */ } },
    INACTIVE { @Override public void process() { /* 비활성 */ } },
    UNKNOWN  { @Override public void process() { /* 아무것도 안 함 */ } };

    public abstract void process();
}

// 호출자가 분기 없음
Status.fromCode(code).process();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;strong&gt;분기 제거되고 우아하지만, 동작이 많으면 enum 폭발  &lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;패턴 3: Strategy Pattern (동작 복잡할 때)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;enum은 식별자만, 동작은 별도 클래스로 분리&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum OrderStatus {
    CREATED, PAID, SHIPPED, DELIVERED, CANCELLED, UNKNOWN;
}

public interface OrderStatusHandler {
    void handle(Order order);
}

@Component
public class OrderStatusHandlerFactory {
    private final Map&amp;lt;OrderStatus, OrderStatusHandler&amp;gt; handlers = Map.of(
        OrderStatus.CREATED, new CreatedHandler(),
        OrderStatus.PAID, new PaidHandler(),
        OrderStatus.UNKNOWN, new NoOpHandler()  // ← 핸들러 레벨 Null Object
    );

    public OrderStatusHandler get(OrderStatus status) {
        return handlers.getOrDefault(status, new NoOpHandler());
    }
}

// 사용
factory.get(order.getStatus()).handle(order);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;strong&gt;enum 깔끔, 동작 분리, 확장 용이&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;패턴 4: Optional 반환 (호출자 결정)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public static Optional&amp;lt;Status&amp;gt; fromCode(String code) {
    return Arrays.stream(values())
        .filter(s -&amp;gt; s.name().equals(code))
        .findFirst();
}

// 호출자가 상황에 따라 처리
Status.fromCode(code)
    .ifPresentOrElse(this::process, () -&amp;gt; log.warn(&amp;quot;없음&amp;quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;strong&gt;호출자에게 유연성 제공&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;패턴 5: 예외 던지기 (엄격한 도메인)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public static Status fromCode(String code) {
    return Arrays.stream(values())
        .filter(s -&amp;gt; s.name().equals(code))
        .findFirst()
        .orElseThrow(() -&amp;gt; 
            new IllegalArgumentException(&amp;quot;알 수 없는 상태: &amp;quot; + code));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;strong&gt;결제, 주문 등 절대 잘못되면 안 되는 도메인&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;의사결정 가이드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;quot;못 찾은 enum을 어떻게 처리할까?&amp;quot;
    │
    ▼
동작이 단순하고 추상화 가능한가?
    │
    ├─ YES → Null Object Pattern
    │       (UNKNOWN이 무해한 동작 가짐)
    │
    └─ NO  → 동작이 많거나 복잡함
              │
              ▼
         어디에서 처리할까?
              │
              ├─ 알 수 없는 상태로 진행 OK
              │   → UNKNOWN 반환 + 호출자 분기 ⭐ (가장 흔함)
              │
              ├─ 동작을 깔끔히 분리하고 싶음
              │   → Strategy Pattern
              │
              ├─ 호출자에게 결정 위임
              │   → Optional 반환
              │
              └─ 절대 있으면 안 됨
                  → 예외 던지기 (Fail Fast)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Enum 패턴 비교표&lt;/h3&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;UNKNOWN 반환&lt;/strong&gt;&lt;/td&gt;
&lt;td&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;Null Object Pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&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;Strategy Pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&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;Optional 반환&lt;/strong&gt;&lt;/td&gt;
&lt;td&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;catch 필요&lt;/td&gt;
&lt;td&gt;엄격한 도메인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;실전 권장 순서&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기본&lt;/strong&gt;: UNKNOWN 반환 (또는 Optional 반환)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동작이 명확하고 단순&lt;/strong&gt;하면: Null Object Pattern으로 발전&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동작이 복잡&lt;/strong&gt;해지면: Strategy Pattern으로 분리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;있으면 안 되는 상황&lt;/strong&gt;: 예외 던지기&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;6️⃣ 컬렉션 (List, Set, Map)&lt;/h2&gt;
&lt;h3&gt;  절대 금지: null 컬렉션 반환&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ 최악의 안티패턴
public List&amp;lt;User&amp;gt; findUsers() {
    if (noResult) return null;
    return users;
}

List&amp;lt;User&amp;gt; users = findUsers();
for (User u : users) { ... }  //   NPE!&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;✅ 빈 컬렉션 반환&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public List&amp;lt;User&amp;gt; findUsers() {
    if (noResult) return Collections.emptyList();  // 또는 List.of()
    return users;
}

findUsers().forEach(this::process);  // 결과 없어도 안전 ✅&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;빈 컬렉션 만드는 방법&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 불변 빈 컬렉션 (메모리 효율적)
List.of();                       // Java 9+
Collections.emptyList();
Collections.emptySet();
Collections.emptyMap();

// 가변 빈 컬렉션
new ArrayList&amp;lt;&amp;gt;();
new HashMap&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Map - 키가 없을 때&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ 옛날 스타일
Object value = map.get(key);
if (value == null) {
    value = defaultValue;
}

// ✅ Java 8+
map.getOrDefault(key, defaultValue);
map.computeIfAbsent(key, k -&amp;gt; createNewValue());
map.putIfAbsent(key, defaultValue);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Map.get()의 함정 ⚠️&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 두 가지 의미가 섞임
map.get(key);
//   1. 키가 없음 → null
//   2. 키는 있는데 값이 null → null
// 구분하려면 containsKey() 사용&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;컬렉션 황금 규칙&lt;/strong&gt;:&lt;br&gt;&lt;strong&gt;&amp;quot;값 없음 = null이 아니라, 값 없음 = 빈 컬렉션&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;7️⃣ 배열 (Array)&lt;/h2&gt;
&lt;h3&gt;null 배열 vs 빈 배열&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ null 배열
public int[] getNumbers() {
    return null;
}

// ✅ 빈 배열
public int[] getNumbers() {
    return new int[0];
}

// 또는 상수로 메모리 절약
private static final int[] EMPTY_INT_ARRAY = new int[0];&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;컬렉션 → 배열 변환&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;List&amp;lt;String&amp;gt; list = getStrings();

// ✅ 안전한 패턴 (Java 11+)
String[] arr = list.toArray(String[]::new);

// 빈 리스트면 빈 배열 반환 (null 아님) ✅&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;배열 황금 규칙&lt;/strong&gt;:&lt;br&gt;&lt;strong&gt;&amp;quot;컬렉션과 동일 - null 배열보다 빈 배열을!&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;8️⃣ 예외 처리 (Exception)&lt;/h2&gt;
&lt;h3&gt;null 반환 vs 예외 던지기&lt;/h3&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;strong&gt;정상 흐름&lt;/strong&gt;의 &amp;quot;없음&amp;quot; (조회 실패 등)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Optional&lt;/code&gt; / null&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;호출자가 항상 값을 기대&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;예외 또는 default&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;code&gt;Optional&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;핵심 원칙: Fail Fast&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ null로 조용히 실패
public User getUser(Long id) {
    if (id == null) return null;
    return repository.findById(id).orElse(null);
}
// 호출자: 어디서 null이 됐는지 모름 → 디버깅 지옥  

// ✅ 즉시 예외로 명확히 실패
public User getUser(Long id) {
    Objects.requireNonNull(id, &amp;quot;id는 null일 수 없습니다&amp;quot;);
    return repository.findById(id)
        .orElseThrow(() -&amp;gt; new UserNotFoundException(id));
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;입력 검증 (Guard Clauses)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void process(User user, List&amp;lt;Order&amp;gt; orders) {
    Objects.requireNonNull(user, &amp;quot;user는 null일 수 없습니다&amp;quot;);
    Objects.requireNonNull(orders, &amp;quot;orders는 null일 수 없습니다&amp;quot;);

    if (orders.isEmpty()) {
        throw new IllegalArgumentException(&amp;quot;orders는 비어있을 수 없습니다&amp;quot;);
    }

    // 본 로직 - 여기서부터 null/empty 걱정 없음
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Checked vs Unchecked&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ✅ Unchecked - 프로그래머 실수
throw new IllegalArgumentException(&amp;quot;잘못된 입력&amp;quot;);
throw new IllegalStateException(&amp;quot;잘못된 상태&amp;quot;);

// ✅ Checked - 외부 요인 (호출자가 처리해야 함)
throw new IOException(&amp;quot;파일 읽기 실패&amp;quot;);

// ✅ 도메인 예외는 Unchecked로
public User findUser(Long id) {
    throw new UserNotFoundException(id);  // RuntimeException 상속
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Optional + 예외의 우아한 조합&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserService {
    // 호출자가 결정
    public Optional&amp;lt;User&amp;gt; findUser(Long id) {
        return repository.findById(id);
    }

    // 반드시 있어야 함
    public User getUser(Long id) {
        return findUser(id)
            .orElseThrow(() -&amp;gt; new UserNotFoundException(id));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;&amp;quot;예상 가능한 없음 → Optional, 비정상 상태 → 예외 (Fail Fast)&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&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;권장 방식&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;필드&lt;/strong&gt; - 항상 값 존재&lt;/td&gt;
&lt;td&gt;primitive (&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&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;wrapper + null (&lt;code&gt;Integer&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;sentinel (0, -1), Optional&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;code&gt;Optional&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enum 탐색&lt;/strong&gt; - 단순 기본값&lt;/td&gt;
&lt;td&gt;UNKNOWN 반환 ⭐&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enum 탐색&lt;/strong&gt; - 동작 단순&lt;/td&gt;
&lt;td&gt;Null Object Pattern&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enum 탐색&lt;/strong&gt; - 동작 복잡&lt;/td&gt;
&lt;td&gt;Strategy Pattern&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enum 탐색&lt;/strong&gt; - 호출자 결정&lt;/td&gt;
&lt;td&gt;Optional 반환&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enum 탐색&lt;/strong&gt; - 잘못된 입력&lt;/td&gt;
&lt;td&gt;예외 던지기&lt;/td&gt;
&lt;td&gt;UNKNOWN&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;code&gt;List.of()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Map 조회&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getOrDefault&lt;/code&gt;, &lt;code&gt;computeIfAbsent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null 체크&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;code&gt;new int[0]&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;null&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;code&gt;Objects.requireNonNull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;조용한 null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;예상 가능한 &amp;quot;없음&amp;quot;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Optional&lt;/code&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;예외 (Fail Fast)&lt;/td&gt;
&lt;td&gt;null 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;  핵심 원칙 8가지&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;null&lt;/code&gt;은 &amp;quot;참조할 대상 없음&amp;quot;이지, &amp;quot;빈 데이터&amp;quot;가 아니다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필드 설계&lt;/strong&gt;: primitive 우선, &amp;quot;없음&amp;quot;이 필요하면 wrapper + null. &lt;strong&gt;sentinel은 안티패턴.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Optional&lt;/code&gt;은 메서드 반환 전용&lt;/strong&gt;. 필드/매개변수에 쓰지 말 것.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Optional&lt;/code&gt;은 &lt;code&gt;.get()&lt;/code&gt; 쓰라고 만든 게 아니다.&lt;/strong&gt; &lt;code&gt;orElse&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;ifPresent&lt;/code&gt;로 처리.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enum 탐색은 상황별로 5가지 패턴&lt;/strong&gt;: UNKNOWN 반환 / Null Object / Strategy / Optional / 예외.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;UNKNOWN 반환&amp;quot;과 &amp;quot;Null Object Pattern&amp;quot;은 다른 개념&lt;/strong&gt;. 동작 추상화 가능 여부에 따라 선택.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컬렉션과 배열은 절대 null 반환 금지&lt;/strong&gt; → 항상 빈 컬렉션/빈 배열.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예외는 비정상 상태에서 빠르게 던져라 (Fail Fast)&lt;/strong&gt; → 조용한 null 반환은 디버깅 지옥.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;  영역별 사고방식 정리&lt;/h2&gt;
&lt;h3&gt;  데이터 표현 영역&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;필드        → wrapper + null (sentinel ❌)
컬렉션      → 빈 컬렉션 (null ❌)
배열        → 빈 배열 (null ❌)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;  메서드 시그니처 영역&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;반환값      → Optional ⭐
매개변수    → 오버로딩, Builder 패턴
입력 검증   → Objects.requireNonNull (Fail Fast)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;  Enum 탐색 영역&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;단순 기본값         → UNKNOWN 반환 ⭐
동작 통일 가능      → Null Object Pattern
동작 복잡           → Strategy Pattern
호출자 결정         → Optional 반환
엄격한 도메인       → 예외 던지기&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt; ️ 안전 장치 영역&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;정상 흐름의 없음    → Optional
비정상 상태         → 예외 (Fail Fast)
외부 경계           → 즉시 검증
무음 실패 방지      → 예외 무시 금지&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java</category>
      <category>Java</category>
      <category>NULL</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/252</guid>
      <comments>https://kchanguk.tistory.com/entry/Java%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%97%86%EC%9D%8C%EC%9D%84-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95#entry252comment</comments>
      <pubDate>Sat, 18 Apr 2026 17:14:01 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes Service</title>
      <link>https://kchanguk.tistory.com/entry/Kubernetes-Service</link>
      <description>&lt;h1&gt;Kubernetes Service 정리&lt;/h1&gt;
&lt;hr&gt;
&lt;h2&gt;1. Service란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pod는 동적으로 생성/삭제되어 IP가 계속 바뀜&lt;/li&gt;
&lt;li&gt;Service는 Pod들에게 &lt;strong&gt;안정적인 고정 IP / DNS&lt;/strong&gt;를 제공하는 리소스&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pod 집합에 대한 추상화된 네트워크 접근 방법&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. Service의 주요 역할&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;/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;Pod가 바뀌어도 Service IP/DNS는 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;로드밸런싱&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;여러 Pod에 트래픽 자동 분산 (Round Robin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;서비스 디스커버리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DNS 이름으로 Pod 접근 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;헬스체크 연동&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;죽은 Pod는 자동으로 트래픽 대상에서 제외&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;3. Service의 종류&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClusterIP&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;code&gt;NodePort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;노드 IP + 포트&lt;/td&gt;
&lt;td&gt;개발/테스트 외부 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LoadBalancer&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;code&gt;ExternalName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;외부 DNS&lt;/td&gt;
&lt;td&gt;외부 서비스 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;4. Service는 &amp;quot;Pod를 위한 로드밸런서&amp;quot;?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;맞습니다&lt;/strong&gt; — 여러 Pod에 트래픽을 분산시키는 로드밸런서 역할&lt;/li&gt;
&lt;li&gt;➕ &lt;strong&gt;그 이상&lt;/strong&gt; — 고정 엔드포인트 제공, 서비스 디스커버리, 헬스체크 연동까지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. Service IP는 VIP?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;VIP와 유사한 역할&lt;/strong&gt; — 고정된 단일 진입점 제공, 뒤의 Pod가 바뀌어도 동일 IP로 접근&lt;/li&gt;
&lt;li&gt;실무에서도 Service IP를 &lt;strong&gt;VIP&lt;/strong&gt;라고 부르기도 함&lt;/li&gt;
&lt;li&gt;단, 구현 방식이 전용 LB 장비가 아닌 &lt;strong&gt;각 노드의 iptables / IPVS 규칙&lt;/strong&gt;으로 동작&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Kubernetes</category>
      <category>kubernetes</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/251</guid>
      <comments>https://kchanguk.tistory.com/entry/Kubernetes-Service#entry251comment</comments>
      <pubDate>Wed, 1 Apr 2026 22:33:48 +0900</pubDate>
    </item>
    <item>
      <title>n8n</title>
      <link>https://kchanguk.tistory.com/entry/n8n</link>
      <description>&lt;div class=&quot;n8n-technical-guide&quot; style=&quot;line-height: 1.9; color: #333; font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, sans-serif; max-width: 950px; margin: auto;&quot;&gt;

    &lt;h3 style=&quot;font-size: 1.8em; color: #2e5bff; border-bottom: 3px solid #2e5bff; padding-bottom: 8px; margin-top: 50px;&quot;&gt;n8n이란&lt;/h3&gt;
    &lt;p&gt;
        &lt;b&gt;n8n(nodemation)&lt;/b&gt;은 Node.js 기반의 &lt;b&gt;오픈소스 워크플로우 자동화 엔진&lt;/b&gt;입니다. 다양한 서비스(SaaS), 데이터베이스, API를 시각적인 '노드(Node)' 형태로 연결하여 비즈니스 로직을 자동화합니다. Zapier와 같은 서비스와 유사하지만, 사용자가 자신의 서버에 직접 설치(Self-hosting)하여 운영할 수 있는 &lt;b&gt;'Fair-code'&lt;/b&gt; 라이선스 모델을 채택하고 있어 데이터 주권 확보에 최적화되어 있습니다.
    &lt;/p&gt;
    

    &lt;h3 style=&quot;font-size: 1.8em; color: #2e5bff; border-bottom: 3px solid #2e5bff; padding-bottom: 8px; margin-top: 50px;&quot;&gt;n8n의 특징&lt;/h3&gt;
    &lt;ul&gt;
        &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;자가 호스팅 (Self-hosting):&lt;/b&gt; 온프레미스(On-premise)나 개인 클라우드(Docker 등)에 직접 설치하여 외부 서버로의 데이터 유출을 원천 차단합니다.&lt;/li&gt;
        &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;노드 기반 시각적 설계:&lt;/b&gt; 복잡한 프로그래밍 코드를 작성하는 대신, 직관적인 UI에서 노드를 드래그 앤 드롭하고 선으로 연결하여 로직을 설계합니다.&lt;/li&gt;
        &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;강력한 확장성 (Low-code):&lt;/b&gt; 기본 노드로 부족한 로직은 &lt;b&gt;JavaScript&lt;/b&gt;를 직접 작성하여 확장할 수 있으며, API가 있는 모든 서비스와 연동 가능합니다.&lt;/li&gt;
        &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;JSON 기반 데이터 흐름:&lt;/b&gt; 모든 노드 간 데이터 교환은 표준 JSON 배열 형식을 따르므로 개발자가 데이터를 가공하고 참조하기 매우 용이합니다.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;h3 style=&quot;font-size: 1.8em; color: #2e5bff; border-bottom: 3px solid #2e5bff; padding-bottom: 8px; margin-top: 50px;&quot;&gt;n8n의 장점 및 단점&lt;/h3&gt;
    &lt;div style=&quot;display: flex; gap: 20px; flex-wrap: wrap; margin-top: 20px;&quot;&gt;
        &lt;div style=&quot;flex: 1; min-width: 300px; background: #eef2ff; padding: 25px; border-radius: 12px; border: 1px solid #d1d9ff;&quot;&gt;
            &lt;h4 style=&quot;margin-top: 0; color: #2e5bff;&quot;&gt;✅ 장점&lt;/h4&gt;
            &lt;ul style=&quot;padding-left: 20px; font-size: 0.95em;&quot;&gt;
                &lt;li&gt;&lt;b&gt;비용 효율성:&lt;/b&gt; 실행 횟수(Task)당 과금이 없어 대규모 트래픽 처리 시 비용 부담이 거의 없습니다.&lt;/li&gt;
                &lt;li&gt;&lt;b&gt;보안 및 프라이버시:&lt;/b&gt; 민감한 비즈니스 데이터를 내부망 내에서만 처리할 수 있습니다.&lt;/li&gt;
                &lt;li&gt;&lt;b&gt;로직의 자유도:&lt;/b&gt; 무한 루프, 조건부 분기, 데이터 병합 등 프로그래밍 수준의 복잡한 설계가 가능합니다.&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div style=&quot;flex: 1; min-width: 300px; background: #fff5f5; padding: 25px; border-radius: 12px; border: 1px solid #ffe3e3;&quot;&gt;
            &lt;h4 style=&quot;margin-top: 0; color: #e03131;&quot;&gt;❌ 단점&lt;/h4&gt;
            &lt;ul style=&quot;padding-left: 20px; font-size: 0.95em;&quot;&gt;
                &lt;li&gt;&lt;b&gt;인프라 관리 부담:&lt;/b&gt; 서버 구축, DB 튜닝, 보안 업데이트, 백업 등을 사용자가 직접 관리해야 합니다.&lt;/li&gt;
                &lt;li&gt;&lt;b&gt;초기 학습 곡선:&lt;/b&gt; JSON 구조 및 자바스크립트에 대한 기초 지식이 없으면 고급 기능을 활용하기 어렵습니다.&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;h3 style=&quot;font-size: 1.8em; color: #2e5bff; border-bottom: 3px solid #2e5bff; padding-bottom: 8px; margin-top: 50px;&quot;&gt;n8n의 워크플로우&lt;/h3&gt;
    &lt;p&gt;
        n8n의 워크플로우는 &lt;b&gt;트리거(Trigger) → 로직/액션 노드 → 데이터 출력&lt;/b&gt;의 순서로 실행됩니다.
    &lt;/p&gt;
    &lt;ul&gt;
        &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;비동기 병렬 처리:&lt;/b&gt; 기본적으로 여러 요청이 동시에 인입될 경우 각 작업을 **병렬(Parallel)**로 즉시 실행합니다.&lt;/li&gt;
        &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;Wait 노드의 특수 동작:&lt;/b&gt; 워크플로우 중간에 대기가 발생하면 현재 상태를 &lt;b&gt;직렬화(Serialization)&lt;/b&gt;하여 DB에 저장한 뒤 메모리를 해제합니다. 이후 정해진 시간에 DB에서 상태를 복원(Hydration)하여 작업을 재개하므로 서버 재시작 시에도 데이터가 보호됩니다.&lt;/li&gt;
    &lt;/ul&gt;
    

    &lt;h3 style=&quot;font-size: 1.8em; color: #2e5bff; border-bottom: 3px solid #2e5bff; padding-bottom: 8px; margin-top: 50px;&quot;&gt;n8n 노드 종류&lt;/h3&gt;
    &lt;div style=&quot;background: #f8f9fa; padding: 20px; border-radius: 10px; border: 1px solid #eee;&quot;&gt;
        &lt;ul style=&quot;margin: 0;&quot;&gt;
            &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;Trigger Nodes:&lt;/b&gt; 워크플로우의 시작점 (Webhook, Cron/Schedule, 이메일 수신 등)&lt;/li&gt;
            &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;Regular Nodes:&lt;/b&gt; 실제 작업을 수행 (Google Sheets 기록, Slack 메시지 전송, DB 쿼리 등)&lt;/li&gt;
            &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;Logic Nodes:&lt;/b&gt; 흐름을 제어 (IF 조건문, Switch 분기, Wait 대기, Merge 병합)&lt;/li&gt;
            &lt;li style=&quot;margin-bottom: 10px;&quot;&gt;&lt;b&gt;Code Node:&lt;/b&gt; JavaScript를 사용하여 데이터를 커스텀 가공하는 노드&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;

    &lt;h3 style=&quot;font-size: 1.8em; color: #2e5bff; border-bottom: 30px solid #2e5bff; padding-bottom: 8px; margin-top: 50px;&quot;&gt;n8n 데이터 저장 및 처리 구조&lt;/h3&gt;
    &lt;p&gt;성능과 안정성을 위해 n8n은 데이터를 물리적으로 분리하여 관리합니다.&lt;/p&gt;
    &lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #dee2e6; text-align: left;&quot;&gt;
        &lt;thead&gt;
            &lt;tr style=&quot;background: #f1f3f5;&quot;&gt;
                &lt;th style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;구분&lt;/th&gt;
                &lt;th style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;저장 위치&lt;/th&gt;
                &lt;th style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;역할 및 특성&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
            &lt;tr&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6; font-weight: bold;&quot;&gt;활성 실행 데이터&lt;/td&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;RAM (메모리)&lt;/td&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;실시간 연산 데이터. 재시작 시 휘발됨(In-flight data).&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6; font-weight: bold;&quot;&gt;영속성 데이터&lt;/td&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;DB (Disk)&lt;/td&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;설계도(JSON), Credentials, 실행 이력, Wait 상태 정보.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6; font-weight: bold;&quot;&gt;바이너리 데이터&lt;/td&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;Disk / S3&lt;/td&gt;
                &lt;td style=&quot;padding: 12px; border: 1px solid #dee2e6;&quot;&gt;이미지, PDF 등 대용량 파일 저장.&lt;/td&gt;
            &lt;/tr&gt;
        &lt;/tbody&gt;
    &lt;/table&gt;
    &lt;p style=&quot;font-size: 0.9em; color: #666;&quot;&gt;
        * 운영 환경에서는 PostgreSQL을 권장하며, 기존 인프라에 따라 MySQL 연동도 가능합니다. Docker 배포 시 &lt;code&gt;/home/node/.n8n&lt;/code&gt; 볼륨 마운트는 필수적입니다.
    &lt;/p&gt;
    

&lt;/div&gt;</description>
      <category>DevOps</category>
      <category>n8n</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/250</guid>
      <comments>https://kchanguk.tistory.com/entry/n8n#entry250comment</comments>
      <pubDate>Tue, 24 Mar 2026 11:30:31 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes Deployment</title>
      <link>https://kchanguk.tistory.com/entry/Kubernetes-Deployment</link>
      <description>&lt;h2&gt;1. Deployment란?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;는 Kubernetes에서 &lt;strong&gt;애플리케이션의 선언적 업데이트&lt;/strong&gt;를 관리하는 리소스입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&amp;quot;이 앱을 이런 설정으로, 이 개수만큼 항상 실행되도록 유지해줘!&amp;quot; 라고 Kubernetes에게 선언하는 것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;2. 리소스 계층 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Deployment
    └── ReplicaSet
            └── Pod
                    └── Container&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;3. 각 리소스 역할&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;건축 설계도&lt;/td&gt;
&lt;td&gt;Pod의 설계도 관리 (이미지, 리소스, 버전, 배포 전략)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReplicaSet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;건설 현장 감독&lt;/td&gt;
&lt;td&gt;실제 Pod 생성 / 제거 / 유지 / 재시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pod&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;Container&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;hr&gt;
&lt;h2&gt;4. Deployment가 관리하는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;  어떤 &lt;strong&gt;이미지&lt;/strong&gt;를 사용할지 (&lt;code&gt;image: my-app:1.0.0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;리소스 제한&lt;/strong&gt; (CPU, Memory requests/limits)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;환경변수&lt;/strong&gt;, ConfigMap, Secret 연결&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;버전 히스토리&lt;/strong&gt; (롤백용 이전 ReplicaSet 보존)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;배포 전략&lt;/strong&gt; (RollingUpdate, Recreate 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. ReplicaSet이 관리하는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ 지정된 수의 &lt;strong&gt;Pod 항상 유지&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;➕ Pod &lt;strong&gt;부족 시 생성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;➖ Pod &lt;strong&gt;초과 시 제거&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;  Pod &lt;strong&gt;장애 시 재생성&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;6. 스케일링 관리&lt;/h2&gt;
&lt;p&gt;스케일링의 &lt;strong&gt;목표 수치를 선언&lt;/strong&gt;하는 것은 Deployment (또는 HPA), &lt;strong&gt;실제로 Pod 수를 맞추는 것&lt;/strong&gt;은 ReplicaSet입니다.&lt;/p&gt;
&lt;h3&gt;수동 스케일링&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;사용자 → Deployment (replicas 선언) → ReplicaSet (Pod 수 실현) → Pod&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;자동 스케일링 (HPA)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CPU/메모리 임계값 초과
        ↓
       HPA (자동 감지)
        ↓
   Deployment (replicas 자동 조절)
        ↓
   ReplicaSet (Pod 수 실현)
        ↓
   Pod Pod Pod Pod Pod&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;7. 버전 관리 &amp;amp; 롤백 원리&lt;/h2&gt;
&lt;p&gt;Deployment는 새 버전 배포 시 &lt;strong&gt;이전 ReplicaSet을 삭제하지 않고 보존&lt;/strong&gt;합니다.&lt;br&gt;이 덕분에 빠른 롤백이 가능합니다.&lt;/p&gt;
&lt;h3&gt;배포 시&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[현재] Deployment (image: v2)
         ├── ReplicaSet-A (v1) → Pod 0개  ← 보존됨
         └── ReplicaSet-B (v2) → Pod 3개  ← 현재 운영 중&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;롤백 시&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[롤백] Deployment (image: v1으로 복구)
         ├── ReplicaSet-A (v1) → Pod 3개  ← 복구됨
         └── ReplicaSet-B (v2) → Pod 0개  ← 축소됨&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;8. ReplicaSet 데이터 저장 방식&lt;/h2&gt;
&lt;p&gt;ReplicaSet을 포함한 모든 Kubernetes 리소스는 &lt;strong&gt;etcd&lt;/strong&gt;에 저장됩니다.&lt;/p&gt;
&lt;h3&gt;etcd란?&lt;/h3&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;strong&gt;종류&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;분산 Key-Value 데이터베이스&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;저장 내용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;모든 K8s 리소스 상태 (Deployment, ReplicaSet, Pod 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;고가용성, 분산 저장, Watch 기능 (변경 감지)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;저장 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl apply -f deployment.yaml
        ↓
   API Server
        ↓
  etcd (분산 Key-Value DB) ← 여기에 영구 저장!
        ↓
  Controller Manager
  (etcd 상태를 보고 ReplicaSet, Pod 생성/관리)&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;9. ReplicaSet 보관 주기 관리&lt;/h2&gt;
&lt;p&gt;이전 ReplicaSet의 보존 개수는 &lt;strong&gt;&lt;code&gt;revisionHistoryLimit&lt;/code&gt;&lt;/strong&gt; 으로 설정합니다. (기본값: 10)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  revisionHistoryLimit: 3  # 이전 ReplicaSet 보존 개수
  selector:
    matchLabels:
      app: my-app
  template:
    ...&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;동작 방식 (revisionHistoryLimit: 3)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;배포 v1 → ReplicaSet-A 생성
배포 v2 → ReplicaSet-B 생성  (A 보존)
배포 v3 → ReplicaSet-C 생성  (A, B 보존)
배포 v4 → ReplicaSet-D 생성  (B, C 보존, A 삭제! ← 3개 초과)
배포 v5 → ReplicaSet-E 생성  (C, D 보존, B 삭제! ← 3개 초과)&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;⚠️ &lt;code&gt;revisionHistoryLimit: 0&lt;/code&gt; 으로 설정하면 이전 ReplicaSet을 전혀 보존하지 않아 &lt;strong&gt;롤백이 불가능&lt;/strong&gt;해집니다!&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;10. 전체 핵심 요약&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pod 설계도 (이미지, 리소스, 버전, 배포 전략 관리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReplicaSet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pod 실제 생성/제거/유지 담당&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;스케일링 선언&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deployment 또는 HPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;스케일링 실현&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ReplicaSet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;롤백 가능 이유&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;이전 ReplicaSet을 etcd에 보존하기 때문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;데이터 저장소&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;etcd (디스크 영구 저장)&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;code&gt;revisionHistoryLimit&lt;/code&gt; (기본값: 10)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</description>
      <category>Container &amp;amp; Orchestration</category>
      <category>kubernetes</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/249</guid>
      <comments>https://kchanguk.tistory.com/entry/Kubernetes-Deployment#entry249comment</comments>
      <pubDate>Thu, 5 Mar 2026 12:13:02 +0900</pubDate>
    </item>
    <item>
      <title>Java 22</title>
      <link>https://kchanguk.tistory.com/entry/Java-22</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Unnamed Variables &amp;amp; Patterns (JEP 456) - 정식 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언더스코어(&lt;code&gt;_&lt;/code&gt;)를 사용하여 사용하지 않는 변수나 패턴을 표시할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 사용하지 않는 변수
try {
    // ... 
} catch (Exception _) {  // 예외 변수를 사용하지 않음
    System.out.println(&quot;에러 발생&quot;);
}

// 람다에서 사용하지 않는 파라미터
list. forEach((_, value) -&amp;gt; System.out.println(value));

// 패턴 매칭에서
if (obj instanceof Point(int x, _)) {  // y 값은 필요 없음
    System.out.println(&quot;x = &quot; + x);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Statements Before super() (JEP 447) - 프리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 &lt;code&gt;super()&lt;/code&gt; 호출 전에 문장을 실행할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class Child extends Parent {
    public Child(int value) {
        // super() 전에 검증 가능! 
        if (value &amp;lt; 0) {
            throw new IllegalArgumentException(&quot;음수 불가&quot;);
        }
        super(value);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. String Templates (JEP 459) - 두 번째 프리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 템플릿을 통한 안전한 문자열 보간 기능&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;String name = &quot;홍길동&quot;;
int age = 25;
String message = STR.&quot;이름:  \{name}, 나이:  \{age}&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Stream Gatherers (JEP 461) - 프리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream API에 커스텀 중간 연산을 추가할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 고정 크기 윈도우로 그룹화
List&amp;lt;List&amp;lt;Integer&amp;gt;&amp;gt; windows = Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowFixed(2))
    .toList();
// 결과: [[1, 2], [3, 4], [5]]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Structured Concurrency (JEP 462) - 두 번째 프리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 동시성으로 멀티스레드 프로그래밍 단순화&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;try (var scope = new StructuredTaskScope. ShutdownOnFailure()) {
    Future&amp;lt;String&amp;gt; user = scope.fork(() -&amp;gt; fetchUser());
    Future&amp;lt;Integer&amp;gt; order = scope.fork(() -&amp;gt; fetchOrder());

    scope.join();
    scope.throwIfFailed();

    return new Response(user.resultNow(), order.resultNow());
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. Scoped Values (JEP 464) - 두 번째 프리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThreadLocal의 대안으로 불변 데이터 공유&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;final static ScopedValue&amp;lt;User&amp;gt; CURRENT_USER = ScopedValue.newInstance();

ScopedValue.where(CURRENT_USER, user)
    .run(() -&amp;gt; processRequest());&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>Java/버전별 변화</category>
      <category>docker</category>
      <category>Docker Compose</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/248</guid>
      <comments>https://kchanguk.tistory.com/entry/Java-22#entry248comment</comments>
      <pubDate>Sun, 28 Dec 2025 21:47:27 +0900</pubDate>
    </item>
    <item>
      <title>Docker Compose</title>
      <link>https://kchanguk.tistory.com/entry/Docker-Compose</link>
      <description>&lt;h1&gt;Docker Compose 가이드&lt;/h1&gt;
&lt;p&gt;Docker Compose는 &lt;strong&gt;여러 개의 Docker 컨테이너를 정의하고 실행하기 위한 도구&lt;/strong&gt;입니다.  YAML 파일을 사용하여 애플리케이션의 서비스, 네트워크, 볼륨 등을 구성할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;  주요 특징&lt;/h2&gt;
&lt;h3&gt;1. 다중 컨테이너 관리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;하나의 명령어로 여러 컨테이너를 동시에 시작/중지할 수 있습니다&lt;/li&gt;
&lt;li&gt;복잡한 애플리케이션 스택을 쉽게 관리할 수 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 선언적 구성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; 파일에 인프라를 코드로 정의합니다&lt;/li&gt;
&lt;li&gt;버전 관리가 가능하고 재현 가능한 환경을 만들 수 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;  기본 구조 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &amp;#39;3.8&amp;#39;

services:
  # 웹 애플리케이션 서비스
  web:
    build: ./app
    ports: 
      - &amp;quot;3000:3000&amp;quot;
    environment: 
      - DATABASE_URL=postgres://db:5432/myapp
    depends_on:
      - db
    volumes:
      - ./app:/usr/src/app

  # 데이터베이스 서비스
  db:
    image: postgres: 15
    environment: 
      - POSTGRES_DB=myapp
      - POSTGRES_PASSWORD=secret
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data: &lt;/code&gt;&lt;/pre&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;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서비스 시작 (컨테이너 생성 및 실행)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;백그라운드에서 서비스 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서비스 중지 및 컨테이너 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서비스 이미지 빌드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서비스 로그 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose ps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행 중인 컨테이너 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose exec &amp;lt;서비스&amp;gt; &amp;lt;명령&amp;gt;&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;services (서비스 정의)&lt;/h3&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;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용할 Docker 이미지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dockerfile 경로 (이미지를 직접 빌드할 때)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;포트 매핑 (호스트: 컨테이너)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volumes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;볼륨 마운트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;환경 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;depends_on&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서비스 의존성 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;restart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;재시작 정책 (always, on-failure 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;volumes (볼륨 정의)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 영속성을 위한 볼륨 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;networks (네트워크 정의)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 간 통신을 위한 네트워크 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;  사용 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 서비스 시작 (빌드 포함)
docker compose up --build

# 백그라운드 실행
docker compose up -d

# 특정 서비스만 시작
docker compose up web

# 서비스 중지 및 볼륨까지 삭제
docker compose down -v

# 서비스 스케일링 (web 서비스를 3개로)
docker compose up -d --scale web=3&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;✅ 장점&lt;/h2&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;CI/CD 통합&lt;/strong&gt;: 테스트 및 배포 파이프라인에 쉽게 통합&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h1&gt;Docker Compose 주의사항 및 단점&lt;/h1&gt;
&lt;h2&gt;⚠️ 주의사항&lt;/h2&gt;
&lt;h3&gt;1. 프로덕션 환경에서의 제한&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker Compose는 &lt;strong&gt;단일 호스트&lt;/strong&gt;에서만 작동합니다&lt;/li&gt;
&lt;li&gt;대규모 프로덕션 환경에서는 Kubernetes, Docker Swarm 등 오케스트레이션 도구를 권장합니다&lt;/li&gt;
&lt;li&gt;고가용성(HA), 자동 복구, 로드 밸런싱 기능이 제한적입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. depends_on의 한계&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  web:
    depends_on: 
      - db  # db 컨테이너가 &amp;quot;시작&amp;quot;되면 web 시작&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;depends_on&lt;/code&gt;은 컨테이너 &lt;strong&gt;시작 순서&lt;/strong&gt;만 보장합니다&lt;/li&gt;
&lt;li&gt;서비스가 &lt;strong&gt;실제로 준비(ready)&lt;/strong&gt; 되었는지는 확인하지 않습니다&lt;/li&gt;
&lt;li&gt;해결책: &lt;code&gt;healthcheck&lt;/code&gt; + &lt;code&gt;condition&lt;/code&gt; 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  web: 
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres: 15
    healthcheck:
      test: [&amp;quot;CMD-SHELL&amp;quot;, &amp;quot;pg_isready -U postgres&amp;quot;]
      interval: 5s
      timeout: 5s
      retries: 5&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 환경 변수 보안&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# ❌ 안전하지 않음 - 비밀번호 노출
environment:
  - DB_PASSWORD=mysecretpassword

# ✅ 권장 - . env 파일 또는 secrets 사용
env_file:
  - .env&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;에 민감한 정보를 직접 작성하지 마세요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt; 파일은 반드시 &lt;code&gt;.gitignore&lt;/code&gt;에 추가하세요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 볼륨 데이터 삭제 주의&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ⚠️ 주의! 볼륨까지 모두 삭제됨
docker compose down -v

# 안전하게 중지만 (볼륨 유지)
docker compose down&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 네트워크 충돌&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;여러 프로젝트에서 같은 포트를 사용하면 충돌 발생&lt;/li&gt;
&lt;li&gt;프로젝트별로 다른 포트를 지정하거나 네트워크 분리 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;  단점&lt;/h2&gt;
&lt;h3&gt;1. 확장성 제한&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Docker Compose&lt;/th&gt;
&lt;th&gt;Kubernetes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&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;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&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;/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;2. 모니터링 기능 부재&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;내장된 모니터링, 로깅 시스템이 없습니다&lt;/li&gt;
&lt;li&gt;별도 도구 필요 (Prometheus, Grafana, ELK Stack 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 서비스 디스커버리 제한&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기본적인 DNS 기반 서비스 디스커버리만 제공&lt;/li&gt;
&lt;li&gt;복잡한 마이크로서비스 환경에서는 부족할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 리소스 관리의 한계&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# 리소스 제한 설정 가능하지만... 
services:
  web: 
    deploy:
      resources: 
        limits:
          cpus:  &amp;#39;0.5&amp;#39;
          memory: 512M&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;deploy&lt;/code&gt; 섹션은 Swarm 모드에서만 완전히 동작&lt;/li&gt;
&lt;li&gt;일반 Compose에서는 &lt;code&gt;--compatibility&lt;/code&gt; 플래그 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 버전 호환성 문제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker Engine 버전에 따라 지원되는 Compose 파일 버전이 다름&lt;/li&gt;
&lt;li&gt;팀원 간 Docker 버전 차이로 문제 발생 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. 디버깅의 어려움&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;여러 컨테이너 간 문제 추적이 복잡할 수 있음&lt;/li&gt;
&lt;li&gt;로그가 분산되어 있어 통합 확인이 어려움&lt;/li&gt;
&lt;/ul&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;적합성&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;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;최적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트/CI&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;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&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;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;td&gt;비권장 (K8s 추천)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;  Best Practices&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# docker-compose.yml 모범 사례
version:  &amp;#39;3.8&amp;#39;

services:
  web: 
    image: myapp:${APP_VERSION:-latest}  # 버전 태그 사용
    restart: unless-stopped              # 재시작 정책
    logging:                             # 로깅 설정
      driver: &amp;quot;json-file&amp;quot;
      options: 
        max-size: &amp;quot;10m&amp;quot;
        max-file: &amp;quot;3&amp;quot;
    healthcheck:                          # 헬스체크 설정
      test:  [&amp;quot;CMD&amp;quot;, &amp;quot;curl&amp;quot;, &amp;quot;-f&amp;quot;, &amp;quot;http://localhost:3000/health&amp;quot;]
      interval: 30s
      timeout: 10s
      retries: 3&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/compose/compose-file/&quot;&gt;Compose 파일 레퍼런스&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/overview/&quot;&gt;Kubernetes vs Docker Compose 비교&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;strong&gt;요약&lt;/strong&gt;: Docker Compose는 &lt;strong&gt;개발 및 테스트 환경&lt;/strong&gt;에서는 훌륭한 도구이지만, &lt;strong&gt;대규모 프로덕션 환경&lt;/strong&gt;에서는 Kubernetes 같은 오케스트레이션 도구를 고려하세요! &lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <category>기타</category>
      <category>docker</category>
      <category>Docker Compose</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/247</guid>
      <comments>https://kchanguk.tistory.com/entry/Docker-Compose#entry247comment</comments>
      <pubDate>Sun, 21 Dec 2025 21:59:44 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 하이드레이션</title>
      <link>https://kchanguk.tistory.com/entry/Nextjs-%ED%95%98%EC%9D%B4%EB%93%9C%EB%A0%88%EC%9D%B4%EC%85%98</link>
      <description>&lt;p&gt;하이드레이션이란 서버에서 HTML을 먼저 보내줘 사용자가 화면을 빠르게 볼 수 있게 한 다음, 클라이언트에서 해당 HTML에 JavaScript코드를 결합하여 상호작용이 가능한 동적인 웹페이지로 만드는 과정입니다.&lt;/p&gt;
&lt;h1&gt;하이드레이션의 주요 역할 및 필요성&lt;/h1&gt;
&lt;p&gt;Next.js는 기본적으로 SSR이나 SSG 방식으로 HTML을 미리 생성하여 클라이언트에 전달할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;HTML을 미리 생성과 하이드레이션의 이점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;초기 로딩 속도 향상 및 SEO 최적화: 서버에서 완성된 HTML을 받기 때문에 사용자는 빈 화면이 아닌 내용이 채워진 페이지를 빠르게 볼 수 있으며, 검색 엔진 최적화에도 유리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;React 기능 활성화: 서버에서 내려준 HTML은 단순히 구조만 있는 정적인 상태입니다. 하이드레이션 과정을 통해 클라이언트의 React가 페이지를 인수하여 이벤트 리스너를 DOM 요소에 부착하고 상태를 관리할 수 있게 됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;하이드레이션 과정&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;서버 렌더링: Next.js가 HTML과 최소한의 JavaScript 번들을 생성하여 클라이언트에 전송합니다.&lt;/li&gt;
&lt;li&gt;클라이언트 렌더링: 브라우저는 HTML을 받아 화면에 보여줍니다.&lt;/li&gt;
&lt;li&gt;하이드레이션: JavaScript가 로드된 후, &lt;code&gt;ReactDOM.hydrate()&lt;/code&gt; 메서드 등을 통해 HTML 구조에 React 컴포넌트의 로직을 일치시키고 연결합니다.&lt;/li&gt;
&lt;li&gt;완료&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>JavaScript/React</category>
      <category>Next.js</category>
      <author>창욱씨</author>
      <guid isPermaLink="true">https://kchanguk.tistory.com/246</guid>
      <comments>https://kchanguk.tistory.com/entry/Nextjs-%ED%95%98%EC%9D%B4%EB%93%9C%EB%A0%88%EC%9D%B4%EC%85%98#entry246comment</comments>
      <pubDate>Sun, 26 Oct 2025 23:14:14 +0900</pubDate>
    </item>
  </channel>
</rss>