<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>샤쿠 블로그</title>
    <link>https://syaku.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 18:26:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>syaku</managingEditor>
    <image>
      <title>샤쿠 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/362069/attach/4a8d030ba0fd41b791b76a915ba04346</url>
      <link>https://syaku.tistory.com</link>
    </image>
    <item>
      <title>관측성(Observability) 모니터링 설치</title>
      <link>https://syaku.tistory.com/456</link>
      <description>&lt;h1&gt;&lt;b&gt;엔드포인트&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;서비스 용도 URL&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Prometheus&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;http://localhost:9090&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;타겟 수집 상태&lt;/td&gt;
&lt;td&gt;http://localhost:9090/targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;헬스체크&lt;/td&gt;
&lt;td&gt;http://localhost:9090/-/healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;메트릭&lt;/td&gt;
&lt;td&gt;http://localhost:9090/metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;설정 확인&lt;/td&gt;
&lt;td&gt;http://localhost:9090/config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;설정 reload (POST)&lt;/td&gt;
&lt;td&gt;http://localhost:9090/-/reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Loki&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;헬스체크&lt;/td&gt;
&lt;td&gt;http://localhost:3100/ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;메트릭&lt;/td&gt;
&lt;td&gt;http://localhost:3100/metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;로그 push (POST)&lt;/td&gt;
&lt;td&gt;http://localhost:3100/loki/api/v1/push&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;로그 쿼리&lt;/td&gt;
&lt;td&gt;http://localhost:3100/loki/api/v1/query_range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;레이블 목록&lt;/td&gt;
&lt;td&gt;http://localhost:3100/loki/api/v1/labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;설정 확인&lt;/td&gt;
&lt;td&gt;http://localhost:3100/config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;서비스 상태&lt;/td&gt;
&lt;td&gt;http://localhost:3100/services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Grafana&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;http://localhost:3000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;헬스체크&lt;/td&gt;
&lt;td&gt;http://localhost:3000/api/health&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;메트릭&lt;/td&gt;
&lt;td&gt;http://localhost:3000/metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Jaeger&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;http://localhost:16686&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;헬스체크&lt;/td&gt;
&lt;td&gt;http://localhost:14269&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;메트릭&lt;/td&gt;
&lt;td&gt;http://localhost:14269/metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;OTLP gRPC 수신&lt;/td&gt;
&lt;td&gt;http://localhost:4317&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;OTLP HTTP 수신&lt;/td&gt;
&lt;td&gt;http://localhost:4318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spring Boot&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;헬스체크&lt;/td&gt;
&lt;td&gt;http://localhost:{port}/actuator/health&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;메트릭&lt;/td&gt;
&lt;td&gt;http://localhost:{port}/actuator/prometheus&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Docker 기반 모니터링 설치 및 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 생성&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;docker network create monitoring
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;services:
  # &amp;lt;http://localhost:16686&amp;gt;
  jaeger:
    image: jaegertracing/jaeger:latest
    container_name: jaeger
    volumes:
      - ./jaeger/jaeger-config.yml:/etc/jaeger/config.yml:ro
      - badger-data:/badger
    command: [&quot;--config=/etc/jaeger/config.yml&quot;]
    ports:
      - &quot;16686:16686&quot;  # Jaeger UI
      - &quot;4317:4317&quot;    # OTLP gRPC
      - &quot;4318:4318&quot;    # OTLP HTTP
      - &quot;14269:14269&quot;  # metrics / healthcheck
    networks:
      - monitoring
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - &quot;9090:9090&quot;
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--web.enable-lifecycle'               # 설정 hot-reload 활성화 (선택)
    environment:
      - DOCKER_API_VERSION=1.44
    networks:
      - monitoring
  loki:
    image: grafana/loki:2.9.3
    container_name: loki
    volumes:
      - ./loki/loki-config.yml:/etc/loki/loki-config.yml
      - loki-data:/loki
    command: -config.file=/etc/loki/loki-config.yml
    # user: &quot;0&quot;                        # 권한 문제 방지용 (개발환경)
    ports:
      - &quot;3100:3100&quot;
    networks:
      - monitoring
  # &amp;lt;http://localhost:3000&amp;gt;
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - &quot;3000:3000&quot;
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_PATHS_PROVISIONING=/etc/grafana/provisioning   # provisioning 경로 명시
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning  # 데이터소스 + 대시보드 자동 설정
      - ./grafana/dashboards:/var/lib/grafana/dashboards  # 대시보드 JSON 파일
      - grafana-data:/var/lib/grafana
    networks:
      - monitoring
    depends_on:
      - prometheus
      - loki
      - jaeger
volumes:
  badger-data:
  prometheus-data:
  loki-data:
  grafana-data:
networks:
  monitoring:
    external: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Jaeger 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jaeger/jaeger-config.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;extensions:
  jaeger_storage:
    backends:
      badger_storage:
        badger: {}          # 기본값으로 /badger 경로 사용 &amp;mdash; 볼륨 마운트와 자동 매칭
        # badger:
        #   # ── 데이터 저장 경로 ──────────────────────
        #   directories:
        #     keys: /badger/keys       # 기본값: /badger/keys
        #     values: /badger/values   # 기본값: /badger/values

        #   # ── 데이터 보존 ───────────────────────────
        #   ttl: 72h                   # 스팬 보존 기간. 기본값: 72h

        #   # ── 유지보수 ──────────────────────────────
        #   maintenance_interval: 15m  # GC 실행 주기. 기본값: 15m
        #   metrics_update_interval: 10s  # Badger 내부 메트릭 갱신 주기. 기본값: 10s

  jaeger_query:
    storage:
      traces: badger_storage
    ui:
      log_access: true
    http:
      endpoint: 0.0.0.0:16686
    grpc:
      endpoint: 0.0.0.0:16685

  healthcheckv2:
    use_v2: true
    http:
      endpoint: 0.0.0.0:14269   # metrics / healthcheck 포트

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:

exporters:
  jaeger_storage_exporter:
    trace_storage: badger_storage

service:
  extensions: [jaeger_storage, jaeger_query, healthcheckv2]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger_storage_exporter]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Jaeger v2 Badger 설정 옵션&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;옵션 기본값 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;directories.keys&lt;/td&gt;
&lt;td&gt;/badger/keys&lt;/td&gt;
&lt;td&gt;스팬 키 저장 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;directories.values&lt;/td&gt;
&lt;td&gt;/badger/values&lt;/td&gt;
&lt;td&gt;스팬 값 저장 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ttl&lt;/td&gt;
&lt;td&gt;72h&lt;/td&gt;
&lt;td&gt;이 기간이 지난 스팬은 자동 삭제. 디스크 용량 관리의 핵심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maintenance_interval&lt;/td&gt;
&lt;td&gt;15m&lt;/td&gt;
&lt;td&gt;Badger GC(가비지 컬렉션) 실행 주기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metrics_update_interval&lt;/td&gt;
&lt;td&gt;10s&lt;/td&gt;
&lt;td&gt;Prometheus 메트릭 갱신 주기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Prometheus 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prometheus/prometheus.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;global:
  scrape_interval: 15s      # 수집 주기
  evaluation_interval: 15s  # 룰 평가 주기

scrape_configs:

  # Prometheus 자기 자신 수집
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  # Loki 메트릭 수집
  - job_name: 'loki'
    static_configs:
      - targets: ['loki:3100']

  # Grafana 메트릭 수집
  - job_name: 'grafana'
    static_configs:
      - targets: ['grafana:3000']

  # Jaeger 메트릭 수집
  - job_name: 'jaeger'
    static_configs:
      - targets: ['jaeger:14269']
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Loki 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loki/loki-config.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

compactor:
  working_directory: /loki/compactor
  shared_store: filesystem
  retention_enabled: true
  delete_request_store: filesystem

limits_config:
  retention_period: 72h
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Grafana 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;grafana/provisioning/datasources/datasources.yml&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: &amp;lt;http://prometheus:9090&amp;gt;
    isDefault: true
    editable: true
    jsonData:
      httpMethod: POST
      timeInterval: 15s   # prometheus scrape_interval 과 동일하게

  - name: Loki
    type: loki
    access: proxy
    url: &amp;lt;http://loki:3100&amp;gt;
    editable: true

  - name: Jaeger
    type: jaeger
    access: proxy
    url: &amp;lt;http://jaeger:16686&amp;gt;
    editable: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;grafana/provisioning/dashboards/dashboards.yml&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;apiVersion: 1

providers:
  - name: default
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30   # 파일 변경 시 자동 반영 주기
    options:
      path: /var/lib/grafana/dashboards
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  필수 항목 모니터링 수집 연동 (macOS, Docker 기반 테스트)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  필수 항목 작업 목록&lt;/h2&gt;
&lt;p&gt;작업 대상 방식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;로그 수집 에이전트 설치&lt;/td&gt;
&lt;td&gt;macOS (Spring, Nginx)&lt;/td&gt;
&lt;td&gt;Grafana Alloy (Homebrew)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템 메트릭 수집&lt;/td&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;Node Exporter (Homebrew)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB Exporter&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;mysqld_exporter 컨테이너 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Exporter&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;redis_exporter 컨테이너 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ActiveMQ Exporter&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;jmx_exporter sidecar 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring 분산 트레이싱&lt;/td&gt;
&lt;td&gt;Spring App&lt;/td&gt;
&lt;td&gt;OpenTelemetry Java Agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana Alert 설정&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Alert Rules + Slack 채널&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Grafana Alloy 설치 (로그 수집 &amp;mdash; macOS)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promtail EOL 대안으로 Alloy를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;brew install grafana/grafana/alloy
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Alloy 설정 파일 작성&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 파일 위치 변경: alloy run /Volumes/Develop/workspace/docker/monitoring/config.alloy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/opt/homebrew/etc/alloy/config.alloy 생성:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;// ── Loki 전송 대상 ──────────────────────────────────────────
loki.write &quot;default&quot; {
  endpoint {
    url = &quot;&amp;lt;http://localhost:3100/loki/api/v1/push&amp;gt;&quot;
  }
}

// ── Spring App 로그 수집 ────────────────────────────────────
local.file_match &quot;spring_logs&quot; {
  path_targets = [{
    __path__ = &quot;/var/log/app/*.log&quot;,
    job       = &quot;spring&quot;,
  }]
}

loki.source.file &quot;spring&quot; {
  targets    = local.file_match.spring_logs.targets
  forward_to = [loki.process.spring_parse.receiver]
}

loki.process &quot;spring_parse&quot; {
  stage.json {
    expressions = {
      level  = &quot;level&quot;,
      logger = &quot;logger&quot;,
      msg    = &quot;msg&quot;,
    }
  }
  stage.labels {
    values = {
      level  = &quot;&quot;,
      logger = &quot;&quot;,
    }
  }
  stage.output {
    source = &quot;msg&quot;
  }
  forward_to = [loki.write.default.receiver]
}

// ── Nginx 로그 수집 ─────────────────────────────────────────
local.file_match &quot;nginx_logs&quot; {
  path_targets = [{
    __path__ = &quot;/opt/homebrew/var/log/nginx/*.log&quot;,
    job       = &quot;nginx&quot;,
  }]
}

loki.source.file &quot;nginx&quot; {
  targets    = local.file_match.nginx_logs.targets
  forward_to = [loki.write.default.receiver]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Alloy 서비스 실행&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 서비스 등록 및 시작
brew services start alloy

# 상태 확인
brew services info alloy

# 로그 확인
tail -f /opt/homebrew/var/log/alloy/alloy.log
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node Exporter 설치 (시스템 메트릭 &amp;mdash; macOS)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 내부에서 실행하면 호스트 메트릭이 제대로 수집되지 않으므로, macOS에 직접 설치합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# 설치
brew install node_exporter

# 시작
brew services start node_exporter

혹은

node_exporter

# 확인 (기본 포트 9100)
curl &amp;lt;http://localhost:9100/metrics&amp;gt; | head -20
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus 설정에 Node Exporter 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 prometheus.yml에 아래 job 추가:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;scrape_configs:
  # ... 기존 spring-apps, spring-eureka job ...

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['host.docker.internal:9100']
        labels:
          instance: 'macos-host'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker 미들웨어 Exporter 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 docker-compose.yml에 아래 서비스들을 추가합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MariaDB Exporter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MariaDB에 exporter 전용 계정 생성:&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE USER 'exporter'@'%' IDENTIFIED BY 'exporter_password';

GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';

// MariaDB 10.5+ / MySQL 계열
GRANT SLAVE MONITOR ON *.* TO 'exporter'@'%';

or

GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'exporter'@'%';

FLUSH PRIVILEGES;

SHOW SLAVE STATUS;

SHOW REPLICA STATUS;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql_exporter.cnf 파일 생성:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;[client]
user=exporter
password=exporter_password
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml에 추가:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;  mysqld-exporter:
    image: prom/mysqld-exporter:latest
    command:
      - &quot;--config.my-cnf=/etc/mysql_exporter.cnf&quot;
      - &quot;--mysqld.address=mariadb:3306&quot;   # mariadb 서비스명으로 변경
    volumes:
      - ./mysql_exporter.cnf:/etc/mysql_exporter.cnf:ro
    ports:
      - &quot;9104:9104&quot;
    networks:
      - monitoring
    depends_on:
      - mariadb   # 실제 서비스명으로 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis Exporter&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;  redis-exporter:
    image: oliver006/redis_exporter:latest
    environment:
      REDIS_ADDR: &quot;redis:6379&quot;   # redis 서비스명으로 변경
      # REDIS_PASSWORD: &quot;your_password&quot;  # 비밀번호 있을 경우 활성화
    ports:
      - &quot;9121:9121&quot;
    networks:
      - monitoring
    depends_on:
      - redis   # 실제 서비스명으로 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ActiveMQ Exporter (JMX Exporter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ActiveMQ는 전용 exporter가 없으므로 jmx_exporter를 sidecar 방식으로 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;activemq_jmx_config.yaml 파일 생성:&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;hostPort: activemq:1099
lowercaseOutputName: true
whitelistObjectNames:
  - &quot;org.apache.activemq:destinationType=Queue,*&quot;
  - &quot;org.apache.activemq:destinationType=Topic,*&quot;
  - &quot;org.apache.activemq:type=Broker,brokerName=*&quot;
rules:
  - pattern: 'org.apache.activemq&amp;lt;type=Broker, brokerName=(\\\\S*), destinationType=Queue, destinationName=(\\\\S*)&amp;gt;&amp;lt;&amp;gt;(.*)'
    name: activemq_queue_$3
    attrNameSnakeCase: true
    labels:
      destination: $2
  - pattern: 'org.apache.activemq&amp;lt;type=Broker, brokerName=(\\\\S*)&amp;gt;&amp;lt;&amp;gt;CurrentConnectionsCount'
    name: activemq_connections
    type: GAUGE
  - pattern: 'org.apache.activemq&amp;lt;type=Broker, brokerName=(\\\\S*)&amp;gt;&amp;lt;&amp;gt;Total(.*)Count'
    name: activemq_$2_total
    type: COUNTER
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml에 추가:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;  activemq-exporter:
    image: bitnami/jmx-exporter:latest
    command:
      - &quot;9404&quot;
      - /etc/jmx/config.yaml
    volumes:
      - ./activemq_jmx_config.yaml:/etc/jmx/config.yaml:ro
    ports:
      - &quot;9404:9404&quot;
    networks:
      - monitoring
    depends_on:
      - activemq   # 실제 서비스명으로 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ActiveMQ 컨테이너에 JMX 포트가 열려있어야 합니다. 기존 activemq 서비스에 환경변수 추가:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;environment:
  ACTIVEMQ_JMX: &quot;1099&quot;
  ACTIVEMQ_OPTS: &quot;-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus에 미들웨어 Exporter job 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prometheus/prometheus.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;  - job_name: 'mysqld-exporter'
    static_configs:
      - targets: ['mysqld-exporter:9104']
        labels:
          service: 'mariadb'

  - job_name: 'redis-exporter'
    static_configs:
      - targets: ['redis-exporter:9121']
        labels:
          service: 'redis'

  - job_name: 'activemq-exporter'
    static_configs:
      - targets: ['activemq-exporter:9404']
        labels:
          service: 'activemq'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring 분산 트레이싱 (OTel Agent &amp;rarr; Jaeger)&lt;/h2&gt;
&lt;p&gt;프로토콜 기본 포트 엔드포인트 형식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;grpc&lt;/td&gt;
&lt;td&gt;4317&lt;/td&gt;
&lt;td&gt;http://host:4317&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;http/protobuf&lt;/td&gt;
&lt;td&gt;4318&lt;/td&gt;
&lt;td&gt;http://host:4318/v1/traces&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenTelemetry Java Agent 다운로드&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl -L &amp;lt;https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar&amp;gt; \\
  -o ~/agents/opentelemetry-javaagent.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring 앱 실행 시 Agent 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Spring 서비스 실행 옵션에 추가:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;java \\
  -javaagent:~/agents/opentelemetry-javaagent.jar \\
  -Dotel.service.name=my-service-name \\
  -Dotel.exporter.otlp.endpoint=http://localhost:4317 \\
  -Dotel.exporter.otlp.protocol=grpc \\
#  -Dotel.exporter.otlp.endpoint=http://localhost:4318 \\
#  -Dotel.exporter.otlp.protocol=http/protobuf \\
  -Dotel.logs.exporter=none \\
  -jar my-app.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Boot application.yml 설정&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;management:
  tracing:
    sampling:
      probability: 1.0   # 개발환경 100% 샘플링
  endpoints:
    web:
      exposure:
        include: prometheus, health, info
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jaeger가 OTLP 포트(4317)를 수신하는지 docker-compose 확인:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;  jaeger:
    image: jaegertracing/jaeger:latest
    ports:
      - &quot;16686:16686&quot;   # UI
      - &quot;4317:4317&quot;     # OTLP gRPC
      - &quot;4318:4318&quot;     # OTLP HTTP
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Grafana Alert 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Slack Notification Channel 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana UI &amp;rarr; &lt;b&gt;Alerting &amp;rarr; Contact points &amp;rarr; Add contact point&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Name: slack-alert
Type: Slack
Webhook URL: &amp;lt;https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필수 Alert Rule 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana UI &amp;rarr; &lt;b&gt;Alerting &amp;rarr; Alert rules &amp;rarr; New alert rule&lt;/b&gt;:&lt;/p&gt;
&lt;p&gt;Alert 명 PromQL 임계치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5xx 오류율&lt;/td&gt;
&lt;td&gt;`sum(rate(http_server_requests_seconds_count{status=~&quot;5..&quot;}[5m]))&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sum(rate(http_server_requests_seconds_count[5m]))&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100`&lt;/td&gt;
&lt;td&gt;&amp;gt; 1%&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU 사용률&lt;/td&gt;
&lt;td&gt;(1 - avg by(instance) (rate(node_cpu_seconds_total{mode=&quot;idle&quot;}[5m]))) * 100&lt;/td&gt;
&lt;td&gt;&amp;gt; 70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 사용률&lt;/td&gt;
&lt;td&gt;(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100&lt;/td&gt;
&lt;td&gt;&amp;gt; 80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ActiveMQ DLQ&lt;/td&gt;
&lt;td&gt;activemq_queue_queue_size{destination=~&quot;.*DLQ.*&quot;}&lt;/td&gt;
&lt;td&gt;&amp;gt; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HikariCP pending&lt;/td&gt;
&lt;td&gt;hikaricp_connections_pending&lt;/td&gt;
&lt;td&gt;&amp;gt; 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✅&amp;nbsp;필수 항목&lt;/b&gt; 최종 동작 확인 체크리스트&lt;/h2&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 1. Node Exporter 메트릭 확인
curl &amp;lt;http://localhost:9100/metrics&amp;gt; | grep node_cpu

# 2. Spring Actuator Prometheus 확인
curl &amp;lt;http://localhost&amp;gt;:{서비스포트}/actuator/prometheus | grep hikaricp

# 3. MariaDB Exporter 확인
curl &amp;lt;http://localhost:9104/metrics&amp;gt; | grep mysql_up

# 4. Redis Exporter 확인
curl &amp;lt;http://localhost:9121/metrics&amp;gt; | grep redis_up

# 5. ActiveMQ Exporter 확인
curl &amp;lt;http://localhost:9404/metrics&amp;gt; | grep activemq

# 6. Prometheus Targets 확인 (브라우저)
# &amp;lt;http://localhost:9090/targets&amp;gt; &amp;rarr; 모든 job이 UP 상태인지 확인

# 7. Loki 로그 수집 확인 (Grafana &amp;rarr; Explore &amp;rarr; Loki)
# {job=&quot;spring&quot;} | json 쿼리로 로그 유입 확인
&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;b&gt;Grafana 기본 대시보드 추천&lt;/b&gt;: Prometheus node exporter ID 1860, Spring Boot JVM ID 4701, Redis ID 763, MySQL Overview ID 7362를 Grafana &amp;rarr; Dashboards &amp;rarr; Import에서 바로 불러올 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  권장 항목 모니터링 수집 연동 (macOS, Docker 기반 테스트)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  권장 항목 작업 목록&lt;/h2&gt;
&lt;p&gt;작업 대상 방식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spring 인증/인가 실패 로그 필터링&lt;/td&gt;
&lt;td&gt;Alloy config 수정&lt;/td&gt;
&lt;td&gt;stage.match 파이프라인 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL 쿼리 로깅 (P6Spy)&lt;/td&gt;
&lt;td&gt;Spring App&lt;/td&gt;
&lt;td&gt;의존성 추가 + 설정 파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk I/O Alert 추가&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;PromQL Alert Rule 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Endpoint별 RPS 패널&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;PromQL Panel 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow Query 임계치 Alert&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Alert Rule 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스레드 상태 Alert&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Alert Rule 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB Slow Query 로그 활성화&lt;/td&gt;
&lt;td&gt;Docker mariadb 서비스&lt;/td&gt;
&lt;td&gt;command 옵션 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB InnoDB 메트릭 활성화&lt;/td&gt;
&lt;td&gt;mysqld_exporter&lt;/td&gt;
&lt;td&gt;--collect.info_schema.innodb_metrics 옵션 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx stub_status 활성화&lt;/td&gt;
&lt;td&gt;macOS Nginx&lt;/td&gt;
&lt;td&gt;nginx.conf 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nginx-prometheus-exporter 설치&lt;/td&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;Homebrew 설치 및 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ActiveMQ Consumer Lag 패널&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;PromQL Panel 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Eviction Alert 추가&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Alert Rule 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포 Annotation 스크립트 작성&lt;/td&gt;
&lt;td&gt;배포 스크립트&lt;/td&gt;
&lt;td&gt;Grafana API 호출 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  로그 수집 &amp;mdash; 권장 항목&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인증/인가 실패 로그 (401/403)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도 파일 수집보다 &lt;b&gt;기존 Spring 로그에서 필터링&lt;/b&gt;하는 방식이 효율적입니다. Alloy 설정에 파이프라인 stage만 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config.alloy의 loki.process &quot;spring_parse&quot; 블록에 추가:&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;  stage.match {
    selector = &quot;{job=\\\\&quot;spring\\\\&quot;}&quot;
    pipeline_name = &quot;auth_failure&quot;
    stage.regex {
      expression = &quot;(?P&amp;lt;status&amp;gt;401|403)&quot;
    }
    stage.labels {
      values = {
        auth_failure = &quot;true&quot;,
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana에서 {job=&quot;spring&quot;, auth_failure=&quot;true&quot;} 쿼리로 401/403 급증 감지 Alert 추가.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SQL 쿼리 로깅 (P6Spy / Hibernate)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring build.gradle에 P6Spy 의존성 추가:&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'p6spy:p6spy:3.9.1'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/main/resources/spy.properties 생성:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;appender=com.p6spy.engine.spy.appender.FileLogger
logfile=/var/log/app/sql.log
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime)|%(executionTime)ms|%(category)|%(sqlSingleLine)
filter=true
execution=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.yml datasource url 변경:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  datasource:
    # 기존: jdbc:mariadb://localhost:3306/dbname
    url: jdbc:p6spy:mariadb://localhost:3306/dbname
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alloy에서 /var/log/app/sql.log 추가 수집:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;local.file_match &quot;sql_logs&quot; {
  path_targets = [{
    __path__ = &quot;/var/log/app/sql.log&quot;,
    job       = &quot;sql&quot;,
  }]
}

loki.source.file &quot;sql&quot; {
  targets    = local.file_match.sql_logs.targets
  forward_to = [loki.write.default.receiver]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 하드웨어 &amp;mdash; Disk I/O 메트릭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter 설치 시 기본으로 수집됩니다. Prometheus에 별도 설정 없이 아래 Alert만 추가하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Alert Rules에 추가:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# Disk I/O 읽기 속도 (MB/s)
rate(node_disk_read_bytes_total[5m]) / 1024 / 1024

# Disk I/O 쓰기 속도 (MB/s)
rate(node_disk_written_bytes_total[5m]) / 1024 / 1024
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alert 명 PromQL 임계치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Disk Read 과부하&lt;/td&gt;
&lt;td&gt;rate(node_disk_read_bytes_total[5m]) / 1024 / 1024&lt;/td&gt;
&lt;td&gt;&amp;gt; 100 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk Write 과부하&lt;/td&gt;
&lt;td&gt;rate(node_disk_written_bytes_total[5m]) / 1024 / 1024&lt;/td&gt;
&lt;td&gt;&amp;gt; 100 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;☕ JVM/Spring &amp;mdash; 권장 항목&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP Endpoint별 RPS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot Actuator가 http_server_requests_seconds_count 메트릭을 기본 제공하므로 별도 설정 없이 PromQL만 작성하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Panel PromQL:&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;# 엔드포인트별 RPS
sum by (uri, method) (
  rate(http_server_requests_seconds_count[1m])
)

# 엔드포인트별 에러율
sum by (uri) (
  rate(http_server_requests_seconds_count{status=~&quot;5..&quot;}[5m])
)
/ sum by (uri) (
  rate(http_server_requests_seconds_count[5m])
) * 100
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Slow Query 임계치 Alert&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Actuator의 Timer 메트릭을 활용합니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# P95 응답시간이 1초 초과인 엔드포인트 감지
histogram_quantile(0.95,
  sum by (uri, le) (
    rate(http_server_requests_seconds_bucket[5m])
  )
) &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Alert Rule:&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Alert: SlowEndpointDetected
Condition: histogram_quantile(0.95, ...) &amp;gt; 1
For: 5m
Message: &quot;{{ $labels.uri }} P95 응답시간 초과&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;스레드 수 (BLOCKED/WAITING)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actuator 기본 제공 메트릭 jvm_threads_states_threads 활용:&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;# BLOCKED 스레드 수
jvm_threads_states_threads{state=&quot;blocked&quot;}

# WAITING 스레드 수
jvm_threads_states_threads{state=&quot;waiting&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Alert:&lt;/p&gt;
&lt;p&gt;Alert 명 PromQL 임계치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BLOCKED 스레드 급증&lt;/td&gt;
&lt;td&gt;jvm_threads_states_threads{state=&quot;blocked&quot;}&lt;/td&gt;
&lt;td&gt;&amp;gt; 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전체 스레드 포화&lt;/td&gt;
&lt;td&gt;jvm_threads_live_threads&lt;/td&gt;
&lt;td&gt;&amp;gt; 200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 미들웨어 &amp;mdash; 권장 항목&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MariaDB Slow Query 수집&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MariaDB 컨테이너에 Slow Query 로그 활성화. 기존 docker-compose.yml의 mariadb 서비스에 추가:&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;  mariadb:
    # 기존 설정 유지...
    command:
      - --slow-query-log=1
      - --slow-query-log-file=/var/log/mysql/slow.log
      - --long-query-time=1
    volumes:
      - ./mysql-logs:/var/log/mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysqld_exporter에 slow query 수집 옵션 추가:&lt;/p&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;  mysqld-exporter:
    command:
      - &quot;--config.my-cnf=/etc/mysql_exporter.cnf&quot;
      - &quot;--mysqld.address=mariadb:3306&quot;
      - &quot;--collect.global_status&quot;
      - &quot;--collect.info_schema.innodb_metrics&quot;   # InnoDB 버퍼풀 포함
      - &quot;--collect.perf_schema.eventsstatements&quot; # 슬로우 쿼리 통계
      - &quot;--exporter.log_slow_filter&quot;             # exporter 자체 쿼리는 slow log 제외
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slow query Alloy 수집 (config.alloy에 추가):&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;local.file_match &quot;mysql_slow&quot; {
  path_targets = [{
    __path__ = &quot;/absolute/path/to/mysql-logs/slow.log&quot;,
    job       = &quot;mysql-slow&quot;,
  }]
}

loki.source.file &quot;mysql_slow_log&quot; {
  targets    = local.file_match.mysql_slow.targets
  forward_to = [loki.write.default.receiver]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MariaDB InnoDB Buffer Pool Hit Rate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysqld_exporter에서 --collect.info_schema.innodb_metrics 활성화 시 자동 수집됩니다. Grafana Panel PromQL:&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;# Buffer Pool Hit Rate (95% 이상 유지 권장)
(
  1 - (
    rate(mysql_global_status_innodb_buffer_pool_reads_total[5m]) /
    rate(mysql_global_status_innodb_buffer_pool_read_requests_total[5m])
  )
) * 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alert: 히트율 &amp;lt; 95% 지속 시 알림.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nginx Exporter 설치 (macOS)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx stub_status 모듈 활성화 &amp;rarr; nginx_exporter 설치 순서로 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Nginx stub_status 설정&lt;/b&gt; (/opt/homebrew/etc/nginx/nginx.conf에 추가):&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
    listen 8080;
    server_name localhost;

    location /stub_status {
        stub_status on;
        allow 127.0.0.1;
        deny all;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;brew services restart nginx
curl &amp;lt;http://localhost:8080/stub_status&amp;gt;  # 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) nginx-prometheus-exporter 설치&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;brew tap nginx/tap
brew install nginx-prometheus-exporter

# 실행 (stub_status 주소 지정)
nginx-prometheus-exporter -nginx.scrape-uri=http://localhost:8080/stub_status &amp;amp;

# 백그라운드 서비스로 등록하려면 LaunchAgent 생성
# 포트 기본값: 9113
curl &amp;lt;http://localhost:9113/metrics&amp;gt; | grep nginx_connections
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Prometheus job 추가&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;  - job_name: 'nginx-exporter'
    static_configs:
      - targets: ['host.docker.internal:9113']
        labels:
          service: 'nginx'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ActiveMQ 메시지 처리 지연 / Consumer Lag&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jmx_exporter를 이미 설정했다면 아래 PromQL로 Grafana Panel 구성합니다:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;# 큐에 대기 중인 메시지 수 (consumer lag)
activemq_queue_queue_size

# Enqueue vs Dequeue 차이 (처리 속도 차이)
rate(activemq_queue_enqueue_count[5m]) - rate(activemq_queue_dequeue_count[5m])
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alert: activemq_queue_queue_size &amp;gt; 100 조건으로 Consumer Lag 급증 탐지.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis Evicted Keys&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis_exporter 설치 시 기본 수집됩니다. Grafana Alert:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# Evicted Keys 증가율
rate(redis_evicted_keys_total[5m]) &amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Grafana &amp;mdash; 배포 시점 Annotation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Annotations API를 활용해 배포 시 수직선을 자동으로 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Grafana API Key 발급&lt;/b&gt;: Grafana UI &amp;rarr; &lt;b&gt;Administration &amp;rarr; Service accounts &amp;rarr; Add service account token&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 배포 스크립트에 Annotation 호출 추가&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash
GRAFANA_URL=&quot;&amp;lt;http://localhost:3000&amp;gt;&quot;
GRAFANA_TOKEN=&quot;your_service_account_token&quot;
SERVICE_NAME=&quot;my-service&quot;
VERSION=$(git describe --tags --always)

curl -s -X POST &quot;${GRAFANA_URL}/api/annotations&quot; \\\\
  -H &quot;Authorization: Bearer ${GRAFANA_TOKEN}&quot; \\\\
  -H &quot;Content-Type: application/json&quot; \\\\
  -d &quot;{
    \\\\&quot;text\\\\&quot;: \\\\&quot;Deploy: ${SERVICE_NAME} ${VERSION}\\\\&quot;,
    \\\\&quot;tags\\\\&quot;: [\\\\&quot;deployment\\\\&quot;, \\\\&quot;${SERVICE_NAME}\\\\&quot;],
    \\\\&quot;time\\\\&quot;: $(date +%s)000
  }&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Grafana Dashboard Annotation 소스 등록&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dashboard Settings &amp;rarr; &lt;b&gt;Annotations &amp;rarr; Add annotation query&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Data source: -- Grafana --
Filter by tags: deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  권장 항목 추가 후 Alert 전체 목록&lt;/h2&gt;
&lt;p&gt;Alert 명 PromQL 요약 임계치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Slow Endpoint P95&lt;/td&gt;
&lt;td&gt;histogram_quantile(0.95, ...)&lt;/td&gt;
&lt;td&gt;&amp;gt; 1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOCKED 스레드&lt;/td&gt;
&lt;td&gt;jvm_threads_states_threads{state=&quot;blocked&quot;}&lt;/td&gt;
&lt;td&gt;&amp;gt; 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk 쓰기 I/O&lt;/td&gt;
&lt;td&gt;rate(node_disk_written_bytes_total[5m])&lt;/td&gt;
&lt;td&gt;&amp;gt; 100 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InnoDB Hit Rate&lt;/td&gt;
&lt;td&gt;Buffer pool 히트율&lt;/td&gt;
&lt;td&gt;&amp;lt; 95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumer Lag&lt;/td&gt;
&lt;td&gt;activemq_queue_queue_size&lt;/td&gt;
&lt;td&gt;&amp;gt; 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Eviction&lt;/td&gt;
&lt;td&gt;rate(redis_evicted_keys_total[5m])&lt;/td&gt;
&lt;td&gt;&amp;gt; 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth Failure 급증&lt;/td&gt;
&lt;td&gt;{job=&quot;spring&quot;, auth_failure=&quot;true&quot;} rate&lt;/td&gt;
&lt;td&gt;&amp;gt; 10/min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Grafana 권장 대시보드 Import ID&lt;/b&gt;: Nginx Exporter 12708, ActiveMQ JMX 8050, MySQL InnoDB 7371 을 Dashboards &amp;rarr; Import에서 바로 사용할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 권장 항목 최종 동작 확인 체크리스트&lt;/h2&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# ── 로그 수집 ───────────────────────────────────────────────

# 1. Auth Failure 라벨 확인 (Grafana &amp;rarr; Explore &amp;rarr; Loki)
# 쿼리: {job=&quot;spring&quot;, auth_failure=&quot;true&quot;}
# 401/403 응답 로그가 유입되는지 확인

# 2. SQL 로그 파일 생성 확인
tail -f /var/log/app/sql.log
# P6Spy 포맷: timestamp|executionTime|category|sql 형태 출력 확인

# 3. Alloy SQL 로그 수집 확인 (Grafana &amp;rarr; Explore &amp;rarr; Loki)
# 쿼리: {job=&quot;sql&quot;}

# ── 시스템 메트릭 ────────────────────────────────────────────

# 4. Disk I/O 메트릭 확인 (Node Exporter 기본 제공)
curl &amp;lt;http://localhost:9100/metrics&amp;gt; | grep node_disk_read_bytes_total

# ── Spring JVM 메트릭 ────────────────────────────────────────

# 5. HTTP RPS 메트릭 확인
curl &amp;lt;http://localhost&amp;gt;:{서비스포트}/actuator/prometheus | grep http_server_requests_seconds_count

# 6. 스레드 상태 메트릭 확인
curl &amp;lt;http://localhost&amp;gt;:{서비스포트}/actuator/prometheus | grep jvm_threads_states_threads

# ── 미들웨어 ─────────────────────────────────────────────────

# 7. MariaDB Slow Query 로그 파일 생성 확인
tail -f ./mysql-logs/slow.log

# 8. InnoDB 메트릭 수집 확인
curl &amp;lt;http://localhost:9104/metrics&amp;gt; | grep innodb_buffer_pool

# 9. Nginx stub_status 응답 확인
curl &amp;lt;http://localhost:8080/stub_status&amp;gt;
# 출력 예시:
# Active connections: 1
# server accepts handled requests
#  3 3 3

# 10. nginx-prometheus-exporter 메트릭 확인
curl &amp;lt;http://localhost:9113/metrics&amp;gt; | grep nginx_connections_active

# 11. ActiveMQ Consumer Lag 메트릭 확인
curl &amp;lt;http://localhost:9404/metrics&amp;gt; | grep activemq_queue_queue_size

# 12. Redis Evicted Keys 메트릭 확인
curl &amp;lt;http://localhost:9121/metrics&amp;gt; | grep redis_evicted_keys_total

# ── Grafana ──────────────────────────────────────────────────

# 13. Prometheus Targets 전체 UP 확인 (브라우저)
# &amp;lt;http://localhost:9090/targets&amp;gt;
# nginx-exporter job 포함 모든 job이 State: UP인지 확인

# 14. 배포 Annotation 등록 테스트
curl -X POST &quot;&amp;lt;http://localhost:3000/api/annotations&amp;gt;&quot; \\\\
  -H &quot;Authorization: Bearer {your_token}&quot; \\\\
  -H &quot;Content-Type: application/json&quot; \\\\
  -d '{&quot;text&quot;:&quot;Deploy test annotation&quot;,&quot;tags&quot;:[&quot;deployment&quot;],&quot;time&quot;:'$(date +%s)'000}'
# 응답: {&quot;id&quot;: 1, &quot;message&quot;: &quot;Annotation added&quot;} 확인

# 15. Grafana Dashboard Import 확인 (브라우저)
# Nginx Exporter ID: 12708
# MySQL InnoDB ID:   7371
# ActiveMQ JMX ID:   8050
# &amp;rarr; Dashboards &amp;rarr; Import에서 패널 정상 렌더링 확인
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/456</guid>
      <comments>https://syaku.tistory.com/456#entry456comment</comments>
      <pubDate>Sat, 25 Apr 2026 20:09:13 +0900</pubDate>
    </item>
    <item>
      <title>관측성 (Observability) 모니터링 설계</title>
      <link>https://syaku.tistory.com/455</link>
      <description>&lt;h1&gt;모니터링 기술 스택&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 역할 설치 위치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus&lt;/td&gt;
&lt;td&gt;메트릭 수집 및 저장&lt;/td&gt;
&lt;td&gt;모니터링 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loki&lt;/td&gt;
&lt;td&gt;로그 수집 및 저장&lt;/td&gt;
&lt;td&gt;모니터링 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;시각화 및 알림&lt;/td&gt;
&lt;td&gt;모니터링 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenTelemetry Collector&lt;/td&gt;
&lt;td&gt;메트릭/트레이스/로그 수집 파이프라인&lt;/td&gt;
&lt;td&gt;모니터링 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jaeger + Badger&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;b&gt;Node Exporter&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;OS/하드웨어 메트릭 수집 &amp;rarr; Prometheus에 노출&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;앱 서버&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Grafana Alloy (Promtail 대체)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;로그 파일 수집 &amp;rarr; Loki로 push&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;앱 서버&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spring Boot Actuator&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;JVM/앱 메트릭 &amp;rarr; Prometheus에 노출&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;앱 서버&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚠️&amp;nbsp;중요: Promtail은 2026년 3월 2일부로 공식 EOL이 선언되었습니다. 신규 구성이므로&amp;nbsp;Grafana Alloy로 대체하는 것을 권장하며, 아래 가이드는 기존 설정 구조를 유지하면서 Alloy 기준으로 안내합니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;모니터링 서버 사양&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트별 메모리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 예상 메모리 근거&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus&lt;/td&gt;
&lt;td&gt;~400~600MB&lt;/td&gt;
&lt;td&gt;40대 &amp;times; 800 series = 32,000 series, 3KB/series 기준&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3KB/series &amp;mdash; WAL + 인덱스 + 쿼리 버퍼 포함 보수적 상한값 기준 | | Loki | ~500MB~1GB | 로그 볼륨에 따라 변동 | | Grafana | ~200~300MB | 대시보드 수, 동시 접속자 수에 따라 변동 | | OTel Collector | ~200~400MB | memory_limiter 미설정 시 초과 가능 | | Jaeger + Badger | ~300MB + 디스크 | in-memory ❌ &amp;mdash; 재시작 시 트레이스 전체 유실 | | OS + 여유 | ~1GB | | | &lt;b&gt;합계&lt;/b&gt; | &lt;b&gt;~2.6~3.7GB&lt;/b&gt; | |&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트별 디스크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크는 메모리보다 더 중요하게 설계해야 합니다. retention 기간과 로그 볼륨에 따라 용량이 크게 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 계산식 15일 보존 기준 예상&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus&lt;/td&gt;
&lt;td&gt;series 수 &amp;times; scrape_interval &amp;times; bytes/sample &amp;times; retention&lt;/td&gt;
&lt;td&gt;~5~15GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loki&lt;/td&gt;
&lt;td&gt;일일 로그량(GB) &amp;times; retention일 &amp;times; (1 - 압축률 약 90%)&lt;/td&gt;
&lt;td&gt;로그 1GB/day 기준 ~1.5GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debug 레벨 로그는 retention 1일, info 이상만 15일 보존하는 스트림별 차등 retention 설정으로 디스크 절감 가능 | | Jaeger Badger | 트레이스 수 &amp;times; 평균 트레이스 크기 | ~5~10GB | | &lt;b&gt;권장 디스크&lt;/b&gt; | | &lt;b&gt;최소 50GB, 여유 있게 100GB EBS&lt;/b&gt; |&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ Prometheus 권장 설정: --storage.tsdb.retention.time=15d + --storage.tsdb.retention.size=10GB (크기 기반 상한선 병행 설정)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추천 인스턴스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 인스턴스 스펙 이유&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;모니터링 전용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;t3.large&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;vCPU 2, 8GB RAM&lt;/td&gt;
&lt;td&gt;최대 3.7GB 사용 기준, 2배 이상 여유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확장 여지&lt;/td&gt;
&lt;td&gt;t3.xlarge&lt;/td&gt;
&lt;td&gt;vCPU 4, 16GB RAM&lt;/td&gt;
&lt;td&gt;서버 수 2배 이상 증가 또는 Elasticsearch 추가 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 설정 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Prometheus&lt;/b&gt; &amp;mdash; retention.time과 retention.size 두 가지를 &lt;b&gt;함께 설정&lt;/b&gt;, size는 디스크의 80~85% 이하로 설정 &lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/storage/&quot;&gt;prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OTel Collector&lt;/b&gt; &amp;mdash; memory_limiter processor 필수 설정, 미설정 시 메모리 무제한 증가 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jaeger v2 &amp;mdash;&lt;/b&gt; Docker 볼륨(badger-data:/badger) 마운트로 영속성 확보. v1과 달리 ephemeral 옵션이 별도로 없으며, 볼륨 마운트가 곧 persistent 설정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Loki&lt;/b&gt; &amp;mdash; debug 레벨 로그는 retention 1일, info 이상만 15일 보존하는 &lt;b&gt;스트림별 차등 retention&lt;/b&gt; 설정으로 디스크 절감 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 서버(t3.small) 에이전트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 앱 서버에는 경량 에이전트만 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트 메모리 비고&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node Exporter&lt;/td&gt;
&lt;td&gt;~15~30MB&lt;/td&gt;
&lt;td&gt;OS/하드웨어 메트릭 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana Alloy&lt;/td&gt;
&lt;td&gt;~50~80MB&lt;/td&gt;
&lt;td&gt;로그 파일 &amp;rarr; Loki push&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Actuator&lt;/td&gt;
&lt;td&gt;추가 설치 없음&lt;/td&gt;
&lt;td&gt;/actuator/prometheus 엔드포인트 노출&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;b&gt;~65~110MB&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ t3.small 2GB 중 Spring Boot JVM이 700MB~1.2GB 점유. -Xmx768m 등 JVM 상한선 명시 설정 필수&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;모니터링 수집 항목&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  로그 수집&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 중요도 컴포넌트 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spring App 로그&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;애플리케이션 런타임 로그 (INFO/WARN/ERROR 레벨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx 로그&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;access.log, error.log &amp;mdash; 요청 유입 및 오류 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예외(Exception) 감지&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;Uncaught Exception, Stack Trace 감지 및 집계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correlation ID (MDC traceId)&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki / OTel Collector &amp;rarr; Jaeger&lt;/td&gt;
&lt;td&gt;모든 로그 라인에 traceId 포함 &amp;mdash; 분산 추적과 로그 연계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증/인가 실패 로그&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;401/403 응답 급증 시 비정상 접근 시도 조기 탐지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeDeploy Agent 로그&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;배포 실행 로그, 스크립트 실행 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeDeploy 배포 이력&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki / Grafana Annotation&lt;/td&gt;
&lt;td&gt;배포 성공/실패 시점 기록 &amp;mdash; 장애 상관관계 분석에 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL 쿼리 로깅&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;실행된 SQL 및 파라미터 로깅 (P6Spy, Hibernate 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graceful Shutdown / 생명주기 이벤트&lt;/td&gt;
&lt;td&gt;  선택&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;앱 재시작&amp;middot;종료 시점을 명시적으로 로그 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  분산 트래킹&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 중요도 컴포넌트 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;분산 트래킹 (Trace/Span)&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator (OTel Agent) &amp;rarr; OTel Collector &amp;rarr; Jaeger + Badger &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;OTel + Jaeger로 서비스 간 요청 흐름 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MDC traceId 전 로그 포함 확인&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana Alloy &amp;rarr; Loki / OTel Collector &amp;rarr; Jaeger&lt;/td&gt;
&lt;td&gt;Trace가 있어도 로그에 traceId 없으면 로그-트레이스 연계 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;☕ JVM / Spring App 메트릭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 중요도 컴포넌트 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HikariCP 커넥션 풀 상태&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;active/idle/pending 수 &amp;mdash; pending 증가 시 DB 요청 대기로 장애 직결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4xx / 5xx 에러율&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;클라이언트/서버 오류 비율 &amp;mdash; SLO 위반 감지 기준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC 빈도 / STW 시간 / pause duration&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;STW가 길면 응답 지연 직접 원인 &amp;mdash; JVM 튜닝 판단 근거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JVM 힙/비힙 메모리 사용량&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;Eden, Survivor, Old Gen 등 메모리 영역별 사용량&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP P99 latency&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;상위 1% 느린 요청 응답시간 &amp;mdash; 사용자 체감 성능 지표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP endpoint별 RPS&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;초당 요청 수 &amp;mdash; 트래픽 급증 및 이상 패턴 탐지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow Query 임계치 알림&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana (Alert)&lt;/td&gt;
&lt;td&gt;특정 ms 초과 쿼리 발생 시 즉시 알림 &amp;mdash; 로깅만으로는 탐지 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스레드 수 (BLOCKED/WAITING/데드락)&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;스레드 고갈 및 데드락은 앱 무응답의 주요 원인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비즈니스 커스텀 메트릭 (Micrometer)&lt;/td&gt;
&lt;td&gt;  선택&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator (Micrometer) &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;주문 처리 수, 결제 성공률 등 도메인 특화 지표를 Counter/Timer로 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 하드웨어 / OS 메트릭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 중요도 컴포넌트 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU 사용률&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Node Exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;전체 및 코어별 CPU 점유율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 사용률&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Node Exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;물리 메모리 사용량, swap 사용 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk 사용률&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Node Exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;파티션별 디스크 사용 비율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk I/O (읽기/쓰기 속도)&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;Node Exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;로그 집중 기록 시 I/O 병목 여부 확인 &amp;mdash; 사용률만으로는 부족&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 TCP 연결 수 / 패킷 드롭&lt;/td&gt;
&lt;td&gt;  선택&lt;/td&gt;
&lt;td&gt;Node Exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;서비스 간 통신 이상, 연결 고갈, 패킷 손실 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 미들웨어 상태 / 메트릭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미들웨어 메트릭은 각 서비스 전용 Exporter를 통해 Prometheus로 수집하거나, 일부는 로그 기반으로 Loki에서 집계합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 중요도 컴포넌트 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB 상태 / 연결 수&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;mysqld_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;DB 프로세스 생존 여부, 연결 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ActiveMQ DLQ 누적 수&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;activemq_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;처리 실패 메시지 누적 수 &amp;mdash; 증가 시 비즈니스 로직 장애 신호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis hit/miss ratio&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;redis_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;캐시 효율 지표 &amp;mdash; miss율 높으면 DB 부하 증가로 직결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB 슬로우 쿼리 수&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;mysqld_exporter &amp;rarr; Prometheus &amp;rarr; Grafana / Grafana Alloy &amp;rarr; Loki&lt;/td&gt;
&lt;td&gt;slow_query_log 기반 임계치 초과 쿼리 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB InnoDB buffer pool hit rate&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;mysqld_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;히트율 낮으면 디스크 I/O 과부하 &amp;mdash; 95% 이상 유지 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ActiveMQ 메시지 처리 지연 / consumer lag&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;activemq_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;큐에 쌓이는 메시지 수와 소비 속도 차이 &amp;mdash; 처리 병목 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx 상태 / 메트릭&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;nginx_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;active connections, 요청 처리 수, 응답 코드 분포&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis evicted keys 수&lt;/td&gt;
&lt;td&gt;  권장&lt;/td&gt;
&lt;td&gt;redis_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;메모리 부족으로 강제 삭제된 키 수 &amp;mdash; 캐시 설계 문제 신호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 메모리 단편화율&lt;/td&gt;
&lt;td&gt;  선택&lt;/td&gt;
&lt;td&gt;redis_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;할당 메모리 대비 실제 사용 비율 &amp;mdash; 단편화 심하면 성능 저하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MariaDB replication lag&lt;/td&gt;
&lt;td&gt;  선택&lt;/td&gt;
&lt;td&gt;mysqld_exporter &amp;rarr; Prometheus &amp;rarr; Grafana&lt;/td&gt;
&lt;td&gt;레플리카 사용 시 마스터와의 동기화 지연 &amp;mdash; 데이터 정합성 위험 지표&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;미들웨어 Exporter 추가 참고&lt;/b&gt;: mysqld_exporter, redis_exporter, nginx_exporter, activemq_exporter는 기술 스택 표에 명시된 컴포넌트 외 추가 설치가 필요하며, 앱 서버 또는 별도 사이드카로 배포합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Grafana 대시보드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 중요도 컴포넌트 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alert 규칙 설정&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana (Alert Rules) + Prometheus&lt;/td&gt;
&lt;td&gt;5xx &amp;gt;1%, CPU &amp;gt;70%, 메모리 &amp;gt;80%, DLQ 증가 등 임계치 기반 자동 알림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack / 이메일 알림 채널 연동&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana (Notification Channel)&lt;/td&gt;
&lt;td&gt;Alert 발생 시 실시간 통보 &amp;mdash; 알림 없는 모니터링은 의미 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 대시보드 구성&lt;/td&gt;
&lt;td&gt;  필수&lt;/td&gt;
&lt;td&gt;Grafana (Dashboard / Panel)&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;Grafana (Annotations) + Loki / Prometheus&lt;/td&gt;
&lt;td&gt;배포 시각을 그래프 수직선으로 표시 &amp;mdash; 배포 후 지표 변화 상관 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLO/SLA 패널&lt;/td&gt;
&lt;td&gt;  선택&lt;/td&gt;
&lt;td&gt;Grafana (Dashboard) + Prometheus&lt;/td&gt;
&lt;td&gt;Uptime, P99 latency 목표 달성율 수치화 &amp;mdash; 서비스 품질 기준 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;범례&lt;/b&gt;:   필수 &amp;mdash; 없으면 장애 탐지 불가 /   권장 &amp;mdash; 운영 안정성에 직접 기여 /   선택 &amp;mdash; 성숙도 높아질 때 추가&lt;/p&gt;</description>
      <category>개발</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/455</guid>
      <comments>https://syaku.tistory.com/455#entry455comment</comments>
      <pubDate>Sat, 25 Apr 2026 20:06:43 +0900</pubDate>
    </item>
    <item>
      <title>내가 이해한 MCP의 필요성 정리</title>
      <link>https://syaku.tistory.com/454</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;랭체인으로 애플리케이션을 개발하면서 구지 MCP로 전향해야 하는 지 느끼지 못했다. 깊이 있게 알아보지 않은 것이 이유였겠지?&lt;br /&gt;그래서 이번에 이것저것 찾아보면서 이해한 것들을 내 생각으로 정리?해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 개발에서는 반복적으로 사용하는 기능을 모듈화하여 구현하고,&lt;br /&gt;비즈니스 로직에서는 이러한 모듈을 필요에 따라 호출해 조합하는 방식이 일반적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM(대형 언어 모델) 기반 애플리케이션을 개발할 때도&lt;br /&gt;텍스트 생성, 웹 크롤링, 데이터베이스 조회, 외부 API 호출 등&lt;br /&gt;여러 작업(도구)이 결합되어 최종 결과를 만들어내기 때문에&lt;br /&gt;각 도구를 모듈화하여 사용하는 것이 필수적입니다.&lt;br /&gt;예를 들어, LangChain과 같은 프레임워크는&lt;br /&gt;이런 도구들을 파이썬/자바스크립트 코드 단위의 모듈로 묶어 활용할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방식은 &lt;b&gt;특정 프레임워크나 애플리케이션에 종속&lt;/b&gt;되기 쉽고,&lt;br /&gt;각 도구의 재사용성&amp;middot;호환성에 한계가 있습니다.&lt;br /&gt;즉, 한 번 만든 도구를 다른 LLM 시스템이나 서비스에서 쉽게 재활용하기 어렵고,&lt;br /&gt;확장성에도 제약이 따릅니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MCP의 혁신&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MCP(Model Context Protocol)는 이러한 한계를 극복하는 표준화된 도구 연동 프로토콜&lt;/b&gt;입니다.&lt;br /&gt;MCP Server로 각 도구(모듈)를 구현하면,&lt;br /&gt;어떤 애플리케이션(클라이언트)이든 MCP 프로토콜을 지원하기만 하면&lt;br /&gt;별도의 추가 개발 없이도 해당 도구를 자유롭게 연동&amp;middot;활용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;:&lt;br /&gt;MCP로 개발된 도구는 표준 인터페이스를 통해&lt;br /&gt;다양한 LLM/AI 시스템과 손쉽게 연결할 수 있으므로,&lt;br /&gt;새로운 비즈니스 요구나 기술 변화에 유연하게 대응할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마이크로서비스 아키텍처&lt;/b&gt;:&lt;br /&gt;각 MCP 서버(도구)는 완전히 독립적으로 동작하며,&lt;br /&gt;장애가 발생해도 다른 도구나 전체 서비스에 영향을 주지 않습니다.&lt;br /&gt;이는 시스템의 안정성과 장애 격리(장애 전파 차단)에 큰 장점이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디커플링 및 유연성&lt;/b&gt;:&lt;br /&gt;MCP 서버는 서버간의 느슨하게 결합(Decoupling)되어 있어&lt;br /&gt;언제든지 새로운 기능을 추가하거나,&lt;br /&gt;기존 도구를 교체&amp;middot;업데이트할 수 있는 높은 유연성을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 방식의 다양성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명령형(On-demand) 실행&lt;/b&gt;:&lt;br /&gt;MCP 서버는 항상 구동되어 있을 필요 없이,&lt;br /&gt;실제로 요청이 들어올 때만 프로세스를 자동으로 실행해&lt;br /&gt;작업을 처리한 후 종료할 수 있습니다.&lt;br /&gt;이 방식은 리소스(메모리, CPU) 사용을 최소화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데몬(Always-on) 실행&lt;/b&gt;:&lt;br /&gt;필요하다면 MCP 서버를 상시 데몬(Always-on) 형태로 구동해&lt;br /&gt;빠른 응답성과 실시간 처리가 필요한 환경에도 대응할 수 있습니다.&lt;br /&gt;데몬 방식은 자원을 더 많이 사용하지만,&lt;br /&gt;매번 프로세스를 띄우는 오버헤드가 없다는 장점이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정리:&lt;/b&gt;&lt;br /&gt;MCP는 도구(모듈) 개발&amp;middot;운영의 표준화와 확장성을 극대화하고,&lt;br /&gt;마이크로서비스 기반의 장애 격리, 실행 유연성, 보안성, 중앙 관리 등&lt;br /&gt;현대 AI/LLM 애플리케이션에 필요한 모든 아키텍처적 장점을 제공합니다.&lt;br /&gt;이로써 다양한 클라이언트와 도구가 자유롭게 조합&amp;middot;확장되는&lt;br /&gt;혁신적인 AI 생태계를 실현할 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <category>llm</category>
      <category>MCP</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/454</guid>
      <comments>https://syaku.tistory.com/454#entry454comment</comments>
      <pubDate>Thu, 15 May 2025 20:59:07 +0900</pubDate>
    </item>
    <item>
      <title>alpine linux puppeteer 실행 오류</title>
      <link>https://syaku.tistory.com/453</link>
      <description>&lt;h1&gt;&lt;strong&gt;원인 분석&lt;/strong&gt;&lt;/h1&gt;
&lt;h2&gt;발생하는 최초 위치&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;alpine linux 기반의 도커 컨테이너에서 puppeteer 실행 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;세부적인 원인&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;puppeteer 의 필수 패키지 설치 누락&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# 로그 오류 spawn /home/app/node_modules/puppeteer/.local-chromium/linux-494755/chrome-linux/chrome ENOENT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;puppeteer 에서 설치한 크롬 버전이 리눅스 환경에서 작동하지 않음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# 로그 오류 Error loading shared library libudev.so.1: No such file or directory (needed by /root/.cache/puppeteer/chrome/linux-131.0.6778.204/chrome-linux64/chrome) 2025-01-03 16:24:21 web-crawler:start: Error relocating /root/.cache/puppeteer/chrome/linux-131.0.6778.204/chrome-linux64/chrome: posix_fallocate64: symbol not found&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;puppeteer 에서 크롬 설치를 하지 않고 직접 크롬 설치하면 버전 호환성 오류&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# 로그 오류 puppeteerOptions {executablePath: &amp;#39;/usr/bin/chromium-browser&amp;#39;,headless: true,args: [&amp;#39;--no-sandbox&amp;#39;,&amp;#39;--disable-setuid-sandbox&amp;#39;,&amp;#39;--disable-dev-shm-usage&amp;#39;]} 2025-01-02 20:43:22 web-crawler:start: Crawling failed: Error: Failed to launch browser: Could not find Chrome (ver. 131.0.6778.204). This can occur if either&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;strong&gt;문제 해결&lt;/strong&gt;&lt;/h1&gt;
&lt;h2&gt;puppeteer 를 사용하기 위한 필수 패키지 설치 누락 오류&lt;/h2&gt;
&lt;p&gt;패키지 설치&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk update &amp;amp;&amp;amp; apk upgrade &amp;amp;&amp;amp; \
    apk add --no-cache \
    nss \
    freetype \
    freetype-dev \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    nodejs \
    npm \
    font-liberation \
    alsa-lib \
    at-spi2-core \
    atkmm2.36 \
    libc6-compat \
    cairo \
    cups-libs \
    dbus-libs \
    expat \
    fontconfig \
    mesa-gbm \
    libgcc \
    glib \
    gtk+3.0 \
    nspr \
    nss \
    pango \
    libstdc++ \
    libx11 \
    libxcb \
    libxcomposite \
    libxcursor \
    libxdamage \
    libxext \
    libxfixes \
    libxi \
    libxrandr \
    libxrender \
    libxscrnsaver \
    libxtst \
    wget \
    xdg-utils&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;alpine linux 에서 puppeteer 에서 설치된 크롬 사용 하지 못함&lt;/h2&gt;
&lt;p&gt;직접 크롬 설치&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add --no-cache \
    chromium \
    chromium-chromedriver \

# Puppeteer cache 관련 디렉토리 및 권한 설정
mkdir -p /root/.cache/puppeteer &amp;amp;&amp;amp; \ chmod -R 777 /root/.cache/puppeteer

# Puppeteer 환경 변수 설정
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
export PUPPETEER_CACHE_DIR=/root/.cache/puppeteer&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;puppeteer 런처 옵션 추가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await puppeteer.launch({
    executablePath: &amp;#39;/usr/bin/chromium&amp;#39;, // process.env.PUPPETEER_EXECUTABLE_PATH
    cacheDirectory: &amp;#39;/root/.cache/puppeteer&amp;#39;, // process.env.PUPPETEER_CACHE_DIR
});&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;puppeteer 에서 크롬 설치를 하지 않고 직접 크롬을 설치하면 버전 호환성 오류&lt;/h2&gt;
&lt;p&gt;크롬 버전 확인 puppeteer version 확인은 package-lock.json 에서 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 설치된 크롬 버전 확인
$ chromium --version&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;puppeteer 는 &lt;code&gt;23.11.1&lt;/code&gt; 고정 버전을 사용하면 크롬 버전 &lt;code&gt;131.0.6778.204&lt;/code&gt; 설치하면 호환된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# npm 설치 패키지
puppeteer@23.11.1

# alpine 설치 패키지
chromium=131.0.6778.204-r0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;alpine 에서 직접 크롬 설치&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ echo &amp;quot;http://dl-cdn.alpinelinux.org/alpine/edge/main&amp;quot; &amp;gt;&amp;gt; /etc/apk/repositories &amp;amp;&amp;amp; \
echo &amp;quot;http://dl-cdn.alpinelinux.org/alpine/edge/community&amp;quot; &amp;gt;&amp;gt; /etc/apk/repositories

$ apk add --no-cache chromium=131.0.6778.204-r0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;도커를 사용하면 puppeteer 런처 실행시 옵션 추가 필요&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await puppeteer.launch({
    executablePath: &amp;#39;/usr/bin/chromium&amp;#39;, // process.env.PUPPETEER_EXECUTABLE_PATH
    cacheDirectory: &amp;#39;/root/.cache/puppeteer&amp;#39;, // process.env.PUPPETEER_CACHE_DIR
    args: [&amp;quot;--no-sandbox&amp;quot;,&amp;quot;--disable-setuid-sandbox&amp;quot;,&amp;quot;--disable-dev-shm-usage&amp;quot;, &amp;quot;--disable-gpu&amp;quot;]
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;옵션 설명&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;컨테이너 환경 최적화&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-no-sandbox&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-disable-setuid-sandbox&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-disable-dev-shm-usage&lt;/code&gt;&lt;/strong&gt;는 Docker와 같은 컨테이너 환경에서 안정적인 브라우저 실행을 보장합니다.&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;code&gt;-disable-gpu&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-disable-software-rasterizer&lt;/code&gt;&lt;/strong&gt;는 GPU가 없는 환경에서 성능 문제를 방지합니다.&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;code&gt;-disable-extensions&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-hide-scrollbars&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;-mute-audio&lt;/code&gt;&lt;/strong&gt;는 테스트나 크롤링 작업 중 불필요한 리소스 소비를 줄입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발</category>
      <category>puppeteer</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/453</guid>
      <comments>https://syaku.tistory.com/453#entry453comment</comments>
      <pubDate>Fri, 3 Jan 2025 17:10:44 +0900</pubDate>
    </item>
    <item>
      <title>Fabric.js에서 수직 정렬과 크기 조절이 가능한 TextboxVertical 구현하기</title>
      <link>https://syaku.tistory.com/452</link>
      <description>&lt;h1&gt;Fabric.js에서 수직 정렬과 크기 조절이 가능한 TextboxVertical 구현하기&lt;/h1&gt;
&lt;h1&gt;Implementing TextboxVertical with Vertical Alignment and Resizing in Fabric.js&lt;/h1&gt;
&lt;p&gt;Fabric.js를 사용하다 보면 텍스트박스의 수직 정렬과 크기 조절에 제한이 있다는 것을 발견하게 됩니다. 이러한 제한을 해결하기 위해 Textbox 클래스를 확장한 &lt;code&gt;TextboxVertical&lt;/code&gt; 클래스를 소개합니다.&lt;/p&gt;
&lt;p&gt;When using Fabric.js, you might notice limitations in textbox vertical alignment and resizing. To address these limitations, we introduce the &lt;code&gt;TextboxVertical&lt;/code&gt; class that extends the Textbox class.&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/xArXF/btsLpEcHWLB/IwiNdGpoE0sOuRIfagykGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xArXF/btsLpEcHWLB/IwiNdGpoE0sOuRIfagykGk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xArXF/btsLpEcHWLB/IwiNdGpoE0sOuRIfagykGk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/xArXF/btsLpEcHWLB/IwiNdGpoE0sOuRIfagykGk/img.gif&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;h2&gt;&lt;strong&gt;사용 목적 (Purpose)&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;텍스트박스 내 텍스트의 수직 정렬 (상단, 중앙, 하단)&lt;br&gt;Text vertical alignment within textbox (top, middle, bottom)&lt;/li&gt;
&lt;li&gt;텍스트박스의 높이와 너비를 자유롭게 조절&lt;br&gt;Freely adjustable height and width of textbox&lt;/li&gt;
&lt;li&gt;편집 모드에서도 정확한 커서 위치 유지&lt;br&gt;Maintain accurate cursor position even in edit mode&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;주요 기능 (Key Features)&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;수직 정렬 지원 (Vertical Alignment Support)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;verticalAlign&lt;/code&gt; 속성을 통해 텍스트의 수직 위치를 지정&lt;br&gt;Specify vertical position of text through &lt;code&gt;verticalAlign&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;&amp;#39;top&amp;#39;, &amp;#39;middle&amp;#39;, &amp;#39;bottom&amp;#39; 세 가지 옵션 제공&lt;br&gt;Provides three options: &amp;#39;top&amp;#39;, &amp;#39;middle&amp;#39;, &amp;#39;bottom&amp;#39;&lt;/li&gt;
&lt;li&gt;텍스트박스의 높이가 변경되어도 정렬 유지&lt;br&gt;Maintains alignment even when textbox height changes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;크기 조절 기능 (Resizing Capability)&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;텍스트박스의 높이와 너비를 자유롭게 조절 가능&lt;br&gt;Freely adjustable height and width of textbox&lt;/li&gt;
&lt;li&gt;스케일링 시 텍스트 왜곡 방지&lt;br&gt;Prevents text distortion during scaling&lt;/li&gt;
&lt;li&gt;크기 조절 후에도 텍스트 정렬 유지&lt;br&gt;Maintains text alignment after resizing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;사용 방법 (Usage)&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// TextboxVertical 인스턴스 생성
// Create TextboxVertical instance
const textbox = new TextboxVertical(&amp;#39;Hello World&amp;#39;, {
    left: 100,
    top: 100,
    width: 200,
    height: 150,
    verticalAlign: &amp;#39;middle&amp;#39;, // &amp;#39;top&amp;#39;, &amp;#39;middle&amp;#39;, &amp;#39;bottom&amp;#39;
    textAlign: &amp;#39;center&amp;#39;
});

// 캔버스에 추가
// Add to canvas
canvas.add(textbox);&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;strong&gt;장점 (Benefits)&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;유연한 레이아웃 (Flexible Layout)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;텍스트의 수직 위치를 자유롭게 조정 가능&lt;br&gt;Freely adjustable vertical text position&lt;/li&gt;
&lt;li&gt;다양한 디자인 요구사항 충족&lt;br&gt;Meets various design requirements&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;직관적인 사용성 (Intuitive Usability)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;기존 Textbox와 동일한 방식으로 사용&lt;br&gt;Uses the same approach as the original Textbox&lt;/li&gt;
&lt;li&gt;추가된 verticalAlign 속성으로 쉽게 수직 정렬 제어&lt;br&gt;Easy vertical alignment control with added verticalAlign property&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;안정적인 텍스트 편집 (Stable Text Editing)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;편집 모드에서도 정확한 커서 위치 제공&lt;br&gt;Provides accurate cursor position in edit mode&lt;/li&gt;
&lt;li&gt;텍스트 입력 시 자연스러운 사용자 경험&lt;br&gt;Natural user experience during text input&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;활용 사례 (Use Cases)&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;배너 디자인 (Banner Design)&lt;/li&gt;
&lt;li&gt;카드 레이아웃 (Card Layouts)&lt;/li&gt;
&lt;li&gt;텍스트 중심 인포그래픽 (Text-focused Infographics)&lt;/li&gt;
&lt;li&gt;동적 콘텐츠가 필요한 웹 애플리케이션 (Web Applications Requiring Dynamic Content)&lt;/li&gt;
&lt;li&gt;텍스트 에디터 구현 (Text Editor Implementation)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 커스텀 클래스를 통해 Fabric.js의 텍스트박스 기능을 한층 더 강화하고, 더 다양한 디자인과 레이아웃을 구현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;This custom class enhances Fabric.js textbox functionality and enables implementation of more diverse designs and layouts.&lt;/p&gt;
&lt;h2&gt;소스 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
class TextboxVertical extends Textbox {
  constructor(text, options) {
    super(text, options);
    this.verticalAlign = options.verticalAlign || &amp;#39;top&amp;#39;; // Default to &amp;#39;top&amp;#39;
  }

  initDimensions() {
    if (!this.initialized) {
      return;
    }
    this.isEditing &amp;amp;&amp;amp; this.initDelayedCursor();
    this._clearCache();
    // clear dynamicMinWidth as it will be different after we re-wrap line
    this.dynamicMinWidth = 0;
    // wrap lines
    this._styleMap = this._generateStyleMap(this._splitText());
    // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap
    if (this.dynamicMinWidth &amp;gt; this.width) {
      this._set(&amp;#39;width&amp;#39;, this.dynamicMinWidth);
    }
    if (this.textAlign.includes(&amp;#39;justify&amp;#39;)) {
      // once text is measured we need to make space fatter to make justified text.
      this.enlargeSpaces();
    }
    // clear cache and re-calculate height
    this.height = !this.height ? this.calcTextHeight() : this.height;
  }

  _render(ctx) {
    // 스케일 조정을 먼저 처리
    if (this.scaleY !== 1) {
      this.height = this.height * this.scaleY;
      this.scaleY = 1;
    }

    if (this.scaleX !== 1) {
      this.width = this.width * this.scaleX;
      this.scaleX = 1;
    }

    // 텍스트 높이 계산
    const totalTextHeight = this.calcTextHeight();
    let verticalOffset = 0;

    if (this.verticalAlign === &amp;#39;middle&amp;#39;) {
      verticalOffset = (this.height - totalTextHeight) / 2;
    } else if (this.verticalAlign === &amp;#39;bottom&amp;#39;) {
      verticalOffset = this.height - totalTextHeight;
    }

    // 편집 모드일 때는 오프셋을 조정
    if (this.isEditing) {
      this.top += verticalOffset;
      super._render(ctx);
      this.top -= verticalOffset;
    } else {
      // 편집 모드가 아닐 때는 기존 방식 유지
      ctx.translate(0, verticalOffset);
      super._render(ctx);
      ctx.translate(0, -verticalOffset);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발</category>
      <category>fabricJS</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/452</guid>
      <comments>https://syaku.tistory.com/452#entry452comment</comments>
      <pubDate>Thu, 19 Dec 2024 20:35:46 +0900</pubDate>
    </item>
    <item>
      <title>React에서 onContextMenu로 우클릭 이벤트 제어하기: 기본 사용법과 커스텀 컨텍스트 메뉴 구현</title>
      <link>https://syaku.tistory.com/451</link>
      <description>&lt;h1&gt;React에서 &lt;code&gt;onContextMenu&lt;/code&gt; 이벤트 핸들러 이해하기&lt;/h1&gt;
&lt;p&gt;React는 선언적 방식으로 이벤트를 처리하기 위해 합성 이벤트(Synthetic Events) 시스템을 제공합니다. 이 글에서는 React의 &lt;code&gt;onContextMenu&lt;/code&gt; 이벤트 핸들러에 대해 배우고, 이를 활용해 우클릭(컨텍스트 메뉴) 동작을 사용자 정의하는 방법을 살펴봅니다. &lt;/p&gt;
&lt;p&gt;이 글을 읽으면 다음을 배울 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onContextMenu&lt;/code&gt;가 어떻게 동작하는지 이해&lt;/li&gt;
&lt;li&gt;기본 브라우저 컨텍스트 메뉴를 비활성화하고 커스텀 메뉴를 구현하는 방법&lt;/li&gt;
&lt;li&gt;React와 네이티브 DOM 이벤트의 차이점&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;문제 제기 및 맥락&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;웹 애플리케이션에서 우클릭(컨텍스트 메뉴)은 중요한 사용자 경험 요소 중 하나입니다. 기본 브라우저 컨텍스트 메뉴는 대부분의 상황에서 유용하지만, 특정 애플리케이션에서는 이를 비활성화하거나 사용자 정의 메뉴를 표시해야 할 때가 있습니다.&lt;/p&gt;
&lt;p&gt;예를 들어, 다음과 같은 경우에 커스텀 컨텍스트 메뉴가 필요할 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파일 관리 애플리케이션에서 우클릭으로 특정 파일 작업 메뉴(복사, 삭제 등) 표시&lt;/li&gt;
&lt;li&gt;대시보드에서 그래프나 데이터 테이블의 특정 항목을 우클릭했을 때 추가 옵션 제공&lt;/li&gt;
&lt;li&gt;게임 UI에서 우클릭으로 기능을 활성화하거나 설정을 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;React에서는 &lt;code&gt;onContextMenu&lt;/code&gt;를 이용하여 이러한 요구 사항을 쉽게 구현할 수 있습니다. 브라우저의 기본 &lt;code&gt;contextmenu&lt;/code&gt; 이벤트를 대체하고, React의 상태 관리 기능과 결합하여 동적인 사용자 인터페이스를 만들 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;핵심 내용&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;1. React의 &lt;code&gt;onContextMenu&lt;/code&gt; 동작 원리&lt;/h3&gt;
&lt;p&gt;React의 &lt;code&gt;onContextMenu&lt;/code&gt; 이벤트 핸들러는 네이티브 DOM 이벤트인 &lt;code&gt;contextmenu&lt;/code&gt;를 감싸는 합성 이벤트로, 다음과 같은 특징을 가지고 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;우클릭 시 발생하는 기본 동작을 React 방식으로 처리할 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;event.preventDefault()&lt;/code&gt;를 호출하면 브라우저의 기본 컨텍스트 메뉴를 비활성화 가능.&lt;/li&gt;
&lt;li&gt;React의 상태 관리와 렌더링 사이클과 자연스럽게 통합되어 추가적인 코드 작성 없이 선언적으로 이벤트를 관리할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. React와 네이티브 DOM 이벤트 비교&lt;/h3&gt;
&lt;p&gt;React의 합성 이벤트와 네이티브 DOM 이벤트의 주요 차이는 다음과 같습니다:&lt;/p&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;React의 &lt;code&gt;onContextMenu&lt;/code&gt;&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;네이티브 DOM 이벤트 (&lt;code&gt;addEventListener&lt;/code&gt;)&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;JSX 속성을 통해 선언적 방식 사용&lt;/td&gt;
&lt;td&gt;DOM 요소에 명령형 방식으로 리스너 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;상태 관리와 통합&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ React 컴포넌트 상태와 자연스럽게 연결&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;✅ React가 자동 관리&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;외부 DOM 요소 지원&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ React 컴포넌트 내에서만 적용 가능&lt;/td&gt;
&lt;td&gt;✅ 외부 DOM 및 제3자 라이브러리에 자유롭게 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;React에서 &lt;code&gt;onContextMenu&lt;/code&gt;를 사용하는 것이 대부분의 경우 더 간단하고 선언적입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;3. 예제: React에서 &lt;code&gt;onContextMenu&lt;/code&gt; 활용법&lt;/h3&gt;
&lt;h4&gt;3.1. 기본 사용 예제&lt;/h4&gt;
&lt;p&gt;아래 코드는 우클릭 이벤트를 감지하고 브라우저의 기본 컨텍스트 메뉴를 비활성화하는 기본적인 예제입니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;quot;react&amp;quot;;

const App = () =&amp;gt; {
  const handleContextMenu = (event) =&amp;gt; {
    event.preventDefault(); // 기본 브라우저 메뉴 비활성화
    console.log(&amp;quot;Right-click detected at&amp;quot;, event.clientX, event.clientY);
  };

  return (
    &amp;lt;div
      onContextMenu={handleContextMenu}
      style={{
        width: &amp;quot;300px&amp;quot;,
        height: &amp;quot;200px&amp;quot;,
        backgroundColor: &amp;quot;#f0f0f0&amp;quot;,
        display: &amp;quot;flex&amp;quot;,
        justifyContent: &amp;quot;center&amp;quot;,
        alignItems: &amp;quot;center&amp;quot;,
      }}
    &amp;gt;
      Right-click here
    &amp;lt;/div&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.2. 커스텀 컨텍스트 메뉴 구현&lt;/h4&gt;
&lt;p&gt;우클릭 시 사용자 정의 메뉴를 표시하는 예제입니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState } from &amp;quot;react&amp;quot;;

const App = () =&amp;gt; {
  const [menuPosition, setMenuPosition] = useState(null);

  const handleContextMenu = (event) =&amp;gt; {
    event.preventDefault();
    setMenuPosition({ x: event.clientX, y: event.clientY });
  };

  const handleCloseMenu = () =&amp;gt; {
    setMenuPosition(null);
  };

  return (
    &amp;lt;div
      onContextMenu={handleContextMenu}
      style={{
        width: &amp;quot;100vw&amp;quot;,
        height: &amp;quot;100vh&amp;quot;,
        backgroundColor: &amp;quot;#f0f0f0&amp;quot;,
      }}
    &amp;gt;
      {menuPosition &amp;amp;&amp;amp; (
        &amp;lt;div
          style={{
            position: &amp;quot;absolute&amp;quot;,
            top: menuPosition.y,
            left: menuPosition.x,
            backgroundColor: &amp;quot;#fff&amp;quot;,
            border: &amp;quot;1px solid #ccc&amp;quot;,
            padding: &amp;quot;10px&amp;quot;,
          }}
          onClick={handleCloseMenu}
        &amp;gt;
          &amp;lt;p&amp;gt;Custom Context Menu&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;Option 1&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;Option 2&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
      Right-click anywhere to open the menu.
    &amp;lt;/div&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onContextMenu&lt;/code&gt;를 통해 마우스 위치(&lt;code&gt;event.clientX&lt;/code&gt;, &lt;code&gt;event.clientY&lt;/code&gt;)를 가져와 메뉴의 위치를 동적으로 설정합니다.&lt;/li&gt;
&lt;li&gt;메뉴 클릭 시 &lt;code&gt;setMenuPosition(null)&lt;/code&gt;을 호출하여 메뉴를 닫습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;결론 및 요약&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;React의 &lt;code&gt;onContextMenu&lt;/code&gt;는 네이티브 DOM의 &lt;code&gt;contextmenu&lt;/code&gt; 이벤트를 효과적으로 감싸는 합성 이벤트입니다. 이를 통해 다음을 구현할 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기본 브라우저 컨텍스트 메뉴를 비활성화&lt;/li&gt;
&lt;li&gt;사용자 정의 메뉴를 표시하여 특정 애플리케이션 요구 사항 처리&lt;/li&gt;
&lt;li&gt;React의 상태 관리와 결합하여 동적인 UI를 간편하게 구현&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;a href=&quot;https://react.dev&quot;&gt;React 공식 문서: 합성 이벤트 시스템&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event&quot;&gt;MDN Web Docs: &lt;code&gt;contextmenu&lt;/code&gt; 이벤트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 &lt;code&gt;onContextMenu&lt;/code&gt;를 활용하여 더 정교한 사용자 경험을 제공하는 앱을 구현해 보세요!  &lt;/p&gt;</description>
      <category>개발</category>
      <category>reactjs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/451</guid>
      <comments>https://syaku.tistory.com/451#entry451comment</comments>
      <pubDate>Wed, 18 Dec 2024 16:57:41 +0900</pubDate>
    </item>
    <item>
      <title>TypeScript 속성 정의와 값 부재의 이해: id: string | undefined vs id?: string</title>
      <link>https://syaku.tistory.com/450</link>
      <description>&lt;h2&gt;&lt;code&gt;id: string | undefined&lt;/code&gt;와 &lt;code&gt;id?: string&lt;/code&gt; 차이&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;id: string | undefined&lt;/code&gt;와 &lt;code&gt;id?: string&lt;/code&gt;는 TypeScript에서 비슷해 보이지만, 미묘한 차이가 있습니다. 이 차이는 주로 &lt;strong&gt;타입 정의&lt;/strong&gt;와 &lt;strong&gt;값의 존재 여부&lt;/strong&gt;에서 발생합니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;&lt;code&gt;id: string | undefined&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;의미&lt;/strong&gt;: &lt;code&gt;id&lt;/code&gt;는 반드시 존재해야 하지만, 값이 &lt;code&gt;string&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt;일 수 있습니다.&lt;br&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;br&gt;    -   객체에 &lt;code&gt;id&lt;/code&gt; 속성이 반드시 존재해야 합니다.&lt;br&gt;    -   하지만 그 값은 &lt;code&gt;string&lt;/code&gt;이거나 &lt;code&gt;undefined&lt;/code&gt;일 수 있습니다.&lt;br&gt;&lt;strong&gt;예시&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface WebCrawlerSpec {
  id: string | undefined;
}

const example1: WebCrawlerSpec = { id: &amp;quot;123&amp;quot; }; // ✅ 유효
const example2: WebCrawlerSpec = { id: undefined }; // ✅ 유효
const example3: WebCrawlerSpec = {}; // ❌ 오류: &amp;#39;id&amp;#39; 속성이 누락됨&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;&lt;code&gt;id?: string&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;의미&lt;/strong&gt;: &lt;code&gt;id&lt;/code&gt;는 선택적 속성으로, 객체에 존재하지 않을 수도 있습니다. 만약 존재한다면, 값은 반드시 &lt;code&gt;string&lt;/code&gt;이어야 합니다.&lt;br&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;br&gt;    -   객체에 &lt;code&gt;id&lt;/code&gt; 속성이 없을 수도 있습니다.&lt;br&gt;    -   만약 &lt;code&gt;id&lt;/code&gt;가 존재한다면, 값은 반드시 &lt;code&gt;string&lt;/code&gt;이어야 합니다.&lt;br&gt;&lt;strong&gt;예시&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface WebCrawlerSpec {
  id?: string;
}

const example1: WebCrawlerSpec = { id: &amp;quot;123&amp;quot; }; // ✅ 유효
const example2: WebCrawlerSpec = {}; // ✅ 유효 (속성이 없어도 됨)
const example3: WebCrawlerSpec = { id: undefined }; // ✅ 유효 (undefined도 허용됨)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&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;th&gt;값의 타입&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;`id: string&lt;/td&gt;
&lt;td&gt;undefined`&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;반드시 존재해야 함&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id?: string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;존재하지 않아도 됨&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;값은 &lt;code&gt;string&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt; 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;만약 속성이 항상 객체에 있어야 하지만 값이 없을 수도 있다면&lt;/strong&gt;, &lt;code&gt;id: string | undefined&lt;/code&gt;를 사용하세요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;만약 속성이 아예 없어도 되는 경우라면&lt;/strong&gt;, &lt;code&gt;id?: string&lt;/code&gt;를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;당신의 생각:&lt;/h4&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;undefined는 선언하지 않아도 된다라고 생각했어. 그렇다면 id가 없어도 된다라고 이해했어.&amp;quot;*&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;이는 정확히 맞습니다. TypeScript에서 &lt;code&gt;?&lt;/code&gt;(선택적 속성)는 해당 속성이 없어도 된다는 것을 의미합니다. 따라서, 만약 속성이 아예 없어도 되는 상황이라면 &lt;code&gt;id?: string&lt;/code&gt;를 사용하는 것이 더 적합합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;undefined&lt;/code&gt;와 &lt;code&gt;null&lt;/code&gt;의 차이&lt;/h2&gt;
&lt;p&gt;TypeScript에서 &lt;code&gt;undefined&lt;/code&gt;와 &lt;code&gt;null&lt;/code&gt;은 모두 값이 없음을 나타내지만, 의미와 사용 사례에서 차이가 있습니다. 이 차이를 이해하면 코드에서 언제 각 값을 사용할지 결정하는 데 도움이 됩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;undefined&lt;/code&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;/li&gt;
&lt;li&gt;&lt;code&gt;let x: number | undefined; console.log(x); // 출력: undefined&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;null&lt;/code&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;/li&gt;
&lt;li&gt;&lt;code&gt;let y: number | null = null; console.log(y); // 출력: null&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;주요 고려 사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;의도성&lt;/strong&gt;: 변수가 비어 있거나 지워져야 함을 명시적으로 나타내려면 &lt;code&gt;null&lt;/code&gt;을 사용합니다. 변수가 초기화되지 않았거나 선택적 필드가 설정되지 않았음을 나타내려면 &lt;code&gt;undefined&lt;/code&gt;를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript 가이드라인&lt;/strong&gt;: 일부 TypeScript 스타일 가이드에서는 일관성과 단순성을 위해 &lt;code&gt;null&lt;/code&gt;보다 &lt;code&gt;undefined&lt;/code&gt;를 선호하기도 합니다. 이는 JavaScript가 자연스럽게 초기화되지 않은 변수에 대해 &lt;code&gt;undefined&lt;/code&gt;를 사용하기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실용적 차이점&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;API나 데이터베이스와 인터페이스할 때, JSON은 &lt;code&gt;null&lt;/code&gt;을 지원하므로 &lt;code&gt;null&lt;/code&gt;이 자주 사용됩니다. 반면, &lt;code&gt;undefined&lt;/code&gt; 값은 직렬화 시 생략되는 경우가 많습니다.&lt;/li&gt;
&lt;li&gt;TypeScript에서 엄격한 null 체크(&lt;code&gt;--strictNullChecks&lt;/code&gt;)를 사용하면 &lt;code&gt;null&lt;/code&gt;과 &lt;code&gt;undefined&lt;/code&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;code&gt;undefined&lt;/code&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;code&gt;null&lt;/code&gt;을 사용할 때&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;값을 명시적으로 비우거나 비어 있어야 함을 나타낼 때.&lt;/li&gt;
&lt;li&gt;API나 데이터 구조에서 비어 있는 값을 나타내는 방법으로 &lt;code&gt;null&lt;/code&gt;이 기대될 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결국, &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt;, 또는 둘 다 사용하는 것은 프로젝트의 규칙과 코드베이스의 특정 요구 사항에 따라 달라집니다. 이러한 값들이 어떻게 사용되는지에 대한 일관성을 유지하면 혼란과 잠재적인 버그를 줄일 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>TypeScript</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/450</guid>
      <comments>https://syaku.tistory.com/450#entry450comment</comments>
      <pubDate>Sat, 14 Dec 2024 17:08:11 +0900</pubDate>
    </item>
    <item>
      <title>잠(수면) 부족과 피곤함의 원인과 이를 극복하는 방법</title>
      <link>https://syaku.tistory.com/449</link>
      <description>&lt;h1&gt;잠(수면) 부족과 피곤함의 원인과 이를 극복하는 방법&lt;/h1&gt;
&lt;p&gt;피곤함은 단순히 수면 시간이 부족해서 생기는 것만이 아닙니다. 예를 들어, 6시간 정도의 수면을 취했음에도 불구하고 여전히 피곤하다면, 이는 수면의 질이 낮거나 스트레스, 생활 습관 등 다른 요인들이 작용했을 가능성이 높습니다. 피곤함의 원인을 제대로 이해하고 이를 개선하기 위한 방법을 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;피곤함의 원인&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;수면의 질&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;수면 시간이 충분해도 수면의 질이 낮으면 피로가 해소되지 않을 수 있습니다. 깊은 잠에 들어야 하는 렘수면이나 비렘수면 단계가 부족하거나, 자주 깨어나는 경우 수면의 질이 떨어집니다. 이는 낮 동안 무기력함과 집중력 저하로 이어질 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;예: 자기 전 스마트폰 사용이 멜라토닌 분비를 방해해 숙면을 방해합니다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;수면 무호흡증&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;수면 무호흡증은 수면 중 호흡이 반복적으로 멈추는 질환으로, 산소 공급이 부족해 수면의 질을 크게 떨어뜨립니다. 이는 깊은 잠을 방해하고 다음 날 극도의 피곤함을 유발합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;코골이와 동반될 가능성이 높으며, 장기적으로 심혈관 질환이나 혈압 상승으로 이어질 수 있습니다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;스트레스와 호르몬 불균형&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;스트레스는 코르티솔이라는 스트레스 호르몬의 과도한 분비를 촉진해 숙면을 방해합니다. 또한, 갑상선 호르몬이나 신경전달 물질(예: 세로토닌)과 같은 호르몬의 불균형도 만성 피로를 유발할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;스트레스와 호르몬 문제는 신체 피로뿐 아니라 정서적 피곤함까지 동반합니다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;생활 습관&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;불규칙한 수면 패턴, 잦은 카페인 섭취, 알코올 섭취, 낮 동안의 신체 활동 부족 등이 피곤함을 유발합니다. 특히, 저녁 늦게 카페인 또는 알코올을 섭취하면 수면 깊이에 영향을 미쳐 피로가 회복되지 않을 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;규칙적인 식사와 운동이 부족하면 에너지 관리 능력이 저하되는 원인이 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;졸음 극복 및 집중력 높이는 방법&lt;/h2&gt;
&lt;h3&gt;1. &lt;strong&gt;규칙적인 운동&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;낮 시간대에 30분 정도 꾸준히 운동하면 체내 생체 리듬이 조절되고, 숙면의 질이 향상됩니다. 특히 유산소 운동은 스트레스를 줄이고 신체 회복을 돕는 데 효과적입니다.&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;예: 아침에 간단한 조깅이나 요가를 하면 하루 종일 집중력과 에너지가 높아질 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;2. &lt;strong&gt;명상과 심호흡&lt;/strong&gt;&lt;/h3&gt;
&lt;p&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;간단한 &lt;code&gt;4-7-8 호흡법&lt;/code&gt;을 시도해 보세요: 4초 들이마시고, 7초 멈추며, 8초 내쉬기. 이는 졸음 유도에도 효과적입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;3. &lt;strong&gt;환경 조절&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;작업하거나 잠들기 전에는 환경이 큰 영향을 미칩니다. 조용하고 어두운 환경을 유지하고, 실내 온도는 18~22℃로 조절하세요. 또한, 숙면을 위해 전자기기 사용을 줄이고, 필요하면 백색 소음이나 바이노럴 비트 등 차분한 소리를 활용해 집중력을 유지하거나 잠들 수 있도록 돕습니다.&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;쾌적한 환경은 몸의 긴장을 줄이고, 신경 피로를 덜어줍니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;4. &lt;strong&gt;포모도로 기법 활용&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;포모도로 기법은 시간을 25분씩 나누어 집중하고 5분간 휴식하는 방식으로, 장기간 집중해야 하는 일을 할 때 매우 유용합니다. 일을 효율적으로 끝내는 데 도움을 줄 뿐만 아니라, 간헐적인 휴식이 피로감을 줄이는 데도 효과적입니다.&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;자기 전에 할 일을 정리하며 사용하는 것도 추천됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;5. &lt;strong&gt;적절한 휴식&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;피로가 쌓였을 때는 짧은 낮잠(20분 이내)이나 스트레칭 같은 가벼운 활동이 효과적입니다. 피곤함을 방치하면 오히려 생산성이 감소하니, 중간중간 리프레시 시간이 필요합니다.&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;낮잠은 활력을 회복시키지만, 30분 이상 자면 오히려 더 피곤할 수 있으니 주의하세요.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;졸음을 극복하는 방법&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;물 마시기&lt;/strong&gt;&lt;br&gt;탈수는 피곤함과 집중력 저하를 악화시킬 수 있습니다. 의식적으로 물을 마시는 습관을 들이면 체내 순환이 원활해지고 피로가 줄어듭니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;책상에 물병을 비치하거나 일정 시간마다 물을 마시는 알람을 설정해보세요.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;신체 자극&lt;/strong&gt;&lt;br&gt;졸음을 이겨내기 위해 간단한 신체 활동을 시도해 보세요. 예를 들어, 찬물로 얼굴을 씻거나 손목과 목을 스트레칭하거나 껌을 씹는 것만으로도 신체가 각성됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이는 특히 오후 시간 졸음이 쏟아질 때 유용합니다.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;환기와 산책&lt;/strong&gt;&lt;br&gt;신선한 공기를 마시고 자연광을 쬐면 졸음이 빠르게 해소됩니다. 실내 공기가 정체되어 있을 경우 졸음이 악화될 수 있으니, 환기하거나 밖에서 10분 정도 가벼운 산책을 권장합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;산책은 뇌에 산소를 공급하고, 에너지를 높이는 데 도움을 줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;피곤함은 단순히 수면 시간 부족으로만 설명되지 않습니다. 수면의 질, 스트레스, 생활 습관 등 다양한 요인이 복합적으로 작용할 수 있기 때문에, 이를 체계적으로 점검하고 개선하는 노력이 필요합니다.&lt;/p&gt;
&lt;p&gt;만약 생활 습관을 개선해도 피로가 지속된다면, 건강 전문가와 상담을 통해 수면 무호흡증, 만성 피로 증후군 등 잠재적인 건강 문제를 확인하는 것이 중요합니다. 피로를 해결하는 것은 건강하고 활기찬 삶을 위한 필수 조건임을 기억하세요!&lt;/p&gt;</description>
      <category>생활</category>
      <category>수면</category>
      <category>잠</category>
      <category>피로</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/449</guid>
      <comments>https://syaku.tistory.com/449#entry449comment</comments>
      <pubDate>Thu, 12 Dec 2024 14:25:43 +0900</pubDate>
    </item>
    <item>
      <title>ChromaDB: AI 시대를 위한 벡터 데이터베이스의 모든 것</title>
      <link>https://syaku.tistory.com/448</link>
      <description>&lt;h2&gt;1. &lt;strong&gt;ChromaDB 소개&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;ChromaDB란 무엇인가?&lt;/h3&gt;
&lt;p&gt;ChromaDB는 &lt;strong&gt;오픈소스 벡터 데이터베이스&lt;/strong&gt;로, 주로 대규모 언어 모델(LLM) 기반 애플리케이션에서 사용됩니다. 이 데이터베이스는 텍스트, 이미지, 오디오 등의 데이터를 벡터 임베딩 형태로 저장하고 검색할 수 있도록 설계되었습니다. 벡터 임베딩은 데이터를 고차원 공간에서 수치화하여 유사성을 측정할 수 있게 하는 방식으로, ChromaDB는 이를 효율적으로 관리하며 검색 작업을 수행합니다.&lt;/p&gt;
&lt;p&gt;ChromaDB의 주요 목적은 &lt;strong&gt;검색 증강 생성(Retrieval-Augmented Generation, RAG)&lt;/strong&gt;과 같은 LLM 응용 프로그램에서 데이터를 빠르게 검색하고 활용할 수 있도록 돕는 것입니다. 이를 통해 사용자는 LLM의 환각 현상을 줄이고, 더욱 정확하고 신뢰할 수 있는 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;벡터 데이터베이스의 필요성과 역할&lt;/h3&gt;
&lt;p&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;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 및 머신러닝 지원&lt;/strong&gt;: LLM과 같은 AI 모델이 생성한 임베딩 데이터를 관리하고 활용하는 데 최적화되어 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성과 성능&lt;/strong&gt;: 대규모 데이터셋을 처리하면서도 빠른 검색 속도를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 특성 덕분에 벡터 데이터베이스는 자연어 처리(NLP), 추천 시스템, 이미지 인식 등 다양한 AI 응용 분야에서 필수적인 도구로 자리 잡고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;ChromaDB의 주요 특징 및 장점&lt;/h3&gt;
&lt;p&gt;ChromaDB는 다음과 같은 특징과 장점을 제공합니다:&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;/li&gt;
&lt;li&gt;&lt;strong&gt;간단한 API&lt;/strong&gt;: Python 기반으로 사용이 간편하며, LangChain, OpenAI 등의 기술과 쉽게 통합됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;오픈소스 프로젝트&lt;/strong&gt;: Apache 2.0 라이선스로 배포되어 자유롭게 사용 가능하며 커뮤니티의 기여를 통해 지속적으로 발전하고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG 파이프라인 지원&lt;/strong&gt;: 검색 증강 생성 워크플로우를 쉽게 구현할 수 있어 LLM 기반 애플리케이션 개발에 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성&lt;/strong&gt;: 로컬 환경뿐만 아니라 클라우드 및 대규모 프로덕션 환경에서도 활용 가능하며, 지속적인 데이터 갱신 기능을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ChromaDB는 이러한 장점 덕분에 AI 응용 프로그램에서 비정형 데이터를 효과적으로 관리하고 활용하는 데 중요한 역할을 합니다.&lt;/p&gt;
&lt;h2&gt;2. &lt;strong&gt;ChromaDB의 핵심 개념&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;임베딩(Embedding)과 벡터 데이터&lt;/h3&gt;
&lt;p&gt;임베딩(Embedding)은 텍스트, 이미지, 오디오 등 비정형 데이터를 고차원 벡터 공간의 수치 데이터로 변환하는 과정입니다. 이를 통해 데이터 간의 의미적 유사성을 수학적으로 표현할 수 있습니다. 예를 들어, &amp;quot;고양이&amp;quot;와 &amp;quot;강아지&amp;quot;라는 단어는 유사한 맥락에서 사용되므로 임베딩 공간에서도 서로 가까운 벡터로 나타납니다.&lt;/p&gt;
&lt;p&gt;ChromaDB는 이러한 임베딩 데이터를 저장하고 검색하는 데 최적화된 데이터베이스입니다. 사용자는 OpenAI, Hugging Face 등 다양한 임베딩 생성 모델을 활용하여 데이터를 벡터로 변환한 뒤 ChromaDB에 저장할 수 있습니다. 이후, 특정 질의에 대해 유사한 벡터를 검색하여 관련 데이터를 빠르게 찾을 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;벡터 데이터베이스의 작동 원리&lt;/h3&gt;
&lt;p&gt;벡터 데이터베이스는 다음과 같은 단계로 작동합니다:&lt;/p&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;p&gt;ChromaDB는 이러한 과정을 효율적으로 처리하며, 특히 고차원 공간에서의 유사성 계산을 최적화하여 대규모 데이터셋에서도 빠른 검색 성능을 제공합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;RAG(Retrieval-Augmented Generation)에서의 활용&lt;/h3&gt;
&lt;p&gt;RAG(Retrieval-Augmented Generation)는 대규모 언어 모델(LLM)과 외부 데이터 소스를 결합하여 더욱 정확하고 사실적인 응답을 생성하는 방식입니다. ChromaDB는 RAG 워크플로우에서 중요한 역할을 담당합니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;문서 임베딩 및 저장&lt;/strong&gt;: 외부 데이터를 임베딩으로 변환하여 ChromaDB에 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;질문 임베딩 및 검색&lt;/strong&gt;: 사용자의 질문을 임베딩으로 변환하고, ChromaDB에서 가장 유사한 문서를 검색합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;응답 생성&lt;/strong&gt;: 검색된 문서와 질문을 LLM에 입력하여 맥락에 맞는 답변을 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;예를 들어, 사용자가 특정 주제에 대한 질문을 하면, ChromaDB는 관련 문서를 검색하여 LLM이 보다 정확한 답변을 생성할 수 있도록 지원합니다. 이를 통해 최신 정보나 특정 도메인 지식에 기반한 응답을 제공할 수 있습니다.&lt;/p&gt;
&lt;p&gt;RAG 방식은 특히 다음과 같은 장점을 제공합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최신 정보 반영: 지속적으로 업데이트되는 데이터 활용 가능.&lt;/li&gt;
&lt;li&gt;응답 정확성 향상: LLM의 환각(hallucination) 문제를 줄이고 신뢰성을 높임.&lt;/li&gt;
&lt;li&gt;특정 도메인 특화 가능: 특정 산업이나 주제에 맞춘 챗봇 및 애플리케이션 개발에 적합.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ChromaDB는 이러한 RAG 구현에서 필수적인 역할을 하며, AI 기반 애플리케이션 개발을 한층 더 효과적으로 지원합니다.&lt;/p&gt;
&lt;h2&gt;3. &lt;strong&gt;ChromaDB의 주요 기능&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;임베딩 벡터 저장 및 관리&lt;/h3&gt;
&lt;p&gt;ChromaDB는 임베딩 벡터를 효율적으로 저장하고 관리하는 데 최적화된 데이터베이스입니다. 벡터 데이터는 고차원 공간에서 유사성을 계산하기 위해 사용되며, ChromaDB는 이를 저장할 때 &lt;strong&gt;고급 인덱싱 기술&lt;/strong&gt;과 &lt;strong&gt;압축 알고리즘&lt;/strong&gt;을 활용하여 공간 효율성과 검색 속도를 극대화합니다. 이러한 저장 구조는 대규모 데이터셋에서도 안정적이고 빠른 검색을 가능하게 합니다.&lt;/p&gt;
&lt;p&gt;또한, ChromaDB는 메타데이터와 벡터를 함께 저장할 수 있어, 단순한 벡터 검색뿐만 아니라 메타데이터 기반의 복합적인 질의도 지원합니다. 예를 들어, 영화 데이터베이스에서 영화 설명을 임베딩으로 변환해 저장하고, 제목, 장르 등의 메타데이터를 함께 관리할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;유사성 검색 및 메타데이터 기반 필터링&lt;/h3&gt;
&lt;p&gt;ChromaDB는 &lt;strong&gt;유사성 검색&lt;/strong&gt;을 통해 입력된 임베딩과 가장 유사한 데이터를 빠르게 찾아냅니다. 이를 위해 &lt;strong&gt;코사인 유사도&lt;/strong&gt;, &lt;strong&gt;유클리드 거리&lt;/strong&gt; 등의 계산 방식을 사용하며, 대규모 데이터셋에서도 효율성을 유지하기 위해 &lt;strong&gt;Locality-Sensitive Hashing(LSH)&lt;/strong&gt;와 같은 최적화 알고리즘을 활용합니다.&lt;/p&gt;
&lt;p&gt;또한, ChromaDB는 메타데이터 기반 필터링 기능을 제공하여 특정 조건에 맞는 결과만 반환할 수 있습니다. 예를 들어, 특정 날짜 범위나 카테고리에 해당하는 데이터만 검색하도록 필터를 설정할 수 있습니다. 이 기능은 대규모 데이터셋에서 원하는 정보를 더욱 정확히 추출하는 데 유용합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;컬렉션(Collection)의 생성, 추가, 삭제&lt;/h3&gt;
&lt;p&gt;ChromaDB는 데이터를 관리하기 위한 기본 단위로 &lt;strong&gt;컬렉션(Collection)&lt;/strong&gt;을 사용합니다. 컬렉션은 임베딩 벡터와 해당 메타데이터를 포함하는 컨테이너 역할을 하며, 다음과 같은 작업이 가능합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;생성(Create)&lt;/strong&gt;: 새로운 컬렉션을 생성하여 데이터를 추가할 준비를 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;추가(Add)&lt;/strong&gt;: 임베딩 벡터와 메타데이터를 컬렉션에 추가합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수정(Modify)&lt;/strong&gt;: 기존 컬렉션의 이름이나 내용을 수정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;삭제(Delete)&lt;/strong&gt;: 더 이상 필요하지 않은 컬렉션을 삭제하여 공간을 확보합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어, Python API를 통해 &lt;code&gt;create_collection&lt;/code&gt; 함수로 새로운 컬렉션을 만들고, &lt;code&gt;add&lt;/code&gt; 함수로 데이터를 추가하며, &lt;code&gt;delete_collection&lt;/code&gt; 함수로 컬렉션을 삭제할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;데이터 갱신 및 확장성 지원&lt;/h3&gt;
&lt;p&gt;ChromaDB는 지속적으로 데이터를 갱신하고 확장할 수 있는 기능을 제공합니다. 새로운 데이터를 추가하거나 기존 데이터를 업데이트하는 작업이 간단하며, 멀티스레드 환경에서도 안전하게 작동하도록 설계되었습니다. &lt;/p&gt;
&lt;p&gt;또한, ChromaDB는 확장성을 염두에 둔 아키텍처를 채택하여 로컬 환경뿐만 아니라 클라우드 기반의 대규모 분산 시스템에서도 활용 가능합니다. SQLite 기반의 통합 스토리지 엔진은 메모리 사용량을 줄이고 성능을 향상시키며, 클라이언트/서버 모델로 쉽게 전환할 수 있는 유연성을 제공합니다.&lt;/p&gt;
&lt;p&gt;이러한 기능 덕분에 ChromaDB는 AI 애플리케이션 개발에서 대규모 데이터 관리와 실시간 검색 요구를 효과적으로 충족시킬 수 있습니다.&lt;/p&gt;
&lt;h2&gt;4. &lt;strong&gt;ChromaDB 설치 및 시작하기&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;설치 방법 (Python 및 Node.js)&lt;/h3&gt;
&lt;h4&gt;Python 환경에서 설치&lt;/h4&gt;
&lt;p&gt;ChromaDB는 Python 기반으로 설계되었으며, 설치는 매우 간단합니다. 아래 명령어를 사용하여 필요한 패키지를 설치할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install chromadb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;추가적으로 OpenAI API를 사용할 경우, &lt;code&gt;openai&lt;/code&gt; 패키지도 함께 설치할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install openai&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChromaDB는 SQLite 3.35 이상이 필요하며, Python 3.11 이상에서 최적의 성능을 발휘합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Node.js 환경&lt;/h4&gt;
&lt;p&gt;현재 ChromaDB는 주로 Python 환경에서 사용되며, Node.js에 대한 직접적인 지원은 제공되지 않습니다. 하지만 FastAPI와 같은 서버를 통해 ChromaDB를 API 형태로 노출한 후, Node.js 애플리케이션에서 해당 API를 호출하는 방식으로 통합할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;기본 설정 및 서버 실행&lt;/h3&gt;
&lt;p&gt;ChromaDB를 설정하려면 &lt;code&gt;chromadb.Client&lt;/code&gt; 객체를 생성하고 데이터베이스 설정을 지정해야 합니다. 아래는 DuckDB를 백엔드로 사용하는 영구 데이터베이스 설정 예제입니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import chromadb
from chromadb.config import Settings

# 클라이언트 초기화 및 설정
client = chromadb.Client(Settings(
    chroma_db_impl=&amp;quot;duckdb+parquet&amp;quot;,  # DuckDB 사용
    persist_directory=&amp;quot;db/&amp;quot;          # 데이터 저장 디렉토리 지정
))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 실행을 위해 FastAPI와 같은 프레임워크를 사용할 수도 있습니다. 아래는 FastAPI를 사용하여 ChromaDB 서버를 실행하는 예제입니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from chromadb.server.fastapi import FastAPI
from chromadb.config import Settings

settings = Settings(
    chroma_db_impl=&amp;quot;duckdb+parquet&amp;quot;,
    persist_directory=&amp;quot;db/&amp;quot;
)

server = FastAPI(settings)
app = server.app()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후, Dockerfile을 활용하여 서버를 컨테이너화하거나 &lt;code&gt;uvicorn&lt;/code&gt;으로 실행할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uvicorn server:app --host 0.0.0.0 --port 8000&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;간단한 예제 코드로 시작하기&lt;/h3&gt;
&lt;p&gt;아래는 ChromaDB의 기본적인 사용 예제입니다:&lt;/p&gt;
&lt;h4&gt;1. 클라이언트 생성 및 컬렉션 생성&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 클라이언트 생성
client = chromadb.Client()

# 컬렉션 생성 (테이블과 유사)
collection = client.create_collection(name=&amp;quot;example_collection&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 데이터 추가&lt;/h4&gt;
&lt;p&gt;컬렉션에 데이터를 추가하면 Chroma가 자동으로 임베딩을 생성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;collection.add(
    documents=[&amp;quot;This is a test document.&amp;quot;, &amp;quot;Another example text.&amp;quot;],
    metadatas=[{&amp;quot;type&amp;quot;: &amp;quot;test&amp;quot;}, {&amp;quot;type&amp;quot;: &amp;quot;example&amp;quot;}],
    ids=[&amp;quot;doc1&amp;quot;, &amp;quot;doc2&amp;quot;]
)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 데이터 검색 (유사성 검색)&lt;/h4&gt;
&lt;p&gt;입력 텍스트와 유사한 데이터를 검색합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;results = collection.query(
    query_texts=[&amp;quot;test document&amp;quot;],
    n_results=1  # 반환할 결과 개수
)

print(results)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 데이터 갱신 및 삭제&lt;/h4&gt;
&lt;p&gt;데이터 갱신은 새 데이터를 추가하거나 기존 데이터를 삭제하는 방식으로 이루어집니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 데이터 삭제
collection.delete(ids=[&amp;quot;doc1&amp;quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 간단한 워크플로우를 통해 ChromaDB의 주요 기능을 빠르게 이해하고 활용할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;5. &lt;strong&gt;ChromaDB 활용법&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;텍스트, 이미지, 오디오 데이터 처리&lt;/h3&gt;
&lt;p&gt;ChromaDB는 텍스트, 이미지, 오디오와 같은 비정형 데이터를 벡터 임베딩 형태로 저장하고 검색할 수 있습니다. 이를 통해 다양한 데이터 유형을 처리하며, 다음과 같은 방식으로 활용됩니다:&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;: 이미지 데이터를 텍스트로 변환(LLaVA 등 멀티모달 모델 활용)하거나 직접 임베딩하여 ChromaDB에 저장하고 검색할 수 있습니다. 이는 이미지 분류나 검색 시스템에 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;오디오 데이터&lt;/strong&gt;: 음성 데이터를 텍스트로 변환한 후 임베딩하거나, 직접 오디오 임베딩 모델을 사용해 저장 및 검색할 수 있습니다. 이는 음성 인식 및 검색 애플리케이션에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;자연어 검색 및 의미론적 검색 엔진 구축&lt;/h3&gt;
&lt;p&gt;ChromaDB는 자연어 검색과 의미론적 검색 엔진을 구축하는 데 최적화되어 있습니다. 단순 키워드 매칭이 아닌 &lt;strong&gt;의미론적 유사성&lt;/strong&gt;을 기반으로 관련 데이터를 반환하며, 다음과 같은 방식으로 활용됩니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;임베딩 생성&lt;/strong&gt;: 텍스트 데이터를 임베딩 모델(e.g., OpenAI, Hugging Face)을 사용해 벡터로 변환합니다.&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;p&gt;예를 들어, FAQ 시스템에서 사용자가 &amp;quot;반품 정책은 어떻게 되나요?&amp;quot;라고 물으면, ChromaDB는 비슷한 의미의 답변이 포함된 문서를 반환할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;대규모 언어 모델(LLM)과의 통합&lt;/h3&gt;
&lt;p&gt;ChromaDB는 대규모 언어 모델(LLM)과 쉽게 통합되어 강력한 AI 애플리케이션을 구축할 수 있습니다. 주요 통합 방식은 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;임베딩 생성 및 저장&lt;/strong&gt;: LLM(OpenAI GPT 등)을 사용해 데이터를 임베딩 형태로 변환한 뒤 ChromaDB에 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;질문 응답 시스템&lt;/strong&gt;: LLM과 ChromaDB를 결합해 사용자의 질문에 대해 관련 문서를 검색하고 이를 기반으로 응답을 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LangChain 연동&lt;/strong&gt;: LangChain 프레임워크를 통해 ChromaDB와 LLM 간의 상호작용을 간소화하고 RAG 파이프라인을 쉽게 구현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;RAG(Retrieval-Augmented Generation) 파이프라인 구축 사례&lt;/h3&gt;
&lt;p&gt;RAG는 외부 데이터 소스를 활용해 LLM의 응답 정확도를 높이는 기술로, ChromaDB는 RAG 파이프라인의 핵심 요소로 사용됩니다. 다음은 RAG 파이프라인 구축 과정의 주요 단계입니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터 준비 및 임베딩&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문서를 청크 단위로 분할하고(LangChain의 &lt;code&gt;text-splitter&lt;/code&gt; 등 활용), 이를 임베딩으로 변환하여 ChromaDB에 저장합니다.&lt;/li&gt;
&lt;li&gt;예: OpenAI의 Text Embedding 모델이나 Hugging Face 모델 사용.&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;사용자의 질문을 임베딩으로 변환하고, ChromaDB에서 유사한 문서를 검색합니다.&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;query()&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;ul&gt;
&lt;li&gt;검색된 문서를 컨텍스트로 활용하여 LLM이 최종 응답을 생성합니다.&lt;/li&gt;
&lt;li&gt;예: LangChain의 &lt;code&gt;RetrievalQAChain&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;ul&gt;
&lt;li&gt;PDF 문서를 업로드해 질문에 답변하는 애플리케이션 개발.&lt;/li&gt;
&lt;li&gt;웹 크롤링 데이터를 ChromaDB에 저장 후 질문 응답 시스템 구현.&lt;/li&gt;
&lt;li&gt;멀티모달 데이터를 활용한 이미지 및 텍스트 기반 검색 시스템 구축.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RAG 파이프라인은 최신 정보 반영, 도메인 특화 응답 제공 등에서 강력한 이점을 제공하며, ChromaDB는 이를 구현하는 데 필수적인 역할을 합니다.&lt;/p&gt;
&lt;h2&gt;6. &lt;strong&gt;ChromaDB와 통합 가능한 기술&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;LangChain, LlamaIndex, OpenAI와의 연동&lt;/h3&gt;
&lt;p&gt;ChromaDB는 다양한 기술과 쉽게 통합되어 강력한 AI 및 데이터 처리 워크플로우를 구축할 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LangChain&lt;/strong&gt;: LangChain은 LLM 기반 애플리케이션 개발을 위한 프레임워크로, ChromaDB와의 연동을 통해 &lt;strong&gt;RAG(Retrieval-Augmented Generation)&lt;/strong&gt; 파이프라인을 간단히 구현할 수 있습니다. LangChain은 텍스트 분할, 임베딩 생성, 데이터 검색 등의 작업을 체인으로 연결하여 복잡한 프로세스를 자동화합니다. 예를 들어, ChromaDB를 벡터 저장소로 사용해 문서를 검색하고 LLM에 전달하여 응답을 생성하는 워크플로우를 쉽게 구성할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LlamaIndex&lt;/strong&gt;: LlamaIndex(구 GPT Index)는 비정형 데이터를 LLM과 연결하는 데 특화된 도구입니다. ChromaDB와 통합하면 대량의 문서를 효율적으로 색인화하고 검색할 수 있습니다. 이를 통해 LLM이 외부 데이터 소스를 활용해 보다 정확한 결과를 생성할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenAI&lt;/strong&gt;: OpenAI의 임베딩 모델(e.g., &lt;code&gt;text-embedding-ada-002&lt;/code&gt;)과 ChromaDB를 결합하면 텍스트 데이터를 고품질 벡터로 변환하여 저장하고 검색할 수 있습니다. OpenAI API와 ChromaDB를 함께 사용해 질문 응답 시스템이나 추천 시스템을 구축할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;다양한 임베딩 모델 사용 사례&lt;/h3&gt;
&lt;p&gt;ChromaDB는 기본적으로 다양한 임베딩 모델과 호환되며, 사용자는 애플리케이션 요구에 따라 적합한 모델을 선택할 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face 모델&lt;/strong&gt;: &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;와 같은 경량 모델은 빠르고 효율적인 임베딩 생성을 지원하며, 대규모 데이터셋에서도 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI 임베딩&lt;/strong&gt;: OpenAI의 &lt;code&gt;text-embedding-ada-002&lt;/code&gt;는 높은 정확도를 제공하며, 자연어 처리 애플리케이션에서 자주 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;멀티모달 모델&lt;/strong&gt;: 텍스트 외에 이미지나 오디오 데이터를 처리하려면 CLIP과 같은 멀티모달 임베딩 모델을 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스텀 모델&lt;/strong&gt;: 특정 도메인에 특화된 임베딩이 필요한 경우, 사용자 정의 모델을 적용해 ChromaDB의 컬렉션에 데이터를 저장하고 검색할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;클라우드 환경에서의 활용 가능성&lt;/h3&gt;
&lt;p&gt;ChromaDB는 로컬 환경뿐만 아니라 클라우드 기반의 대규모 시스템에서도 활용 가능합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;클라우드 배포&lt;/strong&gt;: ChromaDB는 Python 기반으로 설계되어 AWS, GCP, Azure 등 주요 클라우드 플랫폼에서 쉽게 배포할 수 있습니다. FastAPI나 Docker를 사용해 API 서버 형태로 배포하면 클라이언트 애플리케이션에서 접근이 용이합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성&lt;/strong&gt;: ChromaDB는 DuckDB+Parquet 백엔드를 사용하여 데이터를 효율적으로 저장하며, 대규모 분산 시스템에서도 확장성을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간 데이터 갱신&lt;/strong&gt;: 클라우드 환경에서 실시간으로 데이터를 추가하거나 갱신하는 작업이 가능하며, 이를 통해 최신 정보를 반영한 검색 결과를 제공할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ChromaDB는 이러한 통합 및 확장성을 통해 AI 애플리케이션 개발에 필수적인 도구로 자리 잡고 있으며, 다양한 기술 스택과의 연계를 통해 더욱 강력한 기능을 제공합니다.&lt;/p&gt;
&lt;h2&gt;7. &lt;strong&gt;ChromaDB의 장점과 한계&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;고속 검색과 낮은 대기 시간&lt;/h3&gt;
&lt;p&gt;ChromaDB는 &lt;strong&gt;HNSW(Hierarchical Navigable Small World) 그래프&lt;/strong&gt; 알고리즘을 활용하여 고차원 벡터 공간에서 매우 빠른 유사성 검색을 제공합니다. 이는 실시간 추천 시스템, 챗봇, 의미론적 검색 등 &lt;strong&gt;낮은 대기 시간&lt;/strong&gt;이 중요한 애플리케이션에 적합합니다. SIMD 최적화와 멀티스레드 쿼리 처리 덕분에 밀리초 단위의 응답 속도를 유지할 수 있으며, 단일 노드 환경에서도 뛰어난 성능을 발휘합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;단순한 API와 프로토타이핑 용이성&lt;/h3&gt;
&lt;p&gt;ChromaDB는 직관적이고 간단한 API를 제공하여 빠르게 프로토타입을 개발할 수 있습니다. Python 기반의 클라이언트 라이브러리는 사용자가 벡터 데이터를 쉽게 저장하고 검색할 수 있도록 설계되었으며, 복잡한 설정 없이도 바로 사용할 수 있습니다. 특히, 자동 임베딩 생성 기능과 컬렉션 중심의 데이터 관리 구조는 초보자부터 전문가까지 모두에게 유용합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;확장성 및 클러스터링 지원&lt;/h3&gt;
&lt;p&gt;ChromaDB는 &lt;strong&gt;단일 노드 기반의 수직 확장(vertical scaling)&lt;/strong&gt;을 지원하며, 다중 CPU 코어와 대용량 RAM을 활용하여 높은 성능을 유지합니다. SQLite와 같은 백엔드를 사용해 테라바이트 크기의 데이터를 처리할 수 있으며, 메모리에 적재된 HNSW 인덱스를 통해 빠른 검색을 보장합니다. 그러나 ChromaDB는 현재 &lt;strong&gt;수평 확장(horizontal scaling)&lt;/strong&gt; 기능이 부족하며, 대규모 분산 환경에서는 Elasticsearch 같은 대안이 더 적합할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;현재 기술적 제약과 개선 방향&lt;/h3&gt;
&lt;p&gt;ChromaDB는 뛰어난 성능에도 불구하고 몇 가지 한계를 가지고 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;단일 노드 제한&lt;/strong&gt;: 현재 ChromaDB는 단일 노드에서만 작동하며, 대규모 데이터셋이나 높은 동시성 요구사항을 가진 애플리케이션에서는 성능 저하가 발생할 수 있습니다. 분산 클러스터링 기능은 아직 지원되지 않으며, 이는 확장성에서 제한 요인으로 작용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동시성 문제&lt;/strong&gt;: HNSW 인덱스는 내부적으로 멀티스레드를 지원하지만, 하나의 인덱스에 대해 동시에 읽기/쓰기 작업을 수행할 수 없습니다. 따라서 높은 동시성 환경에서는 요청 지연(latency)이 증가할 가능성이 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대규모 데이터셋 처리&lt;/strong&gt;: 메모리 기반 HNSW 인덱스 구조로 인해 데이터 크기가 증가하면 삽입 및 쿼리 속도가 선형적으로 느려질 수 있습니다. 이는 특히 7백만 개 이상의 임베딩 데이터를 처리할 때 두드러집니다.&lt;/li&gt;
&lt;/ul&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;li&gt;메모리 효율 개선: HNSW 인덱스의 메모리 사용량 최적화 및 디스크 페이징 성능 향상.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. &lt;strong&gt;고급 활용 예제&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;사용자 정의 임베딩 함수 적용&lt;/h3&gt;
&lt;p&gt;ChromaDB는 기본적으로 다양한 임베딩 모델(OpenAI, Hugging Face 등)과 호환되지만, 특정 도메인에 특화된 모델을 사용하거나 사용자 정의 임베딩 함수를 적용할 수도 있습니다. 이를 통해 데이터의 특성과 애플리케이션 요구에 맞는 최적화된 검색 성능을 구현할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;예제: 사용자 정의 임베딩 함수 사용&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import chromadb

# 사용자 정의 임베딩 함수
def custom_embedding_function(texts):
    # 텍스트를 벡터로 변환하는 로직 (예: 사전 학습된 모델 사용)
    return [[len(text)] for text in texts]  # 간단한 예: 텍스트 길이를 벡터로 반환

# 클라이언트 생성 및 컬렉션 생성
client = chromadb.Client()
collection = client.create_collection(name=&amp;quot;custom_embeddings&amp;quot;, embedding_function=custom_embedding_function)

# 데이터 추가
collection.add(
    documents=[&amp;quot;Hello world!&amp;quot;, &amp;quot;ChromaDB is great!&amp;quot;],
    metadatas=[{&amp;quot;category&amp;quot;: &amp;quot;greeting&amp;quot;}, {&amp;quot;category&amp;quot;: &amp;quot;statement&amp;quot;}],
    ids=[&amp;quot;doc1&amp;quot;, &amp;quot;doc2&amp;quot;]
)

# 검색 실행
results = collection.query(
    query_texts=[&amp;quot;Hi there!&amp;quot;],  # 사용자 정의 함수로 임베딩 생성
    n_results=1
)
print(results)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방식은 특정 도메인(예: 의료, 법률)에서 최적화된 임베딩 모델을 활용하거나, 기존 모델을 커스터마이징할 때 유용합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;메타데이터 기반 고급 검색 구현&lt;/h3&gt;
&lt;p&gt;ChromaDB는 단순한 벡터 유사성 검색뿐만 아니라 메타데이터 필터링을 결합한 고급 검색 기능을 제공합니다. 이를 통해 특정 조건에 맞는 데이터를 더욱 정교하게 검색할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;예제: 메타데이터 필터링 적용&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 데이터 추가 (메타데이터 포함)
collection.add(
    documents=[&amp;quot;Document about AI.&amp;quot;, &amp;quot;Another document about ML.&amp;quot;],
    metadatas=[{&amp;quot;topic&amp;quot;: &amp;quot;AI&amp;quot;, &amp;quot;year&amp;quot;: 2023}, {&amp;quot;topic&amp;quot;: &amp;quot;ML&amp;quot;, &amp;quot;year&amp;quot;: 2024}],
    ids=[&amp;quot;doc1&amp;quot;, &amp;quot;doc2&amp;quot;]
)

# 메타데이터 필터링을 포함한 검색
results = collection.query(
    query_texts=[&amp;quot;Artificial Intelligence&amp;quot;],
    n_results=1,
    where={&amp;quot;topic&amp;quot;: &amp;quot;AI&amp;quot;, &amp;quot;year&amp;quot;: {&amp;quot;$gte&amp;quot;: 2023}}  # 조건: topic이 AI이고 year &amp;gt;= 2023
)
print(results)&lt;/code&gt;&lt;/pre&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;hr&gt;
&lt;h3&gt;특정 애플리케이션에서의 최적화 사례&lt;/h3&gt;
&lt;h4&gt;사례 1: FAQ 시스템에서의 최적화&lt;/h4&gt;
&lt;p&gt;FAQ 시스템에서는 질문과 답변 데이터를 ChromaDB에 저장하고, 사용자의 질문에 가장 적합한 답변을 반환하도록 최적화할 수 있습니다. 텍스트 분할 및 RAG(Retrieval-Augmented Generation) 파이프라인을 활용하면 더욱 정교한 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.text_splitter import CharacterTextSplitter

# 긴 문서를 청크로 나누기 (텍스트 분할)
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20)
chunks = text_splitter.split_text(&amp;quot;This is a long FAQ document...&amp;quot;)

# 청크를 ChromaDB에 추가
collection.add(
    documents=chunks,
    metadatas=[{&amp;quot;source&amp;quot;: &amp;quot;FAQ&amp;quot;} for _ in chunks],
    ids=[f&amp;quot;chunk_{i}&amp;quot; for i in range(len(chunks))]
)

# 질문에 대한 유사성 검색 및 응답 생성
results = collection.query(query_texts=[&amp;quot;What is the refund policy?&amp;quot;], n_results=3)
print(results)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;사례 2: 추천 시스템에서의 최적화&lt;/h4&gt;
&lt;p&gt;사용자 행동 데이터를 벡터로 변환하여 ChromaDB에 저장하고, 유사 사용자나 아이템을 추천하는 데 활용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 사용자 행동 데이터 추가 (임베딩 생성 후 저장)
user_behavior_embeddings = [[0.1, 0.2], [0.4, 0.5]]  # 가상의 사용자 행동 임베딩
collection.add(
    documents=[&amp;quot;User A behavior&amp;quot;, &amp;quot;User B behavior&amp;quot;],
    metadatas=[{&amp;quot;user_id&amp;quot;: &amp;quot;A&amp;quot;}, {&amp;quot;user_id&amp;quot;: &amp;quot;B&amp;quot;}],
    ids=[&amp;quot;user_a&amp;quot;, &amp;quot;user_b&amp;quot;]
)

# 유사 사용자 검색 (추천 시스템)
results = collection.query(query_embeddings=[[0.15, 0.25]], n_results=1)  # 새로운 사용자의 행동 벡터 입력
print(results)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. &lt;strong&gt;ChromaDB의 실제 활용 사례&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;추천 시스템 구축&lt;/h3&gt;
&lt;p&gt;ChromaDB는 &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;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;p&gt;&lt;strong&gt;장점&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChromaDB의 메타데이터 필터링 기능을 활용해 특정 조건(예: 가격대, 카테고리)에 맞는 추천 가능.&lt;/li&gt;
&lt;li&gt;빠른 벡터 유사성 검색으로 실시간 추천 시스템 구현 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;AI 기반 지식 관리 플랫폼 개발&lt;/h3&gt;
&lt;p&gt;ChromaDB는 AI 기반 지식 관리 시스템(Knowledge Management System)에서 중요한 역할을 합니다. 조직 내 방대한 문서와 데이터를 벡터로 변환하여 효율적으로 저장하고 검색할 수 있습니다.&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;strong&gt;지식 검색 및 공유&lt;/strong&gt;: 조직 내 문서, 보고서 등을 임베딩하여 저장하고, 직원들이 자연어 질의를 통해 관련 문서를 검색할 수 있도록 지원합니다. 예를 들어, 특정 프로젝트에 대한 정보를 검색하면 관련 보고서와 메모를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG 파이프라인&lt;/strong&gt;: Retrieval-Augmented Generation(RAG)을 통해 생성형 AI 모델과 통합하여 더욱 정확한 답변을 제공합니다. 예를 들어, 기술 지원 시스템에서 고객의 질문에 대해 관련 문서를 검색하고 이를 기반으로 답변을 생성합니다.&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;ChromaDB의 고속 검색 기능으로 대규모 데이터셋에서도 빠르게 결과를 반환.&lt;/li&gt;
&lt;li&gt;메타데이터를 활용해 특정 부서나 프로젝트와 관련된 문서만 필터링 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;이미지 및 비디오 검색 시스템 구현&lt;/h3&gt;
&lt;p&gt;ChromaDB는 이미지 및 비디오 데이터의 임베딩을 저장하고 유사성 검색을 통해 강력한 멀티모달 검색 엔진을 구축할 수 있습니다.&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;strong&gt;이미지 검색 엔진&lt;/strong&gt;: CLIP 모델과 ChromaDB를 결합하여 텍스트 기반 이미지 검색 엔진을 구축할 수 있습니다. 예를 들어, 사용자가 &amp;quot;빨간 스포츠카&amp;quot;라고 입력하면 해당 키워드와 가장 유사한 이미지를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;얼굴 인식 및 검색&lt;/strong&gt;: 얼굴 데이터셋(LFW 등)을 임베딩하여 저장하고, 특정 얼굴과 유사한 이미지를 빠르게 찾는 데 활용됩니다. 이는 보안 시스템이나 사진 관리 애플리케이션에서 유용합니다.&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;CLIP과 같은 멀티모달 모델과 통합해 텍스트-이미지 간 의미론적 매칭 가능.&lt;/li&gt;
&lt;li&gt;대규모 이미지 및 비디오 데이터셋에서도 빠른 쿼리 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;10. &lt;strong&gt;ChromaDB의 미래와 전망&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;벡터 데이터베이스 기술의 발전 방향&lt;/h3&gt;
&lt;p&gt;벡터 데이터베이스는 AI와 데이터 관리의 핵심 기술로 자리 잡고 있으며, 앞으로 다음과 같은 방향으로 발전할 것으로 예상됩니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;분산 아키텍처&lt;/strong&gt;: 현재 대부분의 벡터 데이터베이스(ChromaDB 포함)는 단일 노드 기반으로 설계되어 있습니다. 그러나 대규모 데이터를 처리하고 높은 동시성을 지원하기 위해 분산 클러스터링 기능이 점차 중요해질 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;멀티모달 데이터 통합&lt;/strong&gt;: 텍스트, 이미지, 오디오 등 다양한 데이터 유형을 동시에 관리하고 검색할 수 있는 멀티모달 벡터 데이터베이스가 더욱 주목받을 것입니다. ChromaDB는 현재 텍스트 중심이지만, 멀티모달 데이터 처리를 강화할 가능성이 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 모델과의 긴밀한 통합&lt;/strong&gt;: 벡터 데이터베이스는 AI 모델과 더욱 밀접하게 연동되며, 실시간 임베딩 생성 및 검색을 통해 지능형 애플리케이션의 성능을 향상시킬 것입니다. 예를 들어, LLM과의 실시간 통합이 더욱 원활해질 전망입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동화된 데이터 관리&lt;/strong&gt;: 벡터 데이터의 지속적인 업데이트와 최적화를 자동으로 수행하는 기능이 추가될 가능성이 높습니다. 이는 특히 대규모 데이터셋에서 효율성을 극대화하는 데 기여할 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;AI와 데이터 관리 패러다임에서 ChromaDB의 역할&lt;/h3&gt;
&lt;p&gt;ChromaDB는 AI 중심 애플리케이션에서 중요한 역할을 담당하며, 다음과 같은 방식으로 데이터 관리 패러다임에 기여할 수 있습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI 네이티브 데이터베이스로서의 입지 강화&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChromaDB는 AI 애플리케이션에 특화된 데이터베이스로, RAG(Retrieval-Augmented Generation)와 같은 워크플로우에서 핵심적인 역할을 합니다. 이는 LLM 기반 애플리케이션에서 신뢰성과 정확성을 높이는 데 기여합니다.&lt;/li&gt;
&lt;li&gt;특히 LangChain, OpenAI 등과의 통합을 통해 사용자가 복잡한 검색 및 생성 작업을 간단히 구현할 수 있도록 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터 중심 AI 개발 지원&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChromaDB는 비정형 데이터를 효율적으로 저장하고 검색할 수 있는 기능을 제공하여 AI 모델 훈련 및 추론 과정에서 필수적인 도구로 자리 잡고 있습니다. 이는 추천 시스템, 자연어 처리, 이미지 검색 등 다양한 응용 분야에 활용됩니다.&lt;/li&gt;
&lt;li&gt;또한, 클라우드 환경에서 확장성을 제공하여 대규모 AI 프로젝트에서도 안정적으로 작동할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI 기술 발전과 동반 성장&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성형 AI와 벡터 검색 기술의 결합은 새로운 애플리케이션 가능성을 열어줍니다. 예를 들어, ChromaDB는 LLM 기반 챗봇이나 지식 관리 플랫폼에서 외부 데이터를 효율적으로 활용하도록 돕습니다.&lt;/li&gt;
&lt;li&gt;앞으로 ChromaDB는 더 많은 AI 프레임워크 및 플랫폼과 통합되어 다양한 산업 분야에서 활용될 가능성이 큽니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;11. &lt;strong&gt;결론&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;ChromaDB가 제공하는 가치와 가능성 요약&lt;/h3&gt;
&lt;p&gt;ChromaDB는 &lt;strong&gt;AI 중심 애플리케이션&lt;/strong&gt;을 위한 강력한 벡터 데이터베이스로, 다음과 같은 가치를 제공합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;효율적인 벡터 데이터 관리&lt;/strong&gt;: 텍스트, 이미지, 오디오 등 비정형 데이터를 벡터 임베딩 형태로 저장하고, 고속 유사성 검색을 통해 AI 모델의 성능을 극대화합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;간단한 API와 확장성&lt;/strong&gt;: 직관적인 Python API를 제공하여 빠르게 프로토타입을 개발할 수 있으며, 로컬 및 클라우드 환경에서 유연하게 확장 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다양한 활용 가능성&lt;/strong&gt;: 추천 시스템, 자연어 검색 엔진, 멀티모달 검색 시스템 등 다양한 AI 애플리케이션에서 핵심적인 역할을 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI와의 긴밀한 통합&lt;/strong&gt;: LangChain, OpenAI, LlamaIndex 등 최신 AI 프레임워크와 쉽게 연동되어 RAG(Retrieval-Augmented Generation) 같은 고급 워크플로우를 구현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ChromaDB는 특히 대규모 언어 모델(LLM)의 한계를 보완하고, 데이터 중심 AI 애플리케이션 개발을 가속화하는 데 중요한 도구로 자리 잡고 있습니다. 앞으로 분산 아키텍처와 멀티모달 데이터 지원이 강화되면 더욱 강력한 솔루션으로 발전할 가능성이 큽니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;독자에게 전하는 메시지 및 추가 학습 자료 안내&lt;/h3&gt;
&lt;p&gt;ChromaDB는 AI 기술의 발전과 함께 빠르게 성장하고 있는 벡터 데이터베이스 생태계의 핵심 도구입니다. 이를 활용하면 단순한 데이터 저장소를 넘어, AI 모델과 결합된 지능형 애플리케이션을 구축할 수 있습니다. 특히, 텍스트 기반 검색부터 멀티모달 검색까지 다양한 요구를 충족시킬 수 있는 유연성과 확장성을 제공합니다.&lt;/p&gt;
&lt;h4&gt;추가 학습 자료:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;공식 문서&lt;/strong&gt;: ChromaDB의 설치 방법, API 사용법, 고급 기능 등을 학습하려면 공식 문서를 참조하세요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LangChain 및 OpenAI 통합 가이드&lt;/strong&gt;: RAG 파이프라인 구현 사례를 학습하며 ChromaDB와 LLM의 통합 방법을 익힐 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;벡터 데이터베이스 개념 이해&lt;/strong&gt;: 벡터 임베딩, 유사성 검색 알고리즘(HNSW 등)에 대한 기초 지식을 쌓으면 ChromaDB 활용에 큰 도움이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커뮤니티 및 GitHub&lt;/strong&gt;: 최신 업데이트와 실무 사례를 확인하고 커뮤니티에서 질문과 답변을 공유하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ChromaDB는 AI 혁신의 중심에서 데이터 관리와 활용의 새로운 가능성을 열어주는 도구입니다. 이를 통해 독자 여러분도 더욱 효율적이고 강력한 AI 기반 솔루션을 개발할 수 있기를 바랍니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/448</guid>
      <comments>https://syaku.tistory.com/448#entry448comment</comments>
      <pubDate>Wed, 11 Dec 2024 21:55:11 +0900</pubDate>
    </item>
    <item>
      <title>LangChain의 모든 것: 체인과 에이전트로 확장하는 AI 기술</title>
      <link>https://syaku.tistory.com/447</link>
      <description>&lt;h2&gt;1. LangChain 소개&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;LangChain이란 무엇인가?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 대규모 언어 모델(LLM)을 기반으로 애플리케이션을 구축하기 위한 오픈소스 프레임워크입니다. 이 프레임워크는 LLM의 기능을 극대화하여 챗봇, 질의응답 시스템, 자동 요약 등 다양한 애플리케이션을 쉽게 개발할 수 있도록 돕습니다. LangChain은 단순히 언어 모델 API를 호출하는 것을 넘어, 외부 데이터와의 통합, 타 시스템과의 상호작용, 그리고 복잡한 작업 흐름을 처리하는 도구를 제공합니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;등장 배경과 필요성&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 2022년 10월, 해리슨 체이스(Harrison Chase)에 의해 개발되었습니다. 기존 LLM 기반 애플리케이션 개발 과정에서 데이터 통합 및 복잡한 시퀀스 관리의 어려움을 해결하고자 설계되었습니다. 특히, 다음과 같은 요구사항에서 필요성이 대두되었습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;특정 도메인에 특화된 데이터를 추가 학습시키려는 요구&lt;/li&gt;
&lt;li&gt;실시간 데이터 검색 및 활용&lt;/li&gt;
&lt;li&gt;외부 서비스와의 연동&lt;/li&gt;
&lt;li&gt;사용자 맞춤형 시나리오 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangChain은 이러한 문제를 해결하며, LLM의 잠재력을 극대화하여 다양한 비즈니스 요구를 충족시키는 데 중점을 둡니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;주요 특징 및 장점&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 다음과 같은 주요 특징과 장점을 제공합니다:&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;: 데이터베이스, API, 파일 시스템 등 여러 소스에서 데이터를 가져와 LLM과 통합할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;체인 구조&lt;/strong&gt;: 여러 구성 요소를 연결하여 복잡한 작업 흐름을 간단히 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성&lt;/strong&gt;: Python 및 TypeScript로 제공되며 다양한 LLM과 쉽게 통합 가능합니다.&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;p&gt;LangChain은 단순히 텍스트 생성에 국한되지 않고, 실시간 데이터와 상호작용하거나 특정 사용 사례에 맞춘 맞춤형 응답을 제공하는 데 강력한 도구로 자리 잡고 있습니다.&lt;/p&gt;
&lt;h2&gt;2. LangChain의 작동 원리&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;LangChain의 기본 구조와 원리&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 대규모 언어 모델(LLM)을 활용하여 복잡한 작업을 단계적으로 처리할 수 있도록 설계된 프레임워크입니다. 주요 구성 요소는 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;모델 (Models)&lt;/strong&gt;: OpenAI GPT, Hugging Face 모델 등 다양한 LLM과 통합하여 텍스트 생성, 분석 등의 작업을 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프롬프트 템플릿 (Prompt Templates)&lt;/strong&gt;: 사용자 입력을 모델이 이해할 수 있는 형식으로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;체인 (Chains)&lt;/strong&gt;: 여러 작업 단계를 연결하여 복잡한 워크플로우를 구성합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메모리 (Memory)&lt;/strong&gt;: 이전 대화나 작업의 컨텍스트를 저장하고 활용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에이전트와 도구 (Agents &amp;amp; Tools)&lt;/strong&gt;: 외부 데이터 소스나 API와 상호작용하여 실시간 데이터를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangChain은 이러한 구성 요소를 조합하여 사용자의 입력을 처리하고, 모델의 출력을 후처리하는 과정을 체계적으로 관리합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;체인(Chains)과 링크(Links)의 개념&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;체인(Chains)&lt;/strong&gt;: 여러 단계의 작업을 순차적으로 연결한 프로세스를 의미합니다. 예를 들어, 사용자의 입력을 프롬프트로 변환하고, 이를 LLM에 전달한 뒤 출력 결과를 파싱하는 일련의 과정이 체인으로 구성됩니다. 체인은 LangChain Expression Language(LCEL)를 사용해 간단히 정의할 수 있습니다. LCEL은 Unix 파이프라인처럼 각 단계를 연결하며, 복잡한 작업도 쉽게 관리할 수 있게 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예시: &lt;code&gt;prompt | model | output_parser&lt;/code&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;링크(Links)&lt;/strong&gt;: 체인을 구성하는 각각의 작업 단계입니다. 링크는 특정 작업을 수행하며, 여러 링크가 모여 체인을 형성합니다. 예를 들어, 데이터 전처리, 모델 호출, 후처리 등이 각각 링크에 해당합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;체인의 장점은 복잡한 프로세스를 작은 단계로 나누어 관리 가능하다는 점이며, 이를 통해 재사용성과 확장성을 높일 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;데이터 소스와의 통합 방식&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 다양한 데이터 소스와 통합하여 실시간 데이터 처리를 지원합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스&lt;/strong&gt;: SQL 및 NoSQL 데이터베이스와 연결해 데이터를 검색하고 활용할 수 있습니다. 예를 들어, SQLite 데이터베이스에서 데이터를 가져와 LLM에 전달하는 체인을 구성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API&lt;/strong&gt;: 외부 API와 통합해 실시간 데이터를 가져오거나 처리할 수 있습니다. 예를 들어, 웹 크롤링 API를 통해 최신 정보를 수집하여 모델에 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파일 시스템&lt;/strong&gt;: 로컬 파일이나 클라우드 스토리지에서 데이터를 읽고 쓰는 작업도 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다중 데이터 소스 통합&lt;/strong&gt;: 여러 데이터 소스를 동시에 활용해 복잡한 애플리케이션을 구축할 수 있습니다. 예를 들어, 사용자 데이터를 데이터베이스에서 가져오고 실시간 시장 데이터를 API에서 받아 결합하여 처리하는 체인을 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 통합 기능은 LangChain이 단순한 언어 모델 호출 도구를 넘어 실질적인 비즈니스 애플리케이션 개발에 적합한 플랫폼으로 자리 잡게 합니다.&lt;/p&gt;
&lt;h2&gt;3. &lt;strong&gt;LangChain의 주요 구성 요소&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;모델 I/O&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain의 모델 I/O는 언어 모델과 상호작용하는 인터페이스로, 프롬프트 관리와 출력 파싱을 포함합니다.&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;: LangChain은 GPT, Bard, PaLM 등 다양한 LLM과 통합되어 간단한 API 호출로 모델을 사용할 수 있습니다. 출력 파싱은 모델의 응답을 원하는 형식으로 변환하며, OutputFixingParser와 같은 도구를 활용해 오류를 수정하거나 재시도할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;데이터 연결(Data Connection)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 데이터를 로드하고 변환하며 검색하는 기능을 제공하여 애플리케이션과 데이터 소스를 통합합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문서 로더&lt;/strong&gt;: PDF, CSV, 웹페이지 등 다양한 형식의 데이터를 로드합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;변환기(Transformers)&lt;/strong&gt;: 데이터를 전처리하거나 변환하여 LLM이 처리하기 적합한 형태로 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;벡터 스토어(Vector Stores)&lt;/strong&gt;: 문서 임베딩을 저장하고, 유사도 검색을 통해 관련 정보를 효율적으로 검색합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검색기(Retrievers)&lt;/strong&gt;: 벡터 스토어와 통합하여 사용자가 입력한 질의에 적합한 문서를 검색합니다. 이는 RAG(Retrieval-Augmented Generation) 시스템 구현에 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;체인(Chains)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;체인은 여러 작업 단계를 연결하여 복잡한 워크플로우를 구성하는 LangChain의 핵심 요소입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;호출 시퀀스 구성&lt;/strong&gt;: 체인은 사용자의 입력에서 시작해 언어 모델 호출, 데이터 처리, 출력 생성까지의 과정을 순차적으로 연결합니다. SequentialChain(순차적 체인)과 RouterChain(분기 체인) 등이 대표적입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;복잡한 워크플로우 지원&lt;/strong&gt;: 예를 들어, SQL 쿼리를 생성하고 실행 결과를 분석하거나 요약하는 작업을 체인으로 구성할 수 있습니다. 이는 다양한 입출력 처리를 효율적으로 관리할 수 있도록 돕습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;에이전트(Agents)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;에이전트는 작업 순서를 동적으로 결정하고 필요한 도구를 선택하여 실행하는 고급 구성 요소입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;작업 순서 결정&lt;/strong&gt;: 에이전트는 언어 모델을 추론 엔진으로 사용해 어떤 작업을 수행할지 결정합니다. 예를 들어, 사용자의 요청에 따라 특정 API 호출이나 데이터 검색 작업을 자동으로 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도구 활용&lt;/strong&gt;: 에이전트는 외부 도구(API, 계산기 등)를 통합하여 복잡한 작업을 수행할 수 있습니다. 이를 통해 정적 체인보다 더 유연하게 동작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;메모리(Memory)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain의 메모리는 대화 상태를 유지하고 맥락 인식을 가능하게 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;대화 상태 유지&lt;/strong&gt;: 메모리는 이전 대화 내용을 저장하여 컨텍스트를 유지하며, 이를 기반으로 LLM 응답의 일관성을 높입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다양한 메모리 타입&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;ConversationBufferMemory: 대화 기록 전체를 저장.&lt;/li&gt;
&lt;li&gt;ConversationBufferWindowMemory: 최근 K개의 대화만 저장.&lt;/li&gt;
&lt;li&gt;ConversationSummaryMemory: 대화 내용을 요약하여 저장.&lt;/li&gt;
&lt;li&gt;외부 DB와 연동(SQlite, Redis 등)을 통해 분산 환경에서도 사용 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;콜백(Callbacks)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;콜백은 LangChain 애플리케이션 실행 중간 단계에서 정보를 기록하거나 스트리밍하는 데 사용됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;중간 단계 기록&lt;/strong&gt;: LLM 호출 시작/종료 시점이나 에러 발생 시 로그를 남길 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스트리밍 지원&lt;/strong&gt;: StreamingStdOutCallbackHandler와 같은 콜백으로 실시간 응답 스트리밍이 가능합니다. 이는 사용자 경험을 개선하고 디버깅에도 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangChain은 이러한 구성 요소들을 조합하여 강력하고 유연한 LLM 기반 애플리케이션 개발을 지원합니다.&lt;/p&gt;
&lt;h2&gt;4. &lt;strong&gt;LangChain의 활용 사례&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;챗봇 및 Q&amp;amp;A 시스템 개발&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 자연스러운 대화형 챗봇과 질문 답변(Q&amp;amp;A) 시스템을 개발하는 데 효과적으로 사용됩니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;챗봇&lt;/strong&gt;: LangChain을 활용하면 고객 지원, FAQ 응답, 개인 비서 역할을 수행하는 챗봇을 구축할 수 있습니다. 예를 들어, OpenAI GPT와 LangChain의 메모리 기능을 결합해 대화 맥락을 유지하며 사용자와 지속적으로 상호작용할 수 있는 고급 챗봇을 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q&amp;amp;A 시스템&lt;/strong&gt;: LangChain의 RAG(Retrieval-Augmented Generation) 기능을 사용해 외부 데이터베이스나 문서에서 정보를 검색하고 이를 기반으로 정확한 답변을 생성할 수 있습니다. 이는 특정 도메인(예: 법률, 의료 등)에 특화된 질문 답변 시스템 개발에 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;검색 증강 생성(RAG) 애플리케이션&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;RAG는 LangChain의 대표적인 활용 사례 중 하나로, 모델의 기존 지식 외에 외부 데이터 소스를 활용해 더 풍부하고 신뢰도 높은 응답을 생성합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;실시간 데이터 검색&lt;/strong&gt;: 다양한 문서 로더와 벡터 스토어를 통해 데이터를 임베딩하고, 사용자의 질의에 적합한 정보를 검색하여 LLM 응답에 통합합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;적용 사례&lt;/strong&gt;: 기업은 RAG를 통해 내부 문서 검색 시스템, 연구 보고서 요약, 또는 실시간 시장 데이터 분석 애플리케이션을 구축할 수 있습니다. 예를 들어, 의료 애플리케이션에서는 환자의 기록과 최신 연구 데이터를 결합해 적절한 치료 옵션을 제안할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;실시간 데이터 통합 및 외부 API 연동&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 외부 API와의 연동을 통해 실시간 데이터를 처리하고 활용할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API 통합&lt;/strong&gt;: LangChain은 날씨 정보, 금융 데이터, 또는 웹 크롤링 결과와 같은 실시간 데이터를 가져와 모델 출력에 반영할 수 있습니다. 이를 통해 동적이고 최신 정보 기반의 애플리케이션이 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;적용 사례&lt;/strong&gt;: 여행 계획 앱에서는 사용자 입력에 따라 항공편, 호텔 예약 정보 등을 실시간으로 검색하고 맞춤형 일정을 생성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;맞춤형 추천 시스템과 법률 문서 분석&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 특정 도메인에 맞춘 고급 애플리케이션 개발에도 활용됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추천 시스템&lt;/strong&gt;: LangChain은 사용자 선호도와 과거 데이터를 분석해 개인화된 추천을 제공합니다. 예를 들어, 음식 레시피 추천 앱에서는 사용자의 식단 요구사항과 재료를 기반으로 맞춤형 레시피를 제안할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;법률 문서 분석&lt;/strong&gt;: LangChain은 복잡한 법률 문서를 요약하거나 특정 정보를 추출하여 법률 전문가가 더 효율적으로 작업할 수 있도록 돕습니다. 이를 통해 계약 검토, 판례 검색 등의 작업이 자동화됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangChain은 이처럼 다양한 산업과 도메인에서 활용 가능하며, 사용자 요구에 맞는 맞춤형 AI 솔루션 개발을 지원하는 강력한 도구입니다.&lt;/p&gt;
&lt;h2&gt;5. &lt;strong&gt;LangChain의 장점과 한계&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;유연성과 확장성&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 높은 유연성과 확장성을 제공하여 다양한 애플리케이션 개발에 적합합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;유연성&lt;/strong&gt;: LangChain은 프롬프트 템플릿, 체인, 에이전트, 메모리 등 모듈화된 구성 요소를 통해 다양한 워크플로우를 설계할 수 있도록 지원합니다. 이를 통해 간단한 챗봇부터 복잡한 데이터 통합 애플리케이션까지 폭넓게 활용 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성&lt;/strong&gt;: 오픈소스 기반으로 제공되며, 개발자가 필요에 따라 기능을 확장하거나 커스터마이징할 수 있습니다. 특히, 새로운 데이터 소스나 도구를 쉽게 통합할 수 있어 특정 비즈니스 요구 사항에 맞춘 솔루션 구축이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;다양한 데이터 소스와의 통합 가능성&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 외부 데이터 소스와의 통합에 강점을 가지고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터 접근성&lt;/strong&gt;: LangChain은 문서 로더, 벡터 스토어, 검색기 등을 통해 다양한 데이터 소스를 연결하고 처리할 수 있습니다. 예를 들어, Google Drive, MongoDB, API 등을 활용하여 실시간 데이터를 검색하고 이를 모델 응답에 반영할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAG 워크플로우 지원&lt;/strong&gt;: 검색 증강 생성(RAG) 기능을 통해 외부 데이터를 기반으로 신뢰도 높은 응답을 생성하며, 이는 정보 검색 및 요약 작업에서 특히 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;현재 기술적 한계와 개선 방향&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 강력한 도구임에도 불구하고 몇 가지 한계가 존재합니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;기술적 한계&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;복잡한 추상화&lt;/strong&gt;: LangChain의 높은 수준의 추상화는 초보자에게 학습 곡선을 증가시키며, 고급 사용자가 세부적인 조정을 하거나 특정 요구 사항을 구현하는 데 어려움을 겪을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비효율적인 토큰 사용&lt;/strong&gt;: API 호출에서 불필요하게 많은 토큰을 사용할 가능성이 있어 비용이 증가할 수 있습니다. 이는 더 효율적인 프롬프트 설계나 직접적인 코드 구현으로 대체될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문서화 부족&lt;/strong&gt;: 일부 사용자들은 LangChain의 문서가 부족하거나 불명확하다고 느껴 사용 과정에서 혼란을 겪고 있습니다. 특히 기본 매개변수나 설정에 대한 설명이 부족하다는 점이 지적됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성능 및 안정성 문제&lt;/strong&gt;: 복잡한 애플리케이션에서 LangChain은 종종 유지 관리가 어렵거나 비효율적인 것으로 나타났으며, 대규모 프로덕션 환경에서는 신뢰성이 떨어질 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;strong&gt;개선 방향&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문서 개선&lt;/strong&gt;: 보다 명확하고 상세한 문서를 제공하여 사용자들이 LangChain의 기능과 한계를 더 잘 이해하도록 지원해야 합니다.&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;hr&gt;
&lt;p&gt;LangChain은 유연성과 확장성을 갖춘 강력한 도구로 다양한 애플리케이션 개발에 적합하지만, 복잡성과 성능 문제를 해결하기 위해 지속적인 개선이 필요합니다. 이를 통해 프로토타입뿐만 아니라 대규모 프로덕션 환경에서도 신뢰할 수 있는 프레임워크로 자리 잡을 수 있을 것입니다.&lt;/p&gt;
&lt;h2&gt;6. &lt;strong&gt;LangChain 설치 및 시작하기&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;설치 방법 (Python 기반)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 Python 환경에서 간단히 설치할 수 있습니다. 아래는 설치 방법입니다:&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;1. Pip로 설치&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;터미널에서 다음 명령어를 실행하여 LangChain을 설치합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install langchain&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;설치가 완료되면, 아래 명령어로 설치된 LangChain 버전을 확인할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -c &amp;quot;import langchain; print(langchain.__version__)&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;2. Conda로 설치&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Conda를 사용하는 경우, 다음 명령어를 실행하여 LangChain을 설치합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;conda install langchain -c conda-forge&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;3. 추가적인 의존성 설치&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangChain은 기본적으로 최소한의 의존성만 설치되므로, 특정 통합 기능(OpenAI, Hugging Face 등)을 사용하려면 별도의 패키지를 추가로 설치해야 합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OpenAI 통합:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install langchain-openai&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-bash&quot;&gt;pip install langchain-community&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;기본 예제: 간단한 체인 구성 및 실행&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain을 활용하여 간단한 체인을 구성하고 실행하는 기본 예제를 살펴보겠습니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;1. 환경 설정&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangChain을 사용하려면 Python 스크립트에서 필요한 모듈을 가져옵니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.llms import OpenAI&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;2. 프롬프트 템플릿 생성&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;프롬프트 템플릿은 모델이 요청을 이해할 수 있도록 입력 데이터를 구조화하는 데 사용됩니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;prompt = PromptTemplate(
    input_variables=[&amp;quot;name&amp;quot;],
    template=&amp;quot;안녕하세요, {name}! 오늘 기분은 어떠신가요?&amp;quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;3. LLM 초기화&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;OpenAI API 키를 사용하여 언어 모델(LLM)을 초기화합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;llm = OpenAI(model=&amp;quot;text-davinci-003&amp;quot;, openai_api_key=&amp;quot;your_openai_api_key&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;4. 체인 구성&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;프롬프트와 LLM을 결합하여 체인을 생성합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;chain = LLMChain(llm=llm, prompt=prompt)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;5. 체인 실행&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;체인을 실행하여 결과를 얻습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;response = chain.run({&amp;quot;name&amp;quot;: &amp;quot;홍길동&amp;quot;})
print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;위 코드를 실행하면 이름(&amp;quot;홍길동&amp;quot;)을 입력 변수로 받아 프롬프트를 생성하고, OpenAI 모델이 응답을 반환합니다. 예를 들어:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;안녕하세요, 홍길동! 오늘 기분은 어떠신가요?&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 간단한 예제를 통해 LangChain의 기본적인 작동 방식을 이해할 수 있으며, 이를 기반으로 더 복잡한 워크플로우를 설계할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;7. &lt;strong&gt;LangChain을 활용한 고급 개발&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;사용자 정의 체인 구성 방법&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 기본 체인 외에 사용자 정의 체인을 설계하여 특정 요구사항에 맞는 워크플로우를 구축할 수 있습니다. 이를 위해 프롬프트 템플릿, LLM, 에이전트를 조합하여 맞춤형 체인을 구성합니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;구성 예제&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;아래는 사용자 정의 체인을 생성하는 방법의 예제입니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain import PromptTemplate, LLMChain
from langchain.llms import OpenAI

# OpenAI LLM 초기화
llm = OpenAI(api_key=&amp;quot;YOUR_API_KEY&amp;quot;)

# 사용자 정의 프롬프트 템플릿
custom_prompt = PromptTemplate(
    input_variables=[&amp;quot;data&amp;quot;],
    template=&amp;quot;Analyze the following data and provide insights: {data}&amp;quot;
)

# 사용자 정의 체인 생성
custom_chain = LLMChain(llm=llm, prompt=custom_prompt)

# 체인 실행
result = custom_chain.run({&amp;quot;data&amp;quot;: &amp;quot;Sales data for Q1 shows a 15% increase in revenue.&amp;quot;})
print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 특정 데이터를 분석하고 인사이트를 제공하는 맞춤형 체인을 구현한 예제입니다. 필요에 따라 프롬프트와 LLM 구성을 변경하여 다양한 작업을 처리할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;에이전트와 툴킷 활용 사례&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain의 에이전트는 작업 순서를 동적으로 결정하고, 다양한 도구(툴킷)를 사용하여 복잡한 작업을 수행할 수 있도록 설계되었습니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;에이전트 구성과 활용&lt;/strong&gt;&lt;/h4&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;: SQL 데이터베이스와 상호작용하거나 DuckDuckGo 검색 도구를 사용해 실시간 정보를 검색하는 등 다양한 툴킷을 활용할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;strong&gt;구성 예제&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.agents import initialize_agent, Tool
from langchain.llms import OpenAI

# LLM 초기화
llm = OpenAI(api_key=&amp;quot;YOUR_API_KEY&amp;quot;)

# 도구 설정 (예: 계산기)
def calculator(expression):
    return eval(expression)

tools = [
    Tool(name=&amp;quot;Calculator&amp;quot;, func=calculator, description=&amp;quot;Performs mathematical calculations.&amp;quot;)
]

# 에이전트 초기화
agent = initialize_agent(tools=tools, llm=llm)

# 에이전트 실행
result = agent.run(&amp;quot;What is 40 raised to the power of 0.43?&amp;quot;)
print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 계산기 도구를 활용하여 수학적 연산을 수행하는 에이전트를 구현한 사례입니다. 이 외에도 데이터베이스 쿼리나 웹 검색 등 다양한 작업에 적용 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;LangServe와 LangSmith를 통한 배포 및 디버깅&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;LangServe: REST API 배포&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangServe는 LangChain 애플리케이션을 REST API로 배포할 수 있는 도구입니다. 이를 통해 구축된 체인을 쉽게 공유하고 배포할 수 있습니다.&lt;/p&gt;
&lt;ol&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;pip install &amp;quot;langserve[all]&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;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langserve import serve_chain
from langchain.chains import SimpleChain

# 간단한 체인 정의
chain = SimpleChain(input_key=&amp;quot;input&amp;quot;, output_key=&amp;quot;output&amp;quot;, func=lambda x: x.upper())

# LangServe로 배포
serve_chain(chain, host=&amp;quot;localhost&amp;quot;, port=8000)&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API 호출&lt;/strong&gt;:&lt;br&gt;배포 후 &lt;code&gt;http://localhost:8000/&lt;/code&gt;에서 REST API로 체인을 호출할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;strong&gt;LangSmith: 디버깅 및 로깅&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangSmith는 LangChain 애플리케이션의 디버깅과 테스트를 위한 도구입니다. 모든 실행 단계에서 로그와 추적 정보를 기록하며, 이를 통해 문제를 식별하고 개선할 수 있습니다.&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;오류 추적 및 성능 모니터링.&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;p&gt;LangSmith의 기본 설정은 자동으로 활성화되며, Python 코드에서 다음과 같이 구성할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langsmith import enable_tracing

# LangSmith 트레이싱 활성화
enable_tracing()&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangServe와 LangSmith를 결합하면 애플리케이션의 배포와 유지보수를 효율적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;LangChain의 고급 기능은 사용자 정의 체인 설계부터 동적 에이전트 활용, 그리고 배포 및 디버깅까지 폭넓은 개발 가능성을 제공합니다. 이를 통해 복잡한 애플리케이션을 효율적으로 구축하고 관리할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;8. &lt;strong&gt;LangChain의 미래 전망&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;AI 애플리케이션 개발에서의 역할&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 AI 애플리케이션 개발에서 점점 더 중요한 역할을 맡고 있습니다. 대규모 언어 모델(LLM)을 활용한 애플리케이션이 복잡해지고 다양해지면서, LangChain은 이를 효율적으로 관리하고 통합할 수 있는 강력한 프레임워크로 자리 잡고 있습니다. 특히, LangChain은 다음과 같은 방식으로 AI 개발의 핵심 도구로 자리매김하고 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;복잡한 워크플로우 지원&lt;/strong&gt;: 체인과 에이전트를 통해 다단계 작업을 간단히 구성하고 실행할 수 있어, 챗봇, Q&amp;amp;A 시스템, 데이터 분석 등 다양한 AI 애플리케이션에 적용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간 데이터 통합&lt;/strong&gt;: 외부 API, 데이터베이스와의 원활한 연결을 통해 실시간 정보를 처리하고 업데이트된 데이터를 기반으로 동작하는 애플리케이션을 구축할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기업 및 스타트업 채택 증가&lt;/strong&gt;: Google, Microsoft, Amazon과 같은 대기업뿐만 아니라 스타트업에서도 LangChain을 채택하여 컨텍스트 인식 애플리케이션을 개발하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangChain은 단순히 언어 모델을 호출하는 도구를 넘어, AI 개발의 구조적 기반을 제공하며 다양한 산업에서 활용 가능성을 넓혀가고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;기술 발전과 새로운 가능성&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 기술 발전과 함께 새로운 가능성을 열어가며, 다음과 같은 방향으로 진화하고 있습니다:&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;1. 향상된 통합 기능&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangChain은 외부 데이터 소스와의 통합이 더욱 강화될 예정입니다. API, 데이터베이스, 클라우드 서비스 등 다양한 소스와 실시간으로 연결하여 더욱 동적인 AI 애플리케이션을 지원할 것입니다. 이는 특히 RAG(Retrieval-Augmented Generation) 시스템이나 실시간 의사결정 시스템에서 큰 이점을 제공합니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;2. 메모리 관리 개선&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;향후 업데이트에서는 메모리 관리 기능이 더욱 강화되어 장기적인 대화나 작업 흐름에서도 컨텍스트를 유지할 수 있는 애플리케이션 개발이 용이해질 것입니다. 예를 들어, 고객 서비스 챗봇이나 개인 비서와 같은 응용 프로그램에서 사용자 경험을 크게 향상시킬 수 있습니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;3. 신기술과의 융합&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangChain은 강화학습(Reinforcement Learning), 머신러닝(ML), 딥러닝(Deep Learning) 등 다른 AI 기술과의 통합 가능성을 확장하고 있습니다. 이를 통해 언어 이해뿐만 아니라 예측 및 학습 기능을 결합한 더 스마트한 AI 시스템 개발이 가능해질 것입니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;4. 모듈화 및 확장성 강화&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;LangChain의 모듈식 아키텍처는 더욱 유연하게 발전하여 기업의 요구 사항에 맞춘 맞춤형 솔루션 설계가 쉬워질 것입니다. 이는 특히 대규모 엔터프라이즈 환경에서 중요한 역할을 할 것으로 예상됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;LangChain의 장기적 영향&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 앞으로도 AI 연구와 개발 방향에 큰 영향을 미칠 것으로 보입니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prompt Engineering 발전&lt;/strong&gt;: LangChain은 프롬프트 설계 및 최적화를 통해 언어 모델의 효율성과 정밀도를 높이고 있으며, 이는 향후 더 복잡한 작업에서도 중요한 역할을 할 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;산업 혁신 촉진&lt;/strong&gt;: LangChain은 의료, 금융, 교육 등 다양한 산업에서 혁신적인 AI 솔루션 구현을 지원하며, 기업들이 경쟁력을 유지할 수 있도록 돕고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 접근성 확대&lt;/strong&gt;: LangChain은 LLM 기반 애플리케이션 개발을 단순화하여 더 많은 기업과 개발자가 AI 기술을 활용할 수 있도록 하고 있습니다. 이는 AI 기술의 민주화를 촉진하는 데 기여할 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;LangChain은 기술 발전과 함께 지속적으로 진화하며 AI 개발의 핵심 도구로 자리 잡고 있습니다. 향상된 통합 기능, 메모리 관리, 그리고 신기술과의 융합 등을 통해 LangChain은 더욱 강력하고 유연한 프레임워크로 발전할 것이며, AI 애플리케이션의 미래를 선도할 잠재력을 가지고 있습니다.&lt;/p&gt;
&lt;h2&gt;9. &lt;strong&gt;결론&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;LangChain이 가져올 변화와 가치&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;LangChain은 대규모 언어 모델(LLM)의 잠재력을 극대화하여 AI 애플리케이션 개발의 새로운 패러다임을 제시합니다. 기존 언어 모델이 단순한 텍스트 생성에 머물렀다면, LangChain은 이를 넘어 실시간 데이터 통합, 복잡한 작업 체계화, 그리고 사용자 맞춤형 솔루션 제공 등 다양한 기능을 통해 AI 기술의 활용 범위를 확장하고 있습니다. &lt;/p&gt;
&lt;p&gt;LangChain의 가장 큰 가치는 다음과 같이 요약할 수 있습니다:&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;: 실시간 데이터 및 외부 소스와의 통합을 지원하여 더욱 정교하고 유용한 AI 애플리케이션을 개발할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;산업 전반의 혁신 촉진&lt;/strong&gt;: 금융, 의료, 교육 등 다양한 산업에서 LangChain은 맞춤형 솔루션을 통해 비즈니스 성과를 향상시키고 사용자 경험을 개선하는 데 기여하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LangChain은 단순히 도구를 넘어, AI 개발자들에게 강력한 프레임워크를 제공하며, AI 기술이 우리의 일상과 업무 방식을 혁신적으로 변화시키는 데 중요한 역할을 하고 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;LangChain은 언어 모델 기반 애플리케이션 개발의 복잡성을 줄이고, 실시간 데이터와 상호작용하며, 사용자 맞춤형 솔루션을 제공하는 강력한 오픈소스 프레임워크입니다. 이 블로그에서는 LangChain의 소개부터 작동 원리, 주요 구성 요소, 활용 사례, 설치 방법, 고급 개발 방법, 그리고 미래 전망까지 다루며 LangChain이 제공하는 가치와 가능성을 상세히 살펴보았습니다.&lt;/p&gt;
&lt;p&gt;LangChain은 AI 기술이 가진 한계를 극복하고 새로운 가능성을 열어주는 도구로서, 앞으로도 지속적인 발전과 혁신이 기대됩니다. 독자 여러분들도 LangChain을 활용해 자신만의 창의적이고 실용적인 AI 애플리케이션을 만들어 보시길 바랍니다. LangChain은 단순히 기술 도구가 아니라, 여러분의 아이디어를 현실로 바꾸는 데 필요한 강력한 파트너가 될 것입니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/447</guid>
      <comments>https://syaku.tistory.com/447#entry447comment</comments>
      <pubDate>Wed, 11 Dec 2024 21:37:57 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript `document.querySelector()` 메서드, 그리고 CSS Selector의 이해</title>
      <link>https://syaku.tistory.com/446</link>
      <description>&lt;h1&gt;JavaScript &lt;code&gt;document.querySelector()&lt;/code&gt; 메서드, 그리고 CSS Selector의 이해&lt;/h1&gt;
&lt;p&gt;HTML 문서의 노드를 찾는 데 사용하는 값이 왜 &amp;quot;CSS Selector&amp;quot;라고 불리는가?&lt;/p&gt;
&lt;p&gt;JavaScript를 사용하다 보면 DOM 중심의 작업을 하게 될 때가 많습니다. 특히 특정 요소를 선택하거나 조작하기 위해 사용하는 &lt;code&gt;document.querySelector()&lt;/code&gt; 메서드는 매우 유용한 도구입니다. 그런데, 이 메서드에 전달되는 값이 왜 &amp;quot;CSS Selector&amp;quot;라고 불리는지 궁금하지 않으셨나요?&lt;/p&gt;
&lt;p&gt;오늘은 &lt;code&gt;querySelector()&lt;/code&gt;의 동작 원리와 CSS Selector의 역할, 그리고 이를 활용한 다양한 예제까지 살펴보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;&lt;code&gt;querySelector()&lt;/code&gt;와 CSS Selector&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;querySelector()&lt;/code&gt; 메서드는 HTML 문서 내에서 특정 요소를 검색하여 반환하는 기능을 제공합니다. 여기서 중요한 점은 이 메서드가 &lt;strong&gt;CSS Selector&lt;/strong&gt;라는 문법을 사용한다는 것입니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;CSS Selector란?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;CSS Selector는 원래 CSS(Cascading Style Sheets)에서 &lt;strong&gt;특정 HTML 요소를 선택&lt;/strong&gt;하고 스타일을 적용하기 위해 만들어진 문법입니다.&lt;br&gt;예를 들어:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div &amp;gt; p {
  color: red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 &lt;code&gt;div &amp;gt; p&lt;/code&gt;는 &amp;quot;CSS Selector&amp;quot;로, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 요소의 자식 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 요소만을 선택하여 텍스트 색상을 빨간색으로 설정합니다.&lt;br&gt;이러한 선택 규칙이 JavaScript에서도 동일하게 활용됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;JavaScript에서 CSS Selector 활용&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;JavaScript의 &lt;code&gt;querySelector()&lt;/code&gt;는 CSS Selector를 활용하여 HTML 요소를 선택할 수 있습니다. 이를 통해 간결하고 직관적으로 특정 요소를 검색할 수 있는 강력한 도구를 제공합니다.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;CSS Selector를 사용하는 이유&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTML 탐색에 적합&lt;/strong&gt;&lt;br&gt;CSS Selector는 HTML 계층 구조에서 특정 요소를 한눈에 지정할 수 있는 효율적인 문법입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;브라우저 엔진의 최적화&lt;/strong&gt;&lt;br&gt;CSS Selector는 브라우저가 스타일 적용 시 사용하는 핵심 도구이므로, 이미 최적화된 선택 알고리즘을 JavaScript에서도 활용할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;일관성&lt;/strong&gt;&lt;br&gt;CSS와 JavaScript에서 동일한 문법을 사용하므로, 학습하기 쉽고 협업에 유리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;재사용성&lt;/strong&gt;&lt;br&gt;CSS로 스타일링한 동일한 Selector를 JavaScript에서도 그대로 사용할 수 있어 코드 간 결합이 용이합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;&lt;code&gt;querySelector()&lt;/code&gt;와 CSS Selector 예시&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;다양한 CSS Selector를 &lt;code&gt;querySelector()&lt;/code&gt;에서 사용하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;태그 선택자&lt;/strong&gt;  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const element = document.querySelector(&amp;quot;div&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 문서 내 첫 번째 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 요소를 선택합니다.&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-javascript&quot;&gt;const element = document.querySelector(&amp;quot;.example&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 클래스가 &lt;code&gt;example&lt;/code&gt;인 첫 번째 요소를 선택합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ID 선택자&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const element = document.querySelector(&amp;quot;#example&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ ID가 &lt;code&gt;example&lt;/code&gt;인 요소를 선택합니다.&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-javascript&quot;&gt;const element = document.querySelector(&amp;quot;input[type=&amp;#39;text&amp;#39;]&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;code&gt;type=&amp;quot;text&amp;quot;&lt;/code&gt; 속성을 가진 첫 번째 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 요소를 선택합니다.&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-javascript&quot;&gt;const element = document.querySelector(&amp;quot;div &amp;gt; p&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;의 직속 자식인 첫 번째 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 요소를 선택합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특정 순서를 선택하는 &lt;code&gt;nth-of-type&lt;/code&gt; 사용&lt;/strong&gt;  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const element = document.querySelector(&amp;quot;div:nth-of-type(3)&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ 동일한 부모를 가진 세 번째 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 요소를 선택합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;CSS Selector와 DOM Path의 차이&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;CSS Selector 외에도 DOM을 탐색하는 방법으로 직접적인 DOM Path를 사용할 수도 있습니다. 하지만 두 접근법에는 차이가 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSS Selector&lt;/strong&gt;  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSS 스타일링과 DOM 탐색에서 동일하게 사용 가능.  &lt;/li&gt;
&lt;li&gt;가독성이 뛰어나고 간결함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DOM Path&lt;/strong&gt;  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOM 객체를 계층적으로 탐색하는 방식.  &lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;document.body.children.children&lt;/code&gt;  &lt;/li&gt;
&lt;li&gt;코드가 직관적이지 않을 수 있고, 유지보수가 어려워질 가능성이 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 CSS Selector가 제공하는 직관적인 문법은 개발과 유지보수 측면에서 훨씬 효율적입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;CSS Selector와 HTML 탐색의 관계&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;CSS Selector는 원래 &amp;quot;스타일을 정의하기 위해&amp;quot; 설계된 문법이지만, HTML 구조를 탐색하는 데도 매우 유용합니다. JavaScript에서 &lt;code&gt;querySelector()&lt;/code&gt;가 이 문법을 채택한 이유는 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;표준화&lt;/strong&gt;&lt;br&gt;CSS Selector는 이미 웹 표준의 일부로 자리 잡았으며, 전 세계 개발자들이 익숙한 문법입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;효율성&lt;/strong&gt;&lt;br&gt;브라우저는 CSS Selector 처리에 최적화되어 있으므로, DOM 탐색에서도 이를 사용하는 것이 성능적으로도 합리적입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;결론&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;JavaScript의 &lt;code&gt;querySelector()&lt;/code&gt;가 &amp;quot;CSS Selector&amp;quot;를 사용하는 이유는 단순합니다.&lt;br&gt;CSS Selector는 HTML 구조에서 특정 요소를 선택하는 데 적합한 문법이며, 이를 활용함으로써 DOM 탐색의 효율성과 일관성을 극대화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;특히 CSS Selector는 브라우저 엔진이 이미 최적화하여 사용하는 기술이기 때문에, JavaScript에서도 매우 빠르게 동작합니다. &amp;quot;CSS&amp;quot;라는 이름에 얽매이지 말고, 이를 &lt;strong&gt;HTML 요소 선택을 위한 표준화된 방법&lt;/strong&gt;으로 이해하면 더 쉽게 받아들일 수 있을 것입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Document/querySelector&quot;&gt;MDN Web Docs - Document.querySelector()&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors&quot;&gt;CSS Selectors Reference&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CSS Selector와 &lt;code&gt;querySelector()&lt;/code&gt;를 효과적으로 사용하는 방법을 익히면, 더 빠르고 효율적인 코드 작성을 할 수 있을 것입니다. Happy coding!  &lt;/p&gt;</description>
      <category>개발</category>
      <category>CSS</category>
      <category>HTML</category>
      <category>JavaScript</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/446</guid>
      <comments>https://syaku.tistory.com/446#entry446comment</comments>
      <pubDate>Wed, 11 Dec 2024 12:22:12 +0900</pubDate>
    </item>
    <item>
      <title>미국 국채와 금리의 관계: 기준금리 변화가 미치는 영향</title>
      <link>https://syaku.tistory.com/445</link>
      <description>&lt;p&gt;미국 국채는 세계에서 가장 안전한 투자 자산으로 평가받으며, 글로벌 금융 시장에서 중요한 역할을 합니다. 특히 금리 변화는 미국 국채의 가격과 수익률에 큰 영향을 미치기 때문에, 이를 이해하는 것은 투자 전략 수립에 필수적입니다. 아래에서는 미국 국채의 개념, 특징, 그리고 기준금리와의 연관성을 자세히 설명합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;1. 미국 국채란 무엇인가?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;미국 국채(U.S. Treasury Bonds)는 미국 정부가 발행하는 채권으로, 정부가 자금을 조달하기 위해 투자자들에게 일정 기간 동안 이자를 지급하고 만기 시 원금을 상환하는 금융 상품입니다. 미국 국채는 높은 신뢰성과 유동성으로 인해 전 세계 투자자들에게 매력적인 자산으로 평가받습니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;미국 국채의 주요 종류&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;단기 국채(Treasury Bills)&lt;/strong&gt;: 만기가 1년 이하로, 이자 지급 없이 할인 발행 후 만기에 원금을 지급합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;중기 국채(Treasury Notes)&lt;/strong&gt;: 만기가 2~10년이며, 6개월마다 이자를 지급합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;장기 국채(Treasury Bonds)&lt;/strong&gt;: 만기가 10년 이상(최대 30년)이며, 6개월마다 이자를 지급합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인플레이션 연동 채권(TIPS)&lt;/strong&gt;: 물가상승률에 따라 원금과 이자가 조정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;미국 국채의 특징&lt;/strong&gt;&lt;/h4&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;hr&gt;
&lt;h3&gt;&lt;strong&gt;2. 기준금리란 무엇인가?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;기준금리는 중앙은행(Federal Reserve)이 설정하는 정책 금리로, 금융기관 간 초단기 대출 금리에 직접 영향을 미칩니다. 이는 경제 전반의 금리에 영향을 미치며, 소비와 투자 활동을 조절하는 중요한 도구입니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;기준금리의 역할&lt;/strong&gt;&lt;/h4&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;hr&gt;
&lt;h3&gt;&lt;strong&gt;3. 기준금리와 미국 국채의 관계&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;미국 국채는 기준금리 변화에 따라 가격과 수익률이 크게 변동합니다. 이는 채권의 가격과 수익률(이자율)이 반비례 관계에 있기 때문입니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;(1) 금리 인상 시&lt;/strong&gt;&lt;/h4&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;h4&gt;&lt;strong&gt;(2) 금리 인하 시&lt;/strong&gt;&lt;/h4&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;h4&gt;&lt;strong&gt;(3) 간접적 영향&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;금리가 인하되면 경제 불확실성이 증가하거나 경기 부양 기대감이 커져 안전자산인 미국 국채에 대한 수요가 증가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;반대로 금리가 인상되면 위험 자산으로 자금이 이동하며 미국 국채에 대한 수요가 감소할 가능성이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;4. 일본 기준금리 상승과 미국 국채에 미치는 영향&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;최근 일본 중앙은행(BoJ)은 경제와 물가 상황에 따라 추가적인 금리 인상을 고려하고 있다고 밝혔습니다. 이는 일본뿐 아니라 글로벌 금융 시장에도 파급 효과를 미칠 수 있습니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;(1) 일본 금리 상승 배경&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;일본은 2024년 3월과 7월에 각각 금리를 인상하며 17년간 유지했던 마이너스 금리를 종료했습니다.&lt;/li&gt;
&lt;li&gt;BoJ 총재 우에다 카즈오(Kazuo Ueda)는 경제 성장과 물가 안정 목표를 위해 점진적인 금리 인상을 지속할 가능성을 시사했습니다.&lt;/li&gt;
&lt;li&gt;현재 일본 기준금리는 0.25%로 유지되고 있지만, 경제와 물가 데이터가 예상대로 진행된다면 추가 인상이 이루어질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;(2) 일본 금리 상승이 미국 국채에 미치는 영향&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&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;/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;/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;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;5. 미국 국채 투자 시 고려사항&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;미국 국채는 안정성과 유동성을 제공하지만, 금리 변화와 같은 외부 요인에 민감하기 때문에 다음 요소를 고려해야 합니다:&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;(1) 금리 변동&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;장기 국채는 금리 변화에 더 민감하므로 변동성이 큽니다. 따라서 금리가 상승할 것으로 예상되면 단기 채권 비중을 늘리는 것이 유리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;(2) 환율 리스크&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;외국인이 미국 국채에 투자할 경우 달러 가치 변동이 수익률에 영향을 미칩니다. 예를 들어, 달러 강세 시 환차익을 얻지만, 달러 약세 시 손실 위험이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;(3) 경기 사이클&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;경기 침체나 불확실성이 클 때는 안전자산 선호 심리가 강화되어 미국 국채 가격이 상승할 가능성이 높습니다.&lt;/li&gt;
&lt;li&gt;반대로 경기 회복 시에는 위험 자산 선호로 인해 미국 국채 가격이 하락할 가능성이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;5. 결론&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;미국 국채는 안정성과 유동성을 갖춘 대표적인 안전자산으로, 글로벌 경제와 금융 시장에서 중요한 역할을 합니다. 특히 기준금리는 미국 국채의 가격과 수익률에 직접적인 영향을 미치며, 투자 전략 수립 시 반드시 고려해야 할 요소입니다.&lt;/p&gt;
&lt;p&gt;투자자는 자신의 목적과 리스크 허용 범위에 맞춰 적절한 만기의 채권을 선택하고, 금리 및 환율 변동성을 면밀히 분석해야 합니다. 또한 경기 사이클과 중앙은행(Fed)의 통화정책 방향을 주시하며 장기적인 관점에서 접근하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>경제</category>
      <category>경제</category>
      <category>금리</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/445</guid>
      <comments>https://syaku.tistory.com/445#entry445comment</comments>
      <pubDate>Mon, 9 Dec 2024 16:18:51 +0900</pubDate>
    </item>
    <item>
      <title>Javascript &amp;amp; Node.js 패턴: IIFE의 이해와 활용</title>
      <link>https://syaku.tistory.com/444</link>
      <description>&lt;h3&gt;JavaScript &amp;amp; Node.js 패턴: IIFE의 이해와 활용&lt;/h3&gt;
&lt;p&gt;JavaScript에서 &lt;strong&gt;즉시 실행 함수 표현식(IIFE, Immediately Invoked Function Expression)&lt;/strong&gt;는 오래전부터 자주 사용된 패턴입니다. Node.js와 같은 서버 환경에서도 비동기 초기화와 모듈화 등 다양한 작업에 활용할 수 있습니다. 이번 글에서는 IIFE의 구조, 사용 이유, 장점, 그리고 실제 사용 사례를 다룹니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;IIFE란?&lt;/h3&gt;
&lt;p&gt;IIFE는 &lt;strong&gt;함수를 정의하자마자 실행하는 패턴&lt;/strong&gt;입니다. 일반적인 함수 선언과 달리, 함수 표현식을 소괄호 &lt;code&gt;()&lt;/code&gt;로 감싸고 뒤에 호출 연산자 &lt;code&gt;()&lt;/code&gt;를 붙여 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 일반적인 함수 선언
function normalFunction() {
  console.log(&amp;#39;Hello from normal function!&amp;#39;);
}
normalFunction();

// IIFE
(() =&amp;gt; {
  console.log(&amp;#39;Hello from IIFE!&amp;#39;);
})();&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;IIFE의 주요 특징&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;즉시 실행&lt;/strong&gt;&lt;br&gt;함수가 선언되면 바로 실행됩니다. 별도의 호출이 필요하지 않기 때문에 초기화 작업에 적합합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;스코프 캡슐화&lt;/strong&gt;&lt;br&gt;내부에서 선언된 변수는 외부에서 접근할 수 없습니다. 이를 통해 전역 네임스페이스 오염을 방지할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;모듈화 가능&lt;/strong&gt;&lt;br&gt;함수 내부에 캡슐화된 코드를 반환함으로써 모듈 패턴으로 활용할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;IIFE의 구조&lt;/h3&gt;
&lt;p&gt;IIFE는 두 가지 형태로 작성할 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;익명 함수 사용&lt;/strong&gt;&lt;br&gt;소괄호로 감싸 익명 함수를 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;(function () {
  console.log(&amp;#39;IIFE with an anonymous function&amp;#39;);
})();&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;화살표 함수 사용&lt;/strong&gt;&lt;br&gt;ES6 이후, 화살표 함수를 사용하여 간결하게 표현할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;(() =&amp;gt; {
  console.log(&amp;#39;IIFE with an arrow function&amp;#39;);
})();&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;Node.js에서의 IIFE 활용&lt;/h3&gt;
&lt;h4&gt;비동기 초기화 로직&lt;/h4&gt;
&lt;p&gt;Node.js 애플리케이션 초기화 시, 데이터베이스 연결이나 환경 설정 등 비동기 작업이 필요합니다. IIFE는 이러한 초기화 로직을 간결하게 처리하는 데 유용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;(async () =&amp;gt; {
  try {
    console.log(&amp;#39;Connecting to the database...&amp;#39;);
    await connectToDatabase();
    console.log(&amp;#39;Database connected!&amp;#39;);

    app.listen(PORT, () =&amp;gt; {
      console.log(`Server is running on port ${PORT}`);
    });
  } catch (error) {
    console.error(&amp;#39;Failed to initialize the server:&amp;#39;, error);
    process.exit(1); // 초기화 실패 시 프로세스 종료
  }
})();&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;async/await&lt;/code&gt; 사용&lt;/strong&gt;: 비동기 작업의 실행 흐름을 명확히 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;try-catch&lt;/code&gt; 블록&lt;/strong&gt;: 초기화 단계에서 발생하는 오류를 쉽게 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;즉시 실행&lt;/strong&gt;: 선언 후 바로 실행되므로 초기화 코드가 독립적으로 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;스코프 캡슐화&lt;/h4&gt;
&lt;p&gt;Node.js 모듈 파일 내에서 IIFE를 사용하면 불필요한 전역 변수 선언을 방지할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;(() =&amp;gt; {
  const SECRET_KEY = process.env.SECRET_KEY;

  function getSecret() {
    return SECRET_KEY;
  }

  console.log(&amp;#39;Module initialized with secret key.&amp;#39;);
})();&lt;/code&gt;&lt;/pre&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;hr&gt;
&lt;h3&gt;IIFE와 모듈 패턴&lt;/h3&gt;
&lt;p&gt;IIFE는 단순히 초기화 코드뿐 아니라 &lt;strong&gt;모듈 패턴&lt;/strong&gt;으로도 자주 활용됩니다. 이를 통해 캡슐화된 코드를 반환하고, 외부에서 필요한 부분만 노출할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const CounterModule = (() =&amp;gt; {
  let count = 0; // 캡슐화된 변수

  return {
    increment() {
      count++;
      console.log(`Count: ${count}`);
    },
    decrement() {
      count--;
      console.log(`Count: ${count}`);
    },
  };
})();

CounterModule.increment(); // Count: 1
CounterModule.decrement(); // Count: 0
console.log(CounterModule.count); // undefined (외부 접근 불가)&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;언제 IIFE를 사용할까?&lt;/h3&gt;
&lt;ol&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;외부 API 호출&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;/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;ul&gt;
&lt;li&gt;캡슐화된 코드를 통해 코드 구조를 단순화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;IIFE는 JavaScript와 Node.js에서 단순하지만 강력한 패턴입니다. 특히 초기화 작업과 모듈화에서 유용하며, 비동기 코드와 결합하면 더욱 강력한 도구가 됩니다. 프로젝트에서 IIFE를 적극 활용하여 깔끔하고 유지보수하기 쉬운 코드를 작성해보세요!&lt;/p&gt;</description>
      <category>개발</category>
      <category>javscript</category>
      <category>nodejs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/444</guid>
      <comments>https://syaku.tistory.com/444#entry444comment</comments>
      <pubDate>Thu, 5 Dec 2024 13:05:04 +0900</pubDate>
    </item>
    <item>
      <title>React와 Node.js 개발자를 위한 파일 네이밍의 모든 것</title>
      <link>https://syaku.tistory.com/443</link>
      <description>&lt;h3&gt;파일 네이밍 규칙 정리 및 가이드&lt;/h3&gt;
&lt;p&gt;네이밍 규칙을 정의하면 프로젝트의 일관성을 유지하고, 파일의 역할을 쉽게 이해할 수 있습니다. 아래는 React.js와 Node.js 각각의 파일 네이밍 방식, 예시 코드, 장단점, 그리고 주의사항에 대해 정리한 내용입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;1. &lt;strong&gt;React.js 파일 네이밍 규칙&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;파일 네이밍 방식:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;컴포넌트&lt;/strong&gt;: &lt;strong&gt;PascalCase&lt;/strong&gt;&lt;br&gt;컴포넌트 파일명은 컴포넌트 이름과 동일하게 PascalCase로 작성합니다. 예: &lt;code&gt;UserName.tsx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;훅(Hooks)&lt;/strong&gt;: &lt;strong&gt;camelCase&lt;/strong&gt;&lt;br&gt;파일명은 항상 &lt;code&gt;use&lt;/code&gt;로 시작하며 camelCase를 사용합니다. 예: &lt;code&gt;useUserName.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;예시 코드:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;UserName.tsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

const UserName: React.FC = () =&amp;gt; {
  return &amp;lt;h1&amp;gt;User Name Component&amp;lt;/h1&amp;gt;;
};

export default UserName;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;useUserName.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useState } from &amp;#39;react&amp;#39;;

export const useUserName = () =&amp;gt; {
  const [userName, setUserName] = useState&amp;lt;string&amp;gt;(&amp;#39;John Doe&amp;#39;);
  return { userName, setUserName };
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;2. &lt;strong&gt;Node.js 파일 네이밍 규칙&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;파일 네이밍 방식:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;케밥 케이스(kebab-case)&lt;/strong&gt;를 사용하며, 파일명에는 역할과 목적을 명확히 나타내기 위해 &lt;code&gt;.&lt;/code&gt;와 &lt;code&gt;-&lt;/code&gt;를 조합합니다.&lt;ul&gt;
&lt;li&gt;컨트롤러: &lt;code&gt;username.controller.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;설정 파일: &lt;code&gt;data-source.config.ts&lt;/code&gt;, &lt;code&gt;redis.config.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;예시 코드:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;username.controller.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Request, Response } from &amp;#39;express&amp;#39;;

export const getUserName = (req: Request, res: Response): void =&amp;gt; {
  res.json({ userName: &amp;#39;John Doe&amp;#39; });
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;data-source.config.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export const dataSourceConfig = {
  host: &amp;#39;localhost&amp;#39;,
  port: 5432,
  username: &amp;#39;admin&amp;#39;,
  password: &amp;#39;securepassword&amp;#39;,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;redis.config.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export const redisConfig = {
  host: &amp;#39;127.0.0.1&amp;#39;,
  port: 6379,
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;3. &lt;strong&gt;테스트 케이스 파일 구조&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;테스트 파일은 프로젝트의 명확한 분리를 위해 &lt;code&gt;/__tests__/&lt;/code&gt; 디렉터리 하위에 배치합니다. 테스트 대상과 동일한 이름을 사용하고, 파일명은 &lt;code&gt;.test.ts&lt;/code&gt;로 확장합니다.&lt;/p&gt;
&lt;h4&gt;디렉터리 구조:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;src/
  controllers/
    username.controller.ts
  configs/
    data-source.config.ts
    redis.config.ts
__tests__/
  username.controller.test.ts
  data-source.config.test.ts&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;예시 테스트 코드:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;username.controller.test.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { getUserName } from &amp;#39;../src/controllers/username.controller&amp;#39;;

describe(&amp;#39;getUserName&amp;#39;, () =&amp;gt; {
  it(&amp;#39;should return the user name&amp;#39;, () =&amp;gt; {
    const mockReq = {};
    const mockRes = { json: jest.fn() };
    getUserName(mockReq as any, mockRes as any);
    expect(mockRes.json).toHaveBeenCalledWith({ userName: &amp;#39;John Doe&amp;#39; });
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;장점과 단점&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/h4&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;: PascalCase, camelCase, kebab-case 사용으로 역할에 따라 구분이 쉽습니다.&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;h4&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;긴 파일명&lt;/strong&gt;: kebab-case와 구체적인 명명 방식으로 인해 파일명이 길어질 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대소문자 이슈&lt;/strong&gt;: Git에서 대소문자 변경이 제대로 반영되지 않는 문제가 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;팀원 학습 시간&lt;/strong&gt;: 처음 규칙을 도입할 때 팀원들이 익숙해지는 데 시간이 걸릴 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;주의사항 및 문제 해결&lt;/h3&gt;
&lt;h4&gt;1. &lt;strong&gt;Git 대소문자 인식 문제&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Git은 파일 대소문자 변경을 기본적으로 감지하지 않습니다. 이는 PascalCase와 camelCase를 혼용할 때 문제가 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Git에서 대소문자 변경을 강제 적용:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config core.ignorecase false
git mv FileName.tsx filename.tsx
git mv filename.tsx FileName.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;팀원들에게 위 설정을 공유하여 문제를 방지합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;2. &lt;strong&gt;명명 충돌&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;비슷한 이름의 파일이 많아지면 경로 관리가 어려워질 수 있습니다.&lt;/p&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;code&gt;user.controller.ts&lt;/code&gt;, &lt;code&gt;admin.controller.ts&lt;/code&gt;를 각각 &lt;code&gt;user/&lt;/code&gt;와 &lt;code&gt;admin/&lt;/code&gt; 디렉터리에 배치.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;3. &lt;strong&gt;파일명 혼동&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;PascalCase와 kebab-case 규칙을 혼동하면 역할 구분이 모호해질 수 있습니다.&lt;/p&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;hr&gt;
&lt;h4&gt;4. &lt;strong&gt;파일명 길이&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;kebab-case로 구성된 파일명이 길어져 불편할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;불필요한 접미사(&lt;code&gt;helper&lt;/code&gt;, &lt;code&gt;utils&lt;/code&gt; 등)를 제거하거나 약어를 사용하여 간결하게 작성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;p&gt;파일 네이밍 규칙은 프로젝트의 가독성과 유지보수성을 결정짓는 중요한 요소입니다. 명확한 가이드라인을 설정하고 이를 팀 내에서 지속적으로 공유 및 검토하세요. 이를 통해 생산성을 높이고 협업의 효율성을 극대화할 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>네이밍</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/443</guid>
      <comments>https://syaku.tistory.com/443#entry443comment</comments>
      <pubDate>Thu, 5 Dec 2024 12:30:48 +0900</pubDate>
    </item>
    <item>
      <title>docker compose v2 for Redis</title>
      <link>https://syaku.tistory.com/442</link>
      <description>&lt;p&gt;Docker Compose를 사용하여 Redis를 설치하고 디스크에 데이터를 저장하도록 설정하는 방법은 다음과 같습니다. 이 과정에서는 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일을 작성하여 Redis 서비스를 정의하고, 데이터를 지속적으로 저장하기 위한 설정을 포함합니다.&lt;/p&gt;
&lt;h1&gt;Redis 설치 및 디스크 저장 설정&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;디렉토리 생성&lt;/strong&gt;: Redis 설정 파일과 데이터를 저장할 디렉토리를 생성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p ./redis/data
mkdir -p ./redis/conf&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redis 설정 파일 생성&lt;/strong&gt;: &lt;code&gt;./redis/conf&lt;/code&gt; 디렉토리에 &lt;code&gt;redis.conf&lt;/code&gt; 파일을 생성하고, 다음 내용을 추가하여 RDB와 AOF를 통한 데이터 지속성을 설정합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;# redis.conf

# RDB 지속성 설정
save 900 1        # 900초 동안 1개 이상의 키가 변경되면 저장
save 300 10       # 300초 동안 10개 이상의 키가 변경되면 저장
save 60 10000     # 60초 동안 10000개 이상의 키가 변경되면 저장
rdbcompression yes

# AOF 지속성 설정
appendonly yes    # AOF 활성화
appendfilename &amp;quot;appendonly.aof&amp;quot;
appendfsync everysec&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker Compose 파일 작성&lt;/strong&gt;: 프로젝트의 루트 디렉토리에 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일을 작성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  redis:
    image: redis:alpine
    container_name: redis_server
    ports:
      - &amp;quot;6379:6379&amp;quot;
    volumes:
      - ./redis/data:/data
      - ./redis/conf/redis.conf:/usr/local/etc/redis/redis.conf
    command: [&amp;quot;redis-server&amp;quot;, &amp;quot;/usr/local/etc/redis/redis.conf&amp;quot;]
    restart: always
    labels:
      - &amp;quot;name=redis&amp;quot;
      - &amp;quot;mode=standalone&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker Compose 실행&lt;/strong&gt;: &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일이 있는 디렉토리에서 다음 명령어를 실행하여 Redis를 시작합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Docker Compose V1에서 V2로의 전환과 관련이 있을 수 있습니다. Docker Compose V2는 이제 독립 실행형이 아닌 Docker CLI의 플러그인으로 통합되어 있으며, 이로 인해 명령어 사용 방식이 변경되었습니다.&lt;/p&gt;
&lt;p&gt;Docker Compose V2에서는 하이픈(-)을 사용하지 않고, 공백을 사용하여 명령어를 실행합니다. 따라서 docker-compose up 대신 docker compose up을 사용해야 합니다&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redis 실행 확인&lt;/strong&gt;: Redis 컨테이너가 정상적으로 실행 중인지 확인하려면 다음 명령어를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터 지속성 테스트&lt;/strong&gt;: &lt;code&gt;redis-cli&lt;/code&gt;를 사용하여 Redis 서버에 연결하고 몇 가지 데이터를 입력한 후, 컨테이너를 재시작하여 데이터가 유지되는지 확인할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;설명&lt;/h2&gt;
&lt;p&gt;이 설정은 Redis 인스턴스가 재시작되거나 장애가 발생하더라도 데이터를 복구할 수 있도록 보장합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;볼륨&lt;/strong&gt;: Docker Compose 파일의 &lt;code&gt;volumes&lt;/code&gt; 섹션은 로컬 디렉토리를 컨테이너에 마운트하여 데이터(&lt;code&gt;./redis/data&lt;/code&gt;)와 설정 파일(&lt;code&gt;./redis/conf/redis.conf&lt;/code&gt;)을 지속적으로 유지할 수 있도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지속성 설정&lt;/strong&gt;: &lt;code&gt;redis.conf&lt;/code&gt; 파일은 RDB와 AOF 옵션을 활성화하여 데이터가 주기적으로 저장되고, 모든 변경 사항이 로그로 기록되도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포트&lt;/strong&gt;: 기본 Redis 포트인 6379가 외부에 노출되어 외부에서 접근할 수 있도록 설정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;version&lt;/h3&gt;
&lt;p&gt;Docker Compose 파일에서 &lt;code&gt;version&lt;/code&gt; 속성은 이제 더 이상 필요하지 않습니다. Docker Compose는 최신 Compose 사양을 기본적으로 사용하기 때문에, &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일에 버전을 명시할 필요가 없어졌습니다. 이로 인해 &lt;code&gt;version&lt;/code&gt; 속성을 포함하면 경고 메시지가 나타날 수 있습니다.&lt;/p&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;: 이전에는 Compose 파일의 스키마 버전을 지정하기 위해 &lt;code&gt;version&lt;/code&gt; 속성이 필요했습니다. 이는 이전 버전과의 호환성을 위해 유용했지만, 이제는 Compose가 최신 사양을 자동으로 적용하므로 불필요하게 되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;경고 메시지&lt;/strong&gt;: &lt;code&gt;version&lt;/code&gt; 속성을 포함한 Compose 파일을 실행할 때, &amp;quot;이 속성은 더 이상 사용되지 않으니 제거하라&amp;quot;는 경고 메시지가 나타날 수 있습니다. 이는 오류가 아닌 경고로, 파일의 기능에는 영향을 미치지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;현재 권장 사항&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;version&lt;/code&gt; 속성 제거&lt;/strong&gt;: Compose 파일에서 &lt;code&gt;version&lt;/code&gt; 속성을 제거하는 것이 좋습니다. 이는 파일의 단순성을 높이고 최신 기능을 활용하는 데 도움이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compose 최신 사양 사용&lt;/strong&gt;: 최신 Compose 사양을 활용하여 서비스, 네트워크, 볼륨 등을 정의하는 데 집중하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서, Docker Compose를 사용할 때는 &lt;code&gt;version&lt;/code&gt; 속성을 생략하고 최신 사양에 맞춰 서비스를 정의하는 것이 현대적인 방식입니다.&lt;/p&gt;
&lt;h3&gt;redis version&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;redis:alpine&lt;/code&gt; 이미지는 일반적으로 Alpine Linux 위에서 실행되는 Redis를 의미합니다. 이 이미지의 Redis 버전은 7.2.5입니다. 이 버전은 최신의 안정적인 Redis 기능을 제공하며, Alpine Linux의 경량성을 활용하여 최소한의 리소스로 Redis를 실행할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;restart&lt;/h3&gt;
&lt;p&gt;Docker Compose에서 &lt;code&gt;restart: always&lt;/code&gt;는 컨테이너가 중지되었을 때 항상 재시작하도록 설정하는 정책입니다. Docker Compose의 재시작 정책에는 다음과 같은 종류가 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;no&lt;/strong&gt;: 컨테이너가 중지되거나 오류가 발생해도 자동으로 재시작하지 않습니다. 기본값입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;always&lt;/strong&gt;: 컨테이너가 중지되면 항상 재시작합니다. 이는 서비스가 항상 실행되어야 하는 경우에 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;on-failure&lt;/strong&gt;: 컨테이너가 비정상 종료(비제로 종료 코드)될 때만 재시작합니다. 오류 해결을 위해 유용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unless-stopped&lt;/strong&gt;: 사용자가 명시적으로 중지하지 않는 한 항상 재시작합니다. 이는 &lt;code&gt;always&lt;/code&gt;와 &lt;code&gt;no&lt;/code&gt;의 중간 형태로, Docker 자체가 중지되거나 재시작될 때도 컨테이너를 다시 시작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 정책은 각 서비스의 요구 사항에 맞게 컨테이너의 동작을 제어하는 데 사용됩니다. &lt;code&gt;restart: always&lt;/code&gt;는 특히 중요한 서비스가 지속적으로 실행되어야 할 때 적합한 선택입니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>docker</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/442</guid>
      <comments>https://syaku.tistory.com/442#entry442comment</comments>
      <pubDate>Thu, 5 Dec 2024 11:51:23 +0900</pubDate>
    </item>
    <item>
      <title>Node.js 생태계의 모듈 시스템 진화</title>
      <link>https://syaku.tistory.com/441</link>
      <description>&lt;h1&gt;Node.js 생태계의 모듈 시스템 진화: 주요 프레임워크 분석 - Part 1&lt;/h1&gt;
&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;Node.js 생태계에서 모듈 시스템의 선택은 프로젝트의 성공을 좌우하는 중요한 요소입니다. n8n, TypeORM, NestJS와 같은 주요 프레임워크들의 모듈 시스템 선택과 그 배경을 살펴보며, 현대 Node.js 개발에서의 최적의 선택에 대해 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;시대적 배경과 선택의 이유&lt;/h2&gt;
&lt;h3&gt;1. 역사적 맥락&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;n8n (2019년 시작)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;초기 Node.js 생태계의 CommonJS 표준 채택&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;p&gt;TypeORM (2016년 시작)&lt;/p&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;2. 호환성 고려사항&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// TypeORM의 전통적인 CommonJS 사용 예시
const { createConnection } = require(&amp;#39;typeorm&amp;#39;);

// 현대적 ESM 지원
import { createConnection } from &amp;#39;typeorm&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;프레임워크별 모듈 시스템 현황&lt;/h2&gt;
&lt;h3&gt;1. TypeORM의 변화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기본 CommonJS 사용&lt;/li&gt;
&lt;li&gt;ESM 지원 확대&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;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. n8n의 접근&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CommonJS 기반 안정성&lt;/li&gt;
&lt;li&gt;샌드박스 환경 최적화&lt;/li&gt;
&lt;li&gt;플러그인 시스템 호환성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. NestJS의 전략&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// NestJS의 모듈 시스템 예시
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: &amp;#39;mysql&amp;#39;,
      // ... 설정
    }),
  ],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;현대적 개발 환경에서의 선택 기준&lt;/h2&gt;
&lt;h3&gt;1. 서버사이드 애플리케이션&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CommonJS 선택이 유리한 경우&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;2. 최신 프로젝트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ESM 고려가 필요한 상황&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;3. 하이브리드 접근&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json 예시
{
  &amp;quot;exports&amp;quot;: {
    &amp;quot;.&amp;quot;: {
      &amp;quot;import&amp;quot;: &amp;quot;./dist/esm/index.js&amp;quot;,
      &amp;quot;require&amp;quot;: &amp;quot;./dist/cjs/index.js&amp;quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Node.js 생태계의 모듈 시스템 진화: 주요 프레임워크 분석 - Part 2&lt;/h1&gt;
&lt;h2&gt;프레임워크별 최신 동향과 권장사항&lt;/h2&gt;
&lt;h3&gt;1. TypeORM의 현대화&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 최신 TypeORM ESM 사용 예시
import { DataSource } from &amp;quot;typeorm&amp;quot;
import { User } from &amp;quot;./entity/User.js&amp;quot;  // .js 확장자 필수

const AppDataSource = new DataSource({
    type: &amp;quot;mysql&amp;quot;,
    entities: [User],
    synchronize: true,
})&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;주요 변화&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;ESM과 CommonJS 듀얼 지원&lt;/li&gt;
&lt;li&gt;데코레이터 호환성 개선&lt;/li&gt;
&lt;li&gt;자동 모듈 감지 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. NestJS의 모듈 시스템 전략&lt;/h3&gt;
&lt;h4&gt;현재 상황&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;CommonJS 기본 사용&lt;/li&gt;
&lt;li&gt;Node.js 22 실험적 기능 지원&lt;/li&gt;
&lt;li&gt;ESM 통합 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// NestJS ESM 실험적 사용 예시
import { Module } from &amp;#39;@nestjs/common&amp;#39;;
import { TypeOrmModule } from &amp;#39;@nestjs/typeorm&amp;#39;;

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: () =&amp;gt; ({
        type: &amp;#39;mysql&amp;#39;,
        autoLoadEntities: true,
      }),
    }),
  ],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;실무 적용 가이드&lt;/h2&gt;
&lt;h3&gt;1. 프로젝트 초기 설정&lt;/h3&gt;
&lt;h4&gt;CommonJS 기반 설정&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// tsconfig.json
{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;module&amp;quot;: &amp;quot;CommonJS&amp;quot;,
    &amp;quot;moduleResolution&amp;quot;: &amp;quot;node&amp;quot;,
    &amp;quot;esModuleInterop&amp;quot;: true
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ESM 기반 설정&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// tsconfig.json
{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;module&amp;quot;: &amp;quot;NodeNext&amp;quot;,
    &amp;quot;moduleResolution&amp;quot;: &amp;quot;NodeNext&amp;quot;,
    &amp;quot;target&amp;quot;: &amp;quot;ESNext&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 마이그레이션 전략&lt;/h3&gt;
&lt;h4&gt;단계적 접근&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;의존성 호환성 검토&lt;/li&gt;
&lt;li&gt;테스트 커버리지 확보&lt;/li&gt;
&lt;li&gt;점진적 모듈 전환&lt;/li&gt;
&lt;li&gt;성능 모니터링&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;호환성 유지 방안&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json
{
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;,
  &amp;quot;exports&amp;quot;: {
    &amp;quot;.&amp;quot;: {
      &amp;quot;import&amp;quot;: &amp;quot;./dist/esm/index.js&amp;quot;,
      &amp;quot;require&amp;quot;: &amp;quot;./dist/cjs/index.js&amp;quot;
    }
  }
}&lt;/code&gt;&lt;/pre&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;li&gt;로딩 성능 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 런타임 성능&lt;/h3&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;h2&gt;미래 대비 전략&lt;/h2&gt;
&lt;h3&gt;1. 기술 스택 현대화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ESM 지원 계획 수립&lt;/li&gt;
&lt;li&gt;TypeScript 통합 강화&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;/li&gt;
&lt;li&gt;성능 메트릭 모니터링&lt;/li&gt;
&lt;li&gt;코드 품질 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;Node.js 생태계의 주요 프레임워크들은 CommonJS에서 ESM으로의 전환기에 있습니다. TypeORM과 NestJS의 사례에서 볼 수 있듯이, 안정성과 호환성을 고려한 신중한 접근이 필요합니다. 프로젝트의 특성과 요구사항을 고려하여 적절한 모듈 시스템을 선택하고, 지속적인 모니터링과 개선을 통해 최적의 개발 환경을 구축하는 것이 중요합니다.&lt;/p&gt;
&lt;h3&gt;핵심 권장사항&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;서버사이드 프로젝트: CommonJS 우선 고려&lt;/li&gt;
&lt;li&gt;신규 프로젝트: ESM 도입 검토&lt;/li&gt;
&lt;li&gt;기존 프로젝트: 단계적 마이그레이션 계획&lt;/li&gt;
&lt;li&gt;지속적인 모니터링과 최적화&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발</category>
      <category>nodejs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/441</guid>
      <comments>https://syaku.tistory.com/441#entry441comment</comments>
      <pubDate>Mon, 25 Nov 2024 19:37:12 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript Map vs Object: 성능과 사용법</title>
      <link>https://syaku.tistory.com/440</link>
      <description>&lt;p&gt;자바스크립트의 Map과 Object의 특징과 성능 차이를 종합적으로 살펴보겠습니다:&lt;/p&gt;
&lt;h2&gt;Map의 기본 특징&lt;/h2&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;li&gt;size 속성으로 크기 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;제공 메서드&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set(): 값 추가/수정&lt;/li&gt;
&lt;li&gt;get(): 값 조회&lt;/li&gt;
&lt;li&gt;delete(): 값 삭제&lt;/li&gt;
&lt;li&gt;has(): 키 존재 여부 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Map 사용법&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Map 생성하기&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 빈 Map 생성
const myMap = new Map();

// 초기값과 함께 생성
const myMap2 = new Map([
  [&amp;#39;key1&amp;#39;, &amp;#39;value1&amp;#39;],
  [&amp;#39;key2&amp;#39;, &amp;#39;value2&amp;#39;]
]);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Map 조작하기&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const myMap = new Map();

// 값 추가
myMap.set(&amp;#39;name&amp;#39;, &amp;#39;John&amp;#39;);

// 값 조회
console.log(myMap.get(&amp;#39;name&amp;#39;)); // &amp;#39;John&amp;#39;

// 키 존재 확인
console.log(myMap.has(&amp;#39;name&amp;#39;)); // true

// 값 삭제
myMap.delete(&amp;#39;name&amp;#39;);

// 전체 삭제
myMap.clear();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Map 순회하기&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const myMap = new Map([[&amp;#39;a&amp;#39;, 1], [&amp;#39;b&amp;#39;, 2]]);

// for...of 사용
for (let [key, value] of myMap) {
  console.log(key, value);
}

// forEach 사용
myMap.forEach((value, key) =&amp;gt; {
  console.log(key, value);
});

// 키만 순회
for (let key of myMap.keys()) {
  console.log(key);
}

// 값만 순회
for (let value of myMap.values()) {
  console.log(value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;내부 구현의 최적화&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;해시맵 특화 설계&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Map은 동적인 키-값 쌍의 빈번한 추가/삭제를 위해 특별히 설계되었습니다&lt;/li&gt;
&lt;li&gt;Object는 프로토타입 체인과 상속 구조로 인한 오버헤드가 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;VM 최적화 차이&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript 엔진은 Object를 shape assumption(형태 가정)으로 최적화하려 합니다&lt;/li&gt;
&lt;li&gt;Map은 동적으로 변하는 해시맵 용도로 설계되어 이러한 제약이 없습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;성능 특성&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;작업별 성능 비교&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;삽입 작업: Map이 Object보다 최대 45.8배 더 빠름&lt;/li&gt;
&lt;li&gt;삭제 작업: Map이 Object보다 31배 더 빠름&lt;/li&gt;
&lt;li&gt;검색 작업: Map이 Object보다 2.6배 더 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예외 케이스&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문자열 키를 사용하는 읽기 중심 작업에서는 Object가 더 나은 성능을 보임&lt;/li&gt;
&lt;li&gt;작은 정수를 키로 사용할 때는 Object가 더 효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;메모리 효율성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Map은 동일한 크기의 Object보다 메모리 사용량이 적음&lt;/li&gt;
&lt;li&gt;특히 큰 데이터셋에서 메모리 효율성이 더욱 두드러짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;브라우저 지원&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2017년 6월 이후 모든 최신 브라우저 지원&lt;/li&gt;
&lt;li&gt;Internet Explorer 미지원&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <category>JS</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/440</guid>
      <comments>https://syaku.tistory.com/440#entry440comment</comments>
      <pubDate>Sat, 23 Nov 2024 14:33:00 +0900</pubDate>
    </item>
    <item>
      <title>Windsurf Editor: AI 기반 차세대 개발 환경 소개</title>
      <link>https://syaku.tistory.com/439</link>
      <description>&lt;h1&gt;Windsurf Editor: AI 기반 차세대 개발 환경 소개&lt;/h1&gt;
&lt;p&gt;Windsurf Editor는 Codeium이 개발한 혁신적인 AI 기반 통합 개발 환경(IDE)으로, VS Code를 기반으로 하면서도 더 나은 UI와 성능을 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codeium.com/windsurf&quot;&gt;https://codeium.com/windsurf&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;주요 특징&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AI 통합 개발 환경&lt;/strong&gt;&lt;br&gt;Windsurf는 코파일럿과 에이전트 기능을 결합한 최초의 AI 에이전트 기반 IDE입니다. 개발자와 AI가 실시간으로 협력하며 복잡한 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cascade Flow&lt;/strong&gt;&lt;br&gt;Cascade는 Windsurf의 핵심 기능으로, 다음과 같은 특징을 제공합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드베이스에 대한 깊은 의미론적 이해&lt;/li&gt;
&lt;li&gt;실시간 작업 인식&lt;/li&gt;
&lt;li&gt;다중 파일 편집 기능&lt;/li&gt;
&lt;li&gt;고급 검색 및 인덱싱 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;무료 플랜 (Individual)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;기본적인 AI 자동 완성&lt;/li&gt;
&lt;li&gt;에디터 내 AI 채팅 기능&lt;/li&gt;
&lt;li&gt;AI 명령 실행&lt;/li&gt;
&lt;li&gt;Cascade 읽기 전용 모드만 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Pro 플랜 ($10/월)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GPT-4o와 Claude Sonnet을 포함한 고급 AI 모델 무제한 사용&lt;/li&gt;
&lt;li&gt;Cascade 전체 기능 (월 1000단계)&lt;/li&gt;
&lt;li&gt;Supercomplete 무제한 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;설정에서 Claude Sonnet 모델이 보이더라도, 실제로 이를 사용하기 위해서는 Pro 플랜으로 업그레이드가 필요합니다. Pro 플랜은 14일 무료 체험 기간을 제공하므로 이 기간 동안 고급 기능들을 테스트해볼 수 있습니다.&lt;/p&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;li&gt;선택적 0일 데이터 보관&lt;/li&gt;
&lt;li&gt;기본적인 컨텍스트 인식&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;사용 방법&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;설치 및 초기 설정&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;windsurf.ai에서 운영체제에 맞는 버전 다운로드&lt;/li&gt;
&lt;li&gt;Codeium 계정으로 로그인 또는 새 계정 생성&lt;/li&gt;
&lt;li&gt;VS Code 설정 가져오기 옵션 선택&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;주요 단축키&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cmd + I (또는 Ctrl + I): 자연어 명령으로 코드 생성/리팩토링&lt;/li&gt;
&lt;li&gt;Cmd + L: Cascade 패널에 텍스트 전달&lt;/li&gt;
&lt;li&gt;Cmd + Shift + P: 명령 팔레트 열기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;성능과 최적화&lt;/h2&gt;
&lt;p&gt;Windsurf는 VS Code보다 더 작은 메모리 사용량과 빠른 로딩 시간을 제공합니다. 특히 Linux 환경에서 뛰어난 성능을 보여주며, 엄격한 확장 프로그램 기준을 통해 전체적인 성능을 유지합니다.&lt;/p&gt;
&lt;h2&gt;프라이버시&lt;/h2&gt;
&lt;p&gt;Windsurf는 프라이버시를 중요시하며, 다른 유료 에디터들과 달리 사용자의 코드와 개발 습관을 AI 모델 개선에 무단으로 사용하지 않습니다.&lt;/p&gt;
&lt;p&gt;이러한 특징들로 인해 Windsurf는 현대 개발자들에게 강력하면서도 효율적인 개발 환경을 제공하는 도구로 자리잡고 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>ai editor</category>
      <category>windsurf</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/439</guid>
      <comments>https://syaku.tistory.com/439#entry439comment</comments>
      <pubDate>Fri, 22 Nov 2024 14:35:49 +0900</pubDate>
    </item>
    <item>
      <title>n8n 워크플로우 노드 가이드</title>
      <link>https://syaku.tistory.com/438</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfAhwM/btsKQBgeWUi/UBWllwIyMk5A2U676yYHV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfAhwM/btsKQBgeWUi/UBWllwIyMk5A2U676yYHV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfAhwM/btsKQBgeWUi/UBWllwIyMk5A2U676yYHV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfAhwM%2FbtsKQBgeWUi%2FUBWllwIyMk5A2U676yYHV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;724&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n8n의 주요 노드들의 사용법과 실전 예제를 통해 효과적인 워크플로우 구성 방법을 알아봅니다.&lt;/p&gt;
&lt;h1&gt;운영 모드&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영 모드 활성화 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크플로우 편집기 화면의 &lt;b&gt;오른쪽 상단&lt;/b&gt;에 있는 &lt;b&gt;Activate 토글 스위치&lt;/b&gt;를 켜면 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 모드와 운영 모드의 차이&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 모드&lt;/b&gt;: &quot;Execute Workflow&quot; 버튼을 통해 수동으로 실행할 때 적용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영 모드&lt;/b&gt;: Activate 토글을 켠 후 트리거 노드에 의해 자동으로 실행될 때 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 특징&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영 모드에서만 &lt;code&gt;getWorkflowStaticData()&lt;/code&gt; 메서드가 제대로 작동합니다.&lt;/li&gt;
&lt;li&gt;데이터 변경이나 카운터 증가와 같은 상태 유지가 필요한 기능들은 운영 모드에서만 정상적으로 동작합니다.&lt;/li&gt;
&lt;li&gt;Interval 노드와 같은 트리거 노드들은 운영 모드에서만 자동으로 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;b&gt;Webhook 노드&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 들어오는 HTTP 요청을 처리하기 위해서는 n8n의 Webhook 노드를 사용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Webhook 노드 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기본 설정&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;method&quot;: &quot;POST&quot;,           // HTTP 메서드 선택
  &quot;path&quot;: &quot;/my-webhook&quot;,      // 웹훅 경로
  &quot;respondWith&quot;: &quot;json&quot;,      // 응답 형식
  &quot;authentication&quot;: &quot;none&quot;    // 인증 방식
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 접근 URL&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;&amp;lt;http://your-n8n-domain:5678/webhook/my-webhook&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 단순 데이터 수신&lt;/h3&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;# 외부에서 POST 요청 보내기
curl -X POST \\
  &amp;lt;http://your-n8n-domain:5678/webhook/my-webhook&amp;gt; \\
  -H 'Content-Type: application/json' \\
  -d '{&quot;name&quot;: &quot;John&quot;, &quot;age&quot;: 30}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 인증이 포함된 요청&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# Basic 인증이 포함된 요청
curl -X POST \\
  &amp;lt;http://your-n8n-domain:5678/webhook/my-webhook&amp;gt; \\
  -H 'Content-Type: application/json' \\
  -H 'Authorization: Basic dXNlcjpwYXNz' \\
  -d '{&quot;data&quot;: &quot;test&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. HTTP 메서드&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;GET&lt;/li&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;li&gt;PUT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;PATCH&lt;/li&gt;
&lt;li&gt;HEAD&lt;/li&gt;
&lt;li&gt;OPTIONS&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 응답 설정&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;JSON 응답&lt;/li&gt;
&lt;li&gt;텍스트 응답&lt;/li&gt;
&lt;li&gt;바이너리 응답&lt;/li&gt;
&lt;li&gt;커스텀 HTTP 상태 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 보안 옵션&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;Basic 인증&lt;/li&gt;
&lt;li&gt;헤더 인증&lt;/li&gt;
&lt;li&gt;Query 파라미터 인증&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;워크플로우 구성 예시&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 수신 및 저장&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Webhook &amp;rarr; Set &amp;rarr; Database
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 검증 및 처리&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Webhook &amp;rarr; IF &amp;rarr; Function &amp;rarr; Response
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;API 엔드포인트&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;Webhook &amp;rarr; Switch &amp;rarr; HTTP Request &amp;rarr; Response
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공개 접근 시 보안 설정 필수&lt;/li&gt;
&lt;li&gt;응답 시간 고려&lt;/li&gt;
&lt;li&gt;에러 처리 로직 구현&lt;/li&gt;
&lt;li&gt;데이터 유효성 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webhook 노드를 사용하면 n8n을 API 서버처럼 활용할 수 있으며, 외부 시스템의 이벤트를 처리할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;HTTP Request 노드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP Request 노드는 외부 시스템과의 HTTP 통신을 담당하는 핵심 노드입니다. API 호출부터 웹 스크래핑까지 다양한 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 설정&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;method&quot;: &quot;GET&quot;,               // HTTP 메서드
  &quot;url&quot;: &quot;&amp;lt;https://example.com&amp;gt;&quot;,  // 요청 URL
  &quot;headers&quot;: {                   // HTTP 헤더
    &quot;Content-Type&quot;: &quot;application/json&quot;
  },
  &quot;responseFormat&quot;: &quot;json&quot;       // 응답 형식
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지원하는 HTTP 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET: 데이터 조회&lt;/li&gt;
&lt;li&gt;POST: 데이터 생성&lt;/li&gt;
&lt;li&gt;PUT: 데이터 수정&lt;/li&gt;
&lt;li&gt;DELETE: 데이터 삭제&lt;/li&gt;
&lt;li&gt;PATCH: 부분 수정&lt;/li&gt;
&lt;li&gt;HEAD: 헤더만 조회&lt;/li&gt;
&lt;li&gt;OPTIONS: 지원 옵션 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 형식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;Form-Data&lt;/li&gt;
&lt;li&gt;Raw/Custom&lt;/li&gt;
&lt;li&gt;Binary 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. REST API 호출&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;method&quot;: &quot;POST&quot;,
  &quot;url&quot;: &quot;&amp;lt;https://api.example.com/users&amp;gt;&quot;,
  &quot;headers&quot;: {
    &quot;Authorization&quot;: &quot;Bearer {{$node.auth.data.token}}&quot;,
    &quot;Content-Type&quot;: &quot;application/json&quot;
  },
  &quot;body&quot;: {
    &quot;name&quot;: &quot;John Doe&quot;,
    &quot;email&quot;: &quot;john@example.com&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 웹 스크래핑&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;method&quot;: &quot;GET&quot;,
  &quot;url&quot;: &quot;&amp;lt;https://example.com/data&amp;gt;&quot;,
  &quot;responseFormat&quot;: &quot;string&quot;,
  &quot;options&quot;: {
    &quot;timeout&quot;: 5000
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고급 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 보안 설정&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;SSL/TLS 인증&lt;/li&gt;
&lt;li&gt;프록시 설정&lt;/li&gt;
&lt;li&gt;API 키 인증&lt;/li&gt;
&lt;li&gt;OAuth 인증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 성능 최적화&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타임아웃 설정&lt;/li&gt;
&lt;li&gt;재시도 옵션&lt;/li&gt;
&lt;li&gt;배치 처리&lt;/li&gt;
&lt;li&gt;페이지네이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 편의 기능&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;curl 명령어 변환&lt;/li&gt;
&lt;li&gt;응답 데이터 파싱&lt;/li&gt;
&lt;li&gt;에러 핸들링&lt;/li&gt;
&lt;li&gt;리다이렉트 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;워크플로우 활용 예시&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 동기화&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;HTTP Request &amp;rarr; Function &amp;rarr; Database
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;API 연동&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;Schedule &amp;rarr; HTTP Request &amp;rarr; Send Email
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 수집&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;HTTP Request &amp;rarr; Filter &amp;rarr; Transform &amp;rarr; Save
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타임아웃 설정 필수&lt;/li&gt;
&lt;li&gt;에러 처리 로직 구현&lt;/li&gt;
&lt;li&gt;인증 정보 보안&lt;/li&gt;
&lt;li&gt;요청 제한 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP Request 노드는 n8n에서 가장 많이 사용되는 노드 중 하나로, 외부 시스템과의 효과적인 통합을 가능하게 합니다.&lt;/p&gt;
&lt;h1&gt;Schedule Trigger 노드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Schedule Trigger 노드는 워크플로우를 정해진 시간에 자동으로 실행하게 해주는 트리거 노드입니다. 워크플로우의 시작점으로만 사용할 수 있으며, 다른 노드의 입력으로 사용할 수 없습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 워크플로우 생성&lt;/li&gt;
&lt;li&gt;Schedule Trigger 노드 추가&lt;/li&gt;
&lt;li&gt;실행 모드 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상세 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Timezone 설정&lt;/li&gt;
&lt;li&gt;실행 시간/간격 설정&lt;/li&gt;
&lt;li&gt;특정 날짜/시간 제외 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;워크플로우 상태가 반드시 Active여야 함&lt;/li&gt;
&lt;li&gt;실행 시간대 설정 확인 필수&lt;/li&gt;
&lt;li&gt;과도한 실행 주기 설정 주의&lt;/li&gt;
&lt;li&gt;노드 실행 시간과 스케줄 간격 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;활용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일간 보고서 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;Mode: Every Day
Time: 09:00
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주간 데이터 백업&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;Mode: Every Week
Time: 00:00
Weekday: Sunday
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매시간 모니터링&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Mode: Every Hour
Minute: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 스케줄 (Cron)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Mode: Custom
Cron: 0 9 * * 1-5  // 평일 오전 9시
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시간 간격 설정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mode 선택에서 다양한 옵션 제공:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Every Minute
Every Hour
Every Day
Every Week
Every Month
Custom (Cron)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cron 표현식 지원&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;* * * * *
│ │ │ │ │
│ │ │ │ └─ 요일 (0-7)
│ │ │ └─── 월 (1-12)
│ │ └───── 일 (1-31)
│ └─────── 시간 (0-23)
└───────── 분 (0-59)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연계 노드 추천&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Schedule Trigger 노드와 자주 함께 사용되는 노드들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP Request: API 호출&lt;/li&gt;
&lt;li&gt;Email Send: 정기 이메일 발송&lt;/li&gt;
&lt;li&gt;Function: 데이터 처리&lt;/li&gt;
&lt;li&gt;Database: 정기 데이터 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 스케줄링 기능을 통해 자동화된 워크플로우를 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트리거 간격 옵션&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초 단위&lt;/li&gt;
&lt;li&gt;분 단위&lt;/li&gt;
&lt;li&gt;시간 단위&lt;/li&gt;
&lt;li&gt;일 단위&lt;/li&gt;
&lt;li&gt;주 단위&lt;/li&gt;
&lt;li&gt;월 단위&lt;/li&gt;
&lt;li&gt;커스텀 (Cron 표현식)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 설정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트리거 규칙 여러 개 추가 가능&lt;/li&gt;
&lt;li&gt;실행 시간대(timezone) 설정 가능&lt;/li&gt;
&lt;li&gt;특정 요일, 시간, 분 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Schedule Trigger 노드 추가&lt;/li&gt;
&lt;li&gt;원하는 트리거 간격 선택&lt;/li&gt;
&lt;li&gt;세부 파라미터 설정&lt;/li&gt;
&lt;li&gt;워크플로우 저장 및 활성화(Activate)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;워크플로우를 반드시 활성화(Activate)해야 스케줄이 동작합니다&lt;/li&gt;
&lt;li&gt;타임존 설정 확인이 필요합니다&lt;/li&gt;
&lt;li&gt;워크플로우 설정의 타임존이 우선 적용되고, 없으면 n8n 인스턴스의 타임존이 적용됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Edit Fields(Set) 노드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크플로우에서 데이터를 설정하고 수정하는 핵심 노드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 데이터 설정&lt;/li&gt;
&lt;li&gt;기존 데이터 수정&lt;/li&gt;
&lt;li&gt;데이터 필드 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작동 모드&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Manual Mapping
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GUI를 통한 필드 편집&lt;/li&gt;
&lt;li&gt;드래그 앤 드롭으로 값 설정&lt;/li&gt;
&lt;li&gt;Fixed/Expression 토글 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JSON Output
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON 형식으로 직접 데이터 작성&lt;/li&gt;
&lt;li&gt;배열과 표현식 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 옵션&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keep Only Set Fields: 설정된 필드만 유지&lt;/li&gt;
&lt;li&gt;Include Binary Data: 바이너리 데이터 포함 여부&lt;/li&gt;
&lt;li&gt;Support Dot Notation: 점 표기법 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Code 노드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript나 Python을 사용하여 커스텀 로직을 구현할 수 있는 노드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaScript(Node.js) 또는 Python 지원&lt;/li&gt;
&lt;li&gt;Promise 지원&lt;/li&gt;
&lt;li&gt;console.log를 통한 디버깅 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 제공 기능&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내장 메서드와 변수 사용 (&lt;b&gt;&lt;code&gt;$&lt;/code&gt;&lt;/b&gt; 접두어)&lt;/li&gt;
&lt;li&gt;브라우저 콘솔 로깅&lt;/li&gt;
&lt;li&gt;데이터 처리와 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제한사항&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 시스템 직접 접근 불가&lt;/li&gt;
&lt;li&gt;HTTP 요청은 별도 노드 사용 필요&lt;/li&gt;
&lt;li&gt;n8n Cloud에서는 외부 npm 모듈 사용 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 지원&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;n8n Cloud 사용자를 위한 ChatGPT 코드 생성&lt;/li&gt;
&lt;li&gt;JavaScript 코드 자동 생성 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;활용 시나리오&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Edit Fields(Set) 노드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 엔드포인트 생성&lt;/li&gt;
&lt;li&gt;데이터 필드 매핑&lt;/li&gt;
&lt;li&gt;데이터 구조 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Code 노드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 데이터 처리&lt;/li&gt;
&lt;li&gt;커스텀 비즈니스 로직 구현&lt;/li&gt;
&lt;li&gt;데이터 변환 및 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발</category>
      <category>n8n</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/438</guid>
      <comments>https://syaku.tistory.com/438#entry438comment</comments>
      <pubDate>Wed, 20 Nov 2024 17:08:55 +0900</pubDate>
    </item>
    <item>
      <title>Traefik: 클라우드 네이티브 시대의 현대적인 리버스 프록시</title>
      <link>https://syaku.tistory.com/437</link>
      <description>&lt;h1&gt;Traefik: 클라우드 네이티브 시대의 현대적인 리버스 프록시&lt;/h1&gt;
&lt;h2&gt;소개&lt;/h2&gt;
&lt;p&gt;Traefik은 Go 언어로 작성된 오픈소스 엣지 라우터로, 현대적인 클라우드 네이티브 애플리케이션을 위해 특별히 설계된 리버스 프록시 및 로드 밸런서입니다. 특히 동적인 마이크로서비스 환경에서 강력한 성능을 발휘합니다.&lt;/p&gt;
&lt;h2&gt;핵심 특징&lt;/h2&gt;
&lt;h3&gt;1. 자동화된 서비스 디스커버리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker, Kubernetes, Marathon 등 다양한 플랫폼과 자동 통합&lt;/li&gt;
&lt;li&gt;실시간 컨테이너 모니터링 및 설정 자동 업데이트&lt;/li&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;Let&amp;#39;s Encrypt를 통한 자동 SSL/TLS 인증서 발급 및 갱신&lt;/li&gt;
&lt;li&gt;강력한 TLS 설정 옵션 제공&lt;/li&gt;
&lt;li&gt;IP 화이트리스팅, 기본 인증 등 다양한 보안 기능&lt;/li&gt;
&lt;li&gt;미들웨어를 통한 커스텀 보안 규칙 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 모니터링 및 관리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;실시간 대시보드 UI 제공&lt;/li&gt;
&lt;li&gt;상세한 메트릭스 및 헬스체크 기능&lt;/li&gt;
&lt;li&gt;Prometheus 통합 지원&lt;/li&gt;
&lt;li&gt;API를 통한 동적 설정 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Traefik vs Nginx 상세 비교&lt;/h2&gt;
&lt;h3&gt;1. 설계 철학&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Traefik&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클라우드 네이티브 환경에 최적화&lt;/li&gt;
&lt;li&gt;자동화와 동적 설정에 중점&lt;/li&gt;
&lt;li&gt;마이크로서비스 아키텍처 지원&lt;/li&gt;
&lt;li&gt;컨테이너 오케스트레이션 플랫폼과의 원활한 통합&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Nginx&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전통적인 웹 서버 아키텍처&lt;/li&gt;
&lt;li&gt;정적 설정 기반의 안정성 중시&lt;/li&gt;
&lt;li&gt;높은 성능과 확장성&lt;/li&gt;
&lt;li&gt;세밀한 설정 컨트롤&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 성능 비교&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Traefik&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;- 초당 처리량: ~17,000 요청
- 리소스 사용:
  - CPU: 부하 시 빠른 증가
  - 메모리: 동적 증가 경향
- 지연 시간: 평균 0.09-0.1ms&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Nginx&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;- 초당 처리량: ~40,000 요청
- 리소스 사용:
  - CPU: 안정적인 사용률
  - 메모리: 일정한 사용량 유지
- 지연 시간: 평균 0.05ms&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;구현 예시&lt;/h2&gt;
&lt;h3&gt;1. 기본 설정 (docker-compose.yml)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &amp;quot;3.3&amp;quot;
services:
  traefik:
    image: &amp;quot;traefik:v2.10&amp;quot;
    command:
      - &amp;quot;--api.insecure=true&amp;quot;
      - &amp;quot;--providers.docker=true&amp;quot;
      - &amp;quot;--providers.docker.exposedbydefault=false&amp;quot;
      - &amp;quot;--entrypoints.web.address=:80&amp;quot;
      - &amp;quot;--entrypoints.websecure.address=:443&amp;quot;
    ports:
      - &amp;quot;80:80&amp;quot;
      - &amp;quot;443:443&amp;quot;
      - &amp;quot;8080:8080&amp;quot;
    volumes:
      - &amp;quot;/var/run/docker.sock:/var/run/docker.sock:ro&amp;quot;
      - &amp;quot;./letsencrypt:/letsencrypt&amp;quot;
    labels:
      - &amp;quot;traefik.enable=true&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 서비스 설정 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  webapp:
    image: nginx
    labels:
      - &amp;quot;traefik.enable=true&amp;quot;
      - &amp;quot;traefik.http.routers.webapp.rule=Host(`example.com`)&amp;quot;
      - &amp;quot;traefik.http.services.webapp.loadbalancer.server.port=80&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;사용 시나리오&lt;/h2&gt;
&lt;h3&gt;1. Traefik 적합 환경&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 기반 마이크로서비스 아키텍처&lt;/li&gt;
&lt;li&gt;빈번한 서비스 배포/업데이트가 필요한 환경&lt;/li&gt;
&lt;li&gt;자동화된 SSL 인증서 관리가 필요한 경우&lt;/li&gt;
&lt;li&gt;DevOps/GitOps 기반의 인프라 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Nginx 적합 환경&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;고성능 정적 콘텐츠 서비스&lt;/li&gt;
&lt;li&gt;복잡한 라우팅 규칙이 필요한 환경&lt;/li&gt;
&lt;li&gt;레거시 시스템과의 통합이 필요한 경우&lt;/li&gt;
&lt;li&gt;리소스 효율성이 중요한 환경&lt;/li&gt;
&lt;/ul&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;Prometheus/Grafana 통합&lt;/li&gt;
&lt;li&gt;상세한 액세스 로그 분석&lt;/li&gt;
&lt;li&gt;서비스 헬스체크 및 알림 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 보안 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# TLS 설정 예시
labels:
  - &amp;quot;traefik.http.routers.webapp.tls=true&amp;quot;
  - &amp;quot;traefik.http.routers.webapp.tls.certresolver=myresolver&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 성능 최적화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;적절한 버퍼 크기 설정&lt;/li&gt;
&lt;li&gt;타임아웃 값 조정&lt;/li&gt;
&lt;li&gt;로드밸런싱 알고리즘 선택&lt;/li&gt;
&lt;li&gt;리소스 제한 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;Traefik은 현대적인 클라우드 네이티브 환경에서 강력한 도구입니다. 자동화된 설정과 서비스 디스커버리 기능은 DevOps 팀의 운영 부담을 크게 줄여줍니다. 다만, 순수 성능만을 고려한다면 Nginx가 여전히 우위에 있으므로, 사용 환경과 요구사항을 고려한 선택이 필요합니다.&lt;/p&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Traefik 공식 문서: &lt;a href=&quot;https://doc.traefik.io/traefik/&quot;&gt;https://doc.traefik.io/traefik/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docker Hub: &lt;a href=&quot;https://hub.docker.com/_/traefik&quot;&gt;https://hub.docker.com/_/traefik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/traefik/traefik&quot;&gt;https://github.com/traefik/traefik&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 글이 Traefik을 이해하고 구현하는데 도움이 되길 바랍니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>Server</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/437</guid>
      <comments>https://syaku.tistory.com/437#entry437comment</comments>
      <pubDate>Wed, 20 Nov 2024 13:13:39 +0900</pubDate>
    </item>
    <item>
      <title>Editly 옵션 설명</title>
      <link>https://syaku.tistory.com/436</link>
      <description>&lt;p&gt;nodejs 기반 동영상 생성 라이브러리 editly 에 대한 주요 옵션을 설명하였습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;매개변수&lt;/strong&gt;&lt;/h2&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;CLI 동등값&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;outPath&lt;/td&gt;
&lt;td&gt;--out&lt;/td&gt;
&lt;td&gt;출력 경로 (mp4, mkv), .gif도 가능&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;width&lt;/td&gt;
&lt;td&gt;--width&lt;/td&gt;
&lt;td&gt;모든 미디어가 변환될 너비&lt;/td&gt;
&lt;td&gt;640&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;height&lt;/td&gt;
&lt;td&gt;--height&lt;/td&gt;
&lt;td&gt;모든 미디어가 변환될 높이&lt;/td&gt;
&lt;td&gt;너비와 첫 비디오의 화면비에 따라 자동 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fps&lt;/td&gt;
&lt;td&gt;--fps&lt;/td&gt;
&lt;td&gt;모든 비디오가 변환될 FPS&lt;/td&gt;
&lt;td&gt;첫 비디오의 FPS 또는 25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;customOutputArgs&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;ffmpeg용 사용자 지정 출력 코덱/포맷 인수 지정 (예제 참조)&lt;/td&gt;
&lt;td&gt;자동 (h264)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allowRemoteRequests&lt;/td&gt;
&lt;td&gt;--allow-remote-requests&lt;/td&gt;
&lt;td&gt;경로로 원격 URL 허용&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fast&lt;/td&gt;
&lt;td&gt;--fast, -f&lt;/td&gt;
&lt;td&gt;빠른 모드 (미리보기용 낮은 해상도와 FPS ⏩)&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.layer.fontPath&lt;/td&gt;
&lt;td&gt;--font-path&lt;/td&gt;
&lt;td&gt;기본 폰트를 .ttf로 설정&lt;/td&gt;
&lt;td&gt;시스템 폰트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.layer.*&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;defaults.duration&lt;/td&gt;
&lt;td&gt;--clip-duration&lt;/td&gt;
&lt;td&gt;자체 지속 시간이 없는 클립의 기본 지속 시간 설정&lt;/td&gt;
&lt;td&gt;4초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.transition&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;기본 전환을 설명하는 객체 { name, duration }. null로 설정하면 전환 비활성화&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.transition.duration&lt;/td&gt;
&lt;td&gt;--transition-duration&lt;/td&gt;
&lt;td&gt;기본 전환 지속 시간&lt;/td&gt;
&lt;td&gt;0.5초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.transition.name&lt;/td&gt;
&lt;td&gt;--transition-name&lt;/td&gt;
&lt;td&gt;기본 전환 유형. 전환 유형 참조&lt;/td&gt;
&lt;td&gt;random&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.transition.audioOutCurve&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;오디오 크로스페이드의 기본 페이드 아웃 곡선&lt;/td&gt;
&lt;td&gt;tri&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaults.transition.audioInCurve&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;오디오 크로스페이드의 기본 페이드 인 곡선&lt;/td&gt;
&lt;td&gt;tri&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clips[]&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;clips[].duration&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;클립 지속 시간. defaults.duration 참조. 설정되지 않은 경우 첫 비디오 레이어의 지속 시간&lt;/td&gt;
&lt;td&gt;defaults.duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clips[].transition&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;이 클립 끝의 전환 지정. defaults.transition 참조&lt;/td&gt;
&lt;td&gt;defaults.transition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clips[].layers[]&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;clips[].layers[].type&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;clips[].layers[].start&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;clips[].layers[].stop&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;audioTracks[]&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;audioFilePath&lt;/td&gt;
&lt;td&gt;--audio-file-path&lt;/td&gt;
&lt;td&gt;전체 비디오의 오디오 트랙 설정. 오디오 트랙도 참조&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loopAudio&lt;/td&gt;
&lt;td&gt;--loop-audio&lt;/td&gt;
&lt;td&gt;오디오 트랙이 비디오보다 짧을 경우 반복?&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;keepSourceAudio&lt;/td&gt;
&lt;td&gt;--keep-source-audio&lt;/td&gt;
&lt;td&gt;클립의 원본 오디오 유지?&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clipsAudioVolume&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;audioTracks에 대한 클립의 상대적 오디오 볼륨. 오디오 트랙 참조&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;outputVolume&lt;/td&gt;
&lt;td&gt;--output-volume&lt;/td&gt;
&lt;td&gt;출력 볼륨 조정 (최종 단계). 예제 참조&lt;/td&gt;
&lt;td&gt;1 (예: 0.5 또는 10dB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioNorm.enable&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;오디오 정규화 활성화? 오디오 정규화 참조&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioNorm.gaussSize&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;오디오 정규화 가우스 크기. 오디오 정규화 참조&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioNorm.maxGain&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;오디오 정규화 최대 게인. 오디오 정규화 참조&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;전환 유형과 레이어 유형&lt;/h2&gt;
&lt;h3&gt;전환 유형&lt;/h3&gt;
&lt;p&gt;transition.name은 &lt;a href=&quot;https://gl-transitions.com/gallery&quot;&gt;gl-transitions&lt;/a&gt; 중 하나이거나 다음 중 하나일 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;directional-left (왼쪽 방향)&lt;/li&gt;
&lt;li&gt;directional-right (오른쪽 방향)&lt;/li&gt;
&lt;li&gt;directional-up (위쪽 방향)&lt;/li&gt;
&lt;li&gt;directional-down (아래쪽 방향)&lt;/li&gt;
&lt;li&gt;random (무작위)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;레이어 유형&lt;/h3&gt;
&lt;h3&gt;비디오 레이어&lt;/h3&gt;
&lt;p&gt;부모 clip.duration이 지정된 경우 비디오는 clip.duration에 맞게 속도가 조절됩니다. cutFrom/cutTo가 설정된 경우 해당 구간(cutTo-cutFrom)이 clip.duration에 맞게 속도가 조절됩니다. 레이어에 오디오가 있는 경우 유지됩니다(다른 오디오 레이어가 있는 경우 믹싱됨).&lt;/p&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;path&lt;/td&gt;
&lt;td&gt;비디오 파일 경로&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resizeMode&lt;/td&gt;
&lt;td&gt;크기 조정 모드 참조&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cutFrom&lt;/td&gt;
&lt;td&gt;자르기 시작 시간&lt;/td&gt;
&lt;td&gt;0초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cutTo&lt;/td&gt;
&lt;td&gt;자르기 종료 시간&lt;/td&gt;
&lt;td&gt;비디오 끝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;width&lt;/td&gt;
&lt;td&gt;화면 너비 대비 상대 너비&lt;/td&gt;
&lt;td&gt;1 (0~1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;height&lt;/td&gt;
&lt;td&gt;화면 높이 대비 상대 높이&lt;/td&gt;
&lt;td&gt;1 (0~1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;left&lt;/td&gt;
&lt;td&gt;화면 너비 대비 X 위치&lt;/td&gt;
&lt;td&gt;0 (0~1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;top&lt;/td&gt;
&lt;td&gt;화면 높이 대비 Y 위치&lt;/td&gt;
&lt;td&gt;0 (0~1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;originX&lt;/td&gt;
&lt;td&gt;X 앵커 포인트&lt;/td&gt;
&lt;td&gt;left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;originY&lt;/td&gt;
&lt;td&gt;Y 앵커 포인트&lt;/td&gt;
&lt;td&gt;top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixVolume&lt;/td&gt;
&lt;td&gt;다른 오디오와 믹싱 시 상대 볼륨&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;오디오 레이어&lt;/h3&gt;
&lt;p&gt;오디오 레이어들은 함께 믹싱됩니다. cutFrom/cutTo가 설정된 경우 해당 구간은 clip.duration에 맞게 속도가 조절됩니다. 속도 조절은 0.5x에서 100x 사이로 제한됩니다.&lt;/p&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;path&lt;/td&gt;
&lt;td&gt;오디오 파일 경로&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cutFrom&lt;/td&gt;
&lt;td&gt;자르기 시작 시간&lt;/td&gt;
&lt;td&gt;0초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cutTo&lt;/td&gt;
&lt;td&gt;자르기 종료 시간&lt;/td&gt;
&lt;td&gt;clip.duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixVolume&lt;/td&gt;
&lt;td&gt;다른 오디오와 믹싱 시 상대 볼륨&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;분리된 오디오 레이어&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;detached-audio&lt;/code&gt;는 &lt;code&gt;audioTracks&lt;/code&gt;의 특별한 경우로, 전체 시작 시간을 계산할 필요 없이 클립의 시작 시간을 기준으로 오디오를 시작할 수 있게 해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;detached-audio&lt;/code&gt;는 &lt;code&gt;audioTracks&lt;/code&gt;와 동일한 속성을 가지고 있지만, &lt;code&gt;start&lt;/code&gt; 시간이 전체 영상이 아닌 해당 클립의 시작을 기준으로 계산된다는 점이 다릅니다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;detached-audio&lt;/code&gt;는 개별 클립 단위로 오디오 시작 시간을 더 쉽게 제어할 수 있게 해주는 레이어 타입입니다.&lt;/p&gt;
&lt;h3&gt;이미지 레이어&lt;/h3&gt;
&lt;p&gt;전체 화면 이미지를 위한 레이어입니다.&lt;/p&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;path&lt;/td&gt;
&lt;td&gt;이미지 파일 경로&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resizeMode&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;p&gt;Ken Burns 매개변수도 참조하세요.&lt;/p&gt;
&lt;h2&gt;레이어 유형 상세&lt;/h2&gt;
&lt;h3&gt;이미지 오버레이 레이어&lt;/h3&gt;
&lt;p&gt;화면에 사용자 지정 위치와 크기로 이미지를 오버레이합니다. 참고: 애니메이션 GIF를 사용하려면 대신 비디오를 사용하세요.&lt;/p&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;path&lt;/td&gt;
&lt;td&gt;이미지 파일 경로&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;position&lt;/td&gt;
&lt;td&gt;위치 매개변수 참조&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;width&lt;/td&gt;
&lt;td&gt;너비 (0~1, 1은 화면 너비)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;height&lt;/td&gt;
&lt;td&gt;높이 (0~1, 1은 화면 높이)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Ken Burns 매개변수도 참조하세요.&lt;/p&gt;
&lt;h3&gt;제목 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;fontPath: defaults.layer.fontPath 참조&lt;/li&gt;
&lt;li&gt;text: 표시할 제목 텍스트 (짧게 유지)&lt;/li&gt;
&lt;li&gt;textColor: 기본값 #ffffff&lt;/li&gt;
&lt;li&gt;position: 위치 매개변수 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ken Burns 매개변수도 참조하세요.&lt;/p&gt;
&lt;h3&gt;자막 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;fontPath: defaults.layer.fontPath 참조&lt;/li&gt;
&lt;li&gt;text: 표시할 자막 텍스트&lt;/li&gt;
&lt;li&gt;textColor: 기본값 #ffffff&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;제목 배경 레이어&lt;/h3&gt;
&lt;p&gt;배경이 있는 제목&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;text: 제목 유형 참조&lt;/li&gt;
&lt;li&gt;textColor: 제목 유형 참조&lt;/li&gt;
&lt;li&gt;background: { type, ... } - radial-gradient, linear-gradient 또는 fill-color 유형 참조&lt;/li&gt;
&lt;li&gt;fontPath: 제목 유형 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;뉴스 제목 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;fontPath: defaults.layer.fontPath 참조&lt;/li&gt;
&lt;li&gt;text: 텍스트&lt;/li&gt;
&lt;li&gt;textColor: 기본값 #ffffff&lt;/li&gt;
&lt;li&gt;backgroundColor: 기본값 #d02a42&lt;/li&gt;
&lt;li&gt;position: 위치 매개변수 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;슬라이드인 텍스트 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;fontPath: defaults.layer.fontPath 참조&lt;/li&gt;
&lt;li&gt;text: 텍스트&lt;/li&gt;
&lt;li&gt;fontSize: 글꼴 크기&lt;/li&gt;
&lt;li&gt;charSpacing: 문자 간격&lt;/li&gt;
&lt;li&gt;color: 색상&lt;/li&gt;
&lt;li&gt;position: 위치 매개변수 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;단색 채우기, 일시 정지 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;color: 배경을 채울 색상, 기본값: 무작위&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;방사형 그라데이션 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;colors: 두 색상의 배열, 기본값: 무작위&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;선형 그라데이션 레이어&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;colors: 두 색상의 배열, 기본값: 무작위&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;무지개 색상 레이어&lt;/h3&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;캔버스 레이어&lt;/h3&gt;
&lt;p&gt;customCanvas.js 참조&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;func: 사용자 지정 JavaScript 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fabric 레이어&lt;/h3&gt;
&lt;p&gt;customFabric.js 참조&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;func: 사용자 지정 JavaScript 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GL 레이어&lt;/h3&gt;
&lt;p&gt;GLSL 셰이더를 로드합니다. gl.json5와 rainbow-colors.frag 참조&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fragmentPath: 프래그먼트 셰이더 경로&lt;/li&gt;
&lt;li&gt;vertexPath: 버텍스 셰이더 경로 (선택사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;임의의 오디오 트랙&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;audioTracks 속성은 최종 영상에서 임의의 시점에 시작할 수 있는 오디오 트랙 목록을 선택적으로 포함할 수 있습니다. 이러한 트랙들은 함께 믹싱되며 (mixVolume은 다른 트랙들과 비교하여 각 트랙의 상대적인 볼륨을 지정합니다). 클립의 오디오는 audioTracks와 별도로 믹싱되기 때문에, clipsAudioVolume은 audioTracks의 각 오디오 트랙 볼륨과 비교하여 클립에서 결합된 오디오의 볼륨을 지정합니다.&lt;/p&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;설명&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;audioTracks[].path&lt;/td&gt;
&lt;td&gt;이 트랙의 파일 경로&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioTracks[].mixVolume&lt;/td&gt;
&lt;td&gt;이 트랙의 상대적 볼륨&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioTracks[].cutFrom&lt;/td&gt;
&lt;td&gt;원본 파일을 자르기 시작할 시간 값&lt;/td&gt;
&lt;td&gt;0초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioTracks[].cutTo&lt;/td&gt;
&lt;td&gt;원본 파일을 자를 끝 지점의 시간 값&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;audioTracks[].start&lt;/td&gt;
&lt;td&gt;이 오디오 트랙을 시작할 비디오 내 시점(초)&lt;/td&gt;
&lt;td&gt;0초&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;audioTracks와 &amp;#39;audio&amp;#39; 타입 레이어의 차이점은 audioTracks가 여러 클립에 걸쳐 계속 재생되며 필요할 때 언제든지 시작하고 멈출 수 있다는 것입니다.audioTracks 예제를 참조하세요.&amp;#39;detached-audio&amp;#39; 레이어 타입도 참조하세요.&lt;/p&gt;
&lt;h2&gt;오디오 정규화와 크기 조정&lt;/h2&gt;
&lt;h3&gt;오디오 정규화&lt;/h3&gt;
&lt;p&gt;최종 출력 오디오의 정규화를 활성화할 수 있습니다. 이는 오디오 더킹(예: 보이스오버가 나올 때 다른 트랙의 볼륨을 자동으로 낮추기)을 구현할 때 유용합니다.&lt;/p&gt;
&lt;p&gt;audioNorm 매개변수는 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;audioNorm.enable: 오디오 정규화 활성화 여부&lt;/li&gt;
&lt;li&gt;audioNorm.gaussSize: 가우스 크기 설정&lt;/li&gt;
&lt;li&gt;audioNorm.maxGain: 최대 게인 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;크기 조정 모드&lt;/h3&gt;
&lt;p&gt;resizeMode - 화면에 맞추는 방법을 지정합니다. 다음 중 하나를 선택할 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;contain: 모든 비디오가 프레임 내에 포함되며 레터박스 처리됨&lt;/li&gt;
&lt;li&gt;contain-blur: contain과 유사하지만 레터박스에 블러 처리된 복사본 사용&lt;/li&gt;
&lt;li&gt;cover: 전체 화면을 덮도록 비디오가 잘림 (화면 비율 유지)&lt;/li&gt;
&lt;li&gt;stretch: 전체 화면을 덮도록 비디오가 늘어남 (화면 비율 무시)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;기본값은 contain-blur입니다.&lt;/p&gt;
&lt;h3&gt;위치 매개변수&lt;/h3&gt;
&lt;p&gt;특정 레이어는 position 매개변수를 지원합니다.&lt;/p&gt;
&lt;p&gt;position은 다음 중 하나일 수 있습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;미리 정의된 위치: top, bottom, center, top-left, top-right, center-left, center-right, bottom-left, bottom-right&lt;/li&gt;
&lt;li&gt;객체 형식: { x, y, originX = &amp;#39;left&amp;#39;, originY = &amp;#39;top&amp;#39; }&lt;ul&gt;
&lt;li&gt;{x: 0, y: 0}은 화면 좌상단&lt;/li&gt;
&lt;li&gt;{x: 1, y: 1}은 화면 우하단&lt;/li&gt;
&lt;li&gt;x는 비디오 너비에 대한 상대값&lt;/li&gt;
&lt;li&gt;y는 비디오 높이에 대한 상대값&lt;/li&gt;
&lt;li&gt;originX와 originY는 선택사항이며 객체의 기준점(앵커 위치)을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Ken Burns 매개변수&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;zoomDirection&lt;/td&gt;
&lt;td&gt;Ken Burns 효과의 줌 방향: in, out, left, right 또는 비활성화는 null&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zoomAmount&lt;/td&gt;
&lt;td&gt;Ken Burns 효과의 줌 양&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</description>
      <category>개발</category>
      <category>nodejs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/436</guid>
      <comments>https://syaku.tistory.com/436#entry436comment</comments>
      <pubDate>Tue, 19 Nov 2024 21:40:12 +0900</pubDate>
    </item>
    <item>
      <title>n8n - 워크플로우 자동화 도구</title>
      <link>https://syaku.tistory.com/435</link>
      <description>&lt;h1&gt;n8n - 워크플로우 자동화 도구&lt;/h1&gt;
&lt;p&gt;n8n은 오픈소스 워크플로우 자동화 도구로, Node.js와 TypeScript를 기반으로 개발되었습니다. Zapier나 Make(구 Integromat)와 같은 워크플로우 자동화 도구의 대안으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;github - &lt;a href=&quot;https://github.com/n8n-io&quot;&gt;https://github.com/n8n-io&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;기술 스택&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;주요 구성요소:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript/Node.js 기반 백엔드&lt;/li&gt;
&lt;li&gt;Vue.js 기반 프론트엔드&lt;/li&gt;
&lt;li&gt;Express 웹 프레임워크&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker Compose로 설치하기&lt;/h2&gt;
&lt;h3&gt;1. 작업 디렉토리 및 볼륨 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir n8n
cd n8n

// 볼륨 생성
docker volume create n8n_data&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. docker-compose.yml 파일 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;services:
  n8n:
    image: n8nio/n8n:latest
    ports:
      - &amp;quot;5678:5678&amp;quot;
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=user
      - N8N_BASIC_AUTH_PASSWORD=password
      - GENERIC_TIMEZONE=Asia/Seoul
      - TZ=Asia/Seoul
    volumes:
      - n8n_data:/home/node/.n8n
    restart: unless-stopped
volumes:
  n8n_data:
    external: true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;최신 Docker Compose에서는 &lt;strong&gt;&lt;code&gt;version&lt;/code&gt;&lt;/strong&gt; 속성이 더 이상 필요하지 않게 되었습니다. 이전에는 Compose 파일의 스키마 버전을 지정하기 위해 사용되었지만, 현재는 불필요합니다.&lt;/p&gt;
&lt;h3&gt;3. 실행 및 관리&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 시작
docker-compose up -d

# 중지
docker-compose down

# 재시작
docker-compose restart&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;주요 특징&lt;/h2&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;: 400개 이상의 서비스와 연동 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스텀 기능&lt;/strong&gt;: JavaScript/TypeScript로 사용자 정의 노드 개발 가능&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;활용 사례&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;API 통합 및 데이터 동기화&lt;/li&gt;
&lt;li&gt;데이터 변환 및 가공&lt;/li&gt;
&lt;li&gt;자동화된 알림 시스템 구축&lt;/li&gt;
&lt;li&gt;정기적인 데이터 백업&lt;/li&gt;
&lt;li&gt;소셜 미디어 자동화&lt;/li&gt;
&lt;li&gt;이메일 마케팅 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;보안 설정&lt;/h2&gt;
&lt;p&gt;기본 인증을 활성화하여 무단 접근을 방지할 수 있습니다. docker-compose.yml의 환경변수에서 다음 항목을 설정하세요:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;N8N_BASIC_AUTH_ACTIVE&lt;/li&gt;
&lt;li&gt;N8N_BASIC_AUTH_USER&lt;/li&gt;
&lt;li&gt;N8N_BASIC_AUTH_PASSWORD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;설치가 완료되면 웹 브라우저에서 localhost:5678로 접속하여 워크플로우를 구성할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;n8n 파일 권한 보안 오류와 해결 방법&lt;/h1&gt;
&lt;p&gt;n8n에서 발생하는 파일 권한 관련 보안 오류에 대해 설명하고 이를 해결하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;권한 문제 개요&lt;/h2&gt;
&lt;p&gt;n8n 설정 파일(&lt;code&gt;/home/node/.n8n/config&lt;/code&gt;)의 권한이 0644로 설정되어 있어 너무 개방적이라는 경고가 발생합니다. 이러한 권한 설정은 다른 사용자들이 설정 파일을 읽을 수 있게 허용하므로 민감한 정보가 포함된 경우 보안 위험이 될 수 있습니다.&lt;/p&gt;
&lt;h2&gt;권한 설정 이해하기&lt;/h2&gt;
&lt;p&gt;현재 권한(0644)의 의미:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;소유자: 읽기와 쓰기 가능 (6)&lt;/li&gt;
&lt;li&gt;그룹: 읽기만 가능 (4)&lt;/li&gt;
&lt;li&gt;기타 사용자: 읽기만 가능 (4)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;해결 방법&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;방법 1: 자동 권한 적용 (권장)&lt;/strong&gt;&lt;br&gt;다음 환경 변수를 설정하여 올바른 파일 권한을 자동으로 적용할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;방법 2: 권한 검사 비활성화&lt;/strong&gt;&lt;br&gt;현재 권한을 유지하고 경고를 무시하려면 다음과 같이 설정합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;방법 3: 수동 권한 수정&lt;/strong&gt;&lt;br&gt;다음 명령어로 파일 권한을 더 제한적으로 변경할 수 있습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod 600 /home/node/.n8n/config&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;보안 모범 사례&lt;/h2&gt;
&lt;p&gt;보안을 위해 다음 사항들을 권장합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true&lt;/code&gt; 환경 변수를 설정하여 자동 권한 적용 사용&lt;/li&gt;
&lt;li&gt;민감한 정보가 포함된 설정 파일은 소유자만 읽을 수 있도록 제한&lt;/li&gt;
&lt;li&gt;n8n의 보안 모범 사례 준수&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;추가 참고사항&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;권한을 변경한 후에는 n8n을 재시작해야 변경사항이 적용됩니다&lt;/li&gt;
&lt;li&gt;Docker를 사용하는 경우 볼륨 마운트 지점의 권한도 확인해야 합니다&lt;/li&gt;
&lt;li&gt;설정 파일에는 데이터베이스 자격 증명과 같은 민감한 정보가 포함될 수 있으므로 적절한 권한 설정이 매우 중요합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 보안 설정을 통해 n8n 설정 파일을 안전하게 관리할 수 있으며, 잠재적인 보안 위험을 최소화할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;n8n의 클러스터링 구성 가이드&lt;/h1&gt;
&lt;p&gt;n8n은 대규모 워크플로우 처리와 고가용성을 위한 클러스터링을 지원합니다. 클러스터링을 통해 워크로드를 분산하고 시스템의 안정성을 높일 수 있습니다.&lt;/p&gt;
&lt;h2&gt;클러스터링 아키텍처&lt;/h2&gt;
&lt;h3&gt;큐 모드 클러스터링&lt;/h3&gt;
&lt;p&gt;큐 모드는 워크플로우 실행을 여러 워커 노드에 분산하는 방식입니다.&lt;/p&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;li&gt;Redis: 큐 시스템으로 활용&lt;/li&gt;
&lt;li&gt;PostgreSQL: 중앙 데이터 저장소&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;멀티 메인 구성 (엔터프라이즈)&lt;/h3&gt;
&lt;p&gt;고가용성을 위한 멀티 메인 설정을 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주요 특징:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;팔로워 프로세스: API 요청 처리, UI 제공, 웹훅 수신&lt;/li&gt;
&lt;li&gt;리더 프로세스: 타이머 실행, 폴링 작업, 메시징 처리&lt;/li&gt;
&lt;li&gt;로드 밸런서를 통한 트래픽 분산&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker Compose로 클러스터 구성하기&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;services:
  n8n-main:
    image: n8nio/n8n
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=password
      - QUEUE_BULL_REDIS_HOST=redis
      - N8N_MODE=main
    ports:
      - &amp;quot;5678:5678&amp;quot;
    depends_on:
      - postgres
      - redis

  n8n-worker:
    image: n8nio/n8n
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=password
      - QUEUE_BULL_REDIS_HOST=redis
      - N8N_MODE=worker
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:13
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:6-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;클러스터 구성 시 고려사항&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 데이터베이스 설정&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL을 사용하여 워크플로우 데이터 중앙 관리&lt;/li&gt;
&lt;li&gt;적절한 백업 전략 수립 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 큐 시스템&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redis를 사용한 효율적인 작업 분배&lt;/li&gt;
&lt;li&gt;워커 노드 간 작업 분산 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 네트워크 구성&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;p&gt;&lt;strong&gt;4. 모니터링&lt;/strong&gt;&lt;/p&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;h2&gt;확장성 관리&lt;/h2&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;p&gt;&lt;strong&gt;수직적 확장:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;개별 노드의 리소스 할당량 증가&lt;/li&gt;
&lt;li&gt;데이터베이스 및 Redis 성능 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;운영 팁&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;모든 노드는 동일한 n8n 버전 사용&lt;/li&gt;
&lt;li&gt;정기적인 백업 구성&lt;/li&gt;
&lt;li&gt;시스템 모니터링 도구 활용&lt;/li&gt;
&lt;li&gt;장애 복구 계획 수립&lt;/li&gt;
&lt;li&gt;정기적인 성능 테스트 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 클러스터 구성을 통해 n8n은 대규모 워크플로우 처리와 고가용성을 제공하며, 비즈니스 요구사항에 맞춰 유연하게 확장할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;n8n의 실행 모드 비교: 일반 모드 vs 클러스터 모드&lt;/h1&gt;
&lt;p&gt;n8n은 사용 환경과 규모에 따라 두 가지 실행 모드를 제공합니다. 각 모드의 특징과 차이점을 살펴보겠습니다.&lt;/p&gt;
&lt;h2&gt;일반(Regular) 모드&lt;/h2&gt;
&lt;p&gt;일반 모드는 단일 인스턴스로 실행되는 기본 설정입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;기본 구성:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;단일 n8n 인스턴스&lt;/li&gt;
&lt;li&gt;SQLite 데이터베이스 (기본값)&lt;/li&gt;
&lt;li&gt;내장 큐 시스템&lt;/li&gt;
&lt;li&gt;단일 프로세스에서 모든 작업 처리&lt;/li&gt;
&lt;/ul&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;li&gt;소규모 워크플로우에 적합&lt;/li&gt;
&lt;li&gt;별도의 데이터베이스 서버 불필요&lt;/li&gt;
&lt;/ul&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;li&gt;동시 처리 능력 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;클러스터(Queue) 모드&lt;/h2&gt;
&lt;p&gt;클러스터 모드는 대규모 워크플로우 처리와 고가용성을 위한 설정입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;필수 구성 요소:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL 데이터베이스&lt;/li&gt;
&lt;li&gt;Redis 메시지 브로커&lt;/li&gt;
&lt;li&gt;메인 노드와 워커 노드 구성&lt;/li&gt;
&lt;/ul&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;li&gt;고가용성 지원&lt;/li&gt;
&lt;li&gt;장애 복구 기능&lt;/li&gt;
&lt;li&gt;대규모 워크플로우 처리 가능&lt;/li&gt;
&lt;/ul&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;li&gt;운영 비용 증가&lt;/li&gt;
&lt;li&gt;모니터링 필요성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;모드 선택 가이드&lt;/h2&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;li&gt;적은 수의 워크플로우 실행&lt;/li&gt;
&lt;li&gt;간단한 자동화 작업&lt;/li&gt;
&lt;/ul&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;li&gt;고가용성 필요&lt;/li&gt;
&lt;li&gt;많은 동시 실행 필요&lt;/li&gt;
&lt;li&gt;확장 가능성 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;주의사항&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;일반 모드에서는 절대로 여러 인스턴스를 하나의 데이터베이스에 연결하지 마세요&lt;/li&gt;
&lt;li&gt;클러스터 모드로 전환 시 데이터 마이그레이션 계획이 필요합니다&lt;/li&gt;
&lt;li&gt;모든 노드는 동일한 n8n 버전을 사용해야 합니다&lt;/li&gt;
&lt;li&gt;적절한 모니터링과 백업 전략이 필요합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 특징들을 고려하여 사용 환경과 요구사항에 맞는 모드를 선택하면 됩니다.&lt;/p&gt;
&lt;h1&gt;그외 참고&lt;/h1&gt;
&lt;h2&gt;Docker의 볼륨(Volume)과 바인드 마운트(Bind Mount) 비교&lt;/h2&gt;
&lt;p&gt;Docker에서 데이터를 저장하는 두 가지 주요 방식의 차이점을 설명드리겠습니다.&lt;/p&gt;
&lt;h3&gt;볼륨(Volume)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;특징:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker가 완벽하게 관리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt; 디렉토리에 저장&lt;/li&gt;
&lt;li&gt;Docker CLI 명령어로 관리 가능&lt;/li&gt;
&lt;li&gt;백업과 마이그레이션이 용이&lt;/li&gt;
&lt;li&gt;Linux와 Windows 컨테이너 모두 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;성능이 더 우수함 (특히 Mac, Windows에서)&lt;/li&gt;
&lt;li&gt;여러 컨테이너 간 안전한 공유 가능&lt;/li&gt;
&lt;li&gt;Docker API를 통한 관리 가능&lt;/li&gt;
&lt;li&gt;컨테이너의 크기를 증가시키지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;바인드 마운트(Bind Mount)&lt;/h3&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;li&gt;호스트 OS의 디렉토리 구조에 종속적&lt;/li&gt;
&lt;li&gt;Docker CLI로 직접 관리 불가&lt;/li&gt;
&lt;/ul&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;li&gt;호스트와 컨테이너 간 즉각적인 파일 공유 필요 시&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;권장 사용 시나리오&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;볼륨 사용:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  app:
    volumes:
      - data_volume:/app/data

volumes:
  data_volume:&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;바인드 마운트 사용:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  app:
    volumes:
      - ./config:/app/config
      - ./src:/app/src&lt;/code&gt;&lt;/pre&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;p&gt;Docker는 일반적으로 볼륨 사용을 권장하지만, 사용 사례에 따라 적절한 방식을 선택하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>n8n</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/435</guid>
      <comments>https://syaku.tistory.com/435#entry435comment</comments>
      <pubDate>Tue, 19 Nov 2024 21:38:07 +0900</pubDate>
    </item>
    <item>
      <title>Select vs Selector: 네이밍 가이드</title>
      <link>https://syaku.tistory.com/434</link>
      <description>&lt;p&gt;개발에서 Select와 Selector의 네이밍 차이점에 대해 명확하게 정리하겠습니다.&lt;/p&gt;
&lt;h3&gt;Select의 사용&lt;/h3&gt;
&lt;p&gt;Select는 단순한 선택 행위나 기능을 나타낼 때 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;적절한 사용 사례:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TemplateSelect: 템플릿 선택 드롭다운&lt;/li&gt;
&lt;li&gt;UserSelect: 사용자 선택 컴포넌트&lt;/li&gt;
&lt;li&gt;DateSelect: 날짜 선택 컴포넌트&lt;/li&gt;
&lt;/ul&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;li&gt;주로 드롭다운이나 라디오 버튼 같은 단순 선택 UI&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Selector의 사용&lt;/h3&gt;
&lt;p&gt;Selector는 선택을 위한 도구나 복잡한 선택 메커니즘을 의미합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;적절한 사용 사례:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ColorSelector: 색상 선택 도구&lt;/li&gt;
&lt;li&gt;FileSelector: 파일 선택 도구&lt;/li&gt;
&lt;li&gt;ThemeSelector: 테마 선택 도구&lt;/li&gt;
&lt;/ul&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;li&gt;여러 기능이 통합된 UI 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;구분 기준&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Select 사용 조건:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;단순 선택 기능&lt;/li&gt;
&lt;li&gt;드롭다운 형태의 UI&lt;/li&gt;
&lt;li&gt;즉각적인 선택이 가능한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Selector 사용 조건:&lt;/strong&gt;&lt;/p&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;h2&gt;실제 적용 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// Select 예시 - 단순 선택
const TemplateSelect = () =&amp;gt; (
  &amp;lt;Select&amp;gt;
    &amp;lt;Option value=&amp;quot;1&amp;quot;&amp;gt;템플릿 1&amp;lt;/Option&amp;gt;
    &amp;lt;Option value=&amp;quot;2&amp;quot;&amp;gt;템플릿 2&amp;lt;/Option&amp;gt;
  &amp;lt;/Select&amp;gt;
);

// Selector 예시 - 복잡한 도구
const ColorSelector = () =&amp;gt; (
  &amp;lt;div className=&amp;quot;color-selector&amp;quot;&amp;gt;
    &amp;lt;ColorPicker /&amp;gt;
    &amp;lt;ColorPalette /&amp;gt;
    &amp;lt;ColorInput /&amp;gt;
    &amp;lt;RecentColors /&amp;gt;
  &amp;lt;/div&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;네이밍 결정 프로세스&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&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;/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;UI 복잡도 검토&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;/ol&gt;
&lt;p&gt;이러한 구분을 통해 더 명확하고 일관된 네이밍 컨벤션을 유지할 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>네이밍</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/434</guid>
      <comments>https://syaku.tistory.com/434#entry434comment</comments>
      <pubDate>Sun, 17 Nov 2024 14:25:15 +0900</pubDate>
    </item>
    <item>
      <title>TTS(Text-to-Speech) 기술 종합 가이드</title>
      <link>https://syaku.tistory.com/433</link>
      <description>&lt;h1&gt;TTS(Text-to-Speech) 기술 종합 가이드&lt;/h1&gt;
&lt;h2&gt;1. TTS 기술의 기본 이해&lt;/h2&gt;
&lt;h3&gt;음성 합성의 핵심 구성요소&lt;/h3&gt;
&lt;ol&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;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;li&gt;보코더를 통한 음질 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;주요 TTS 모델 아키텍처&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Tacotron 2&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;구조: 인코더-디코더 아키텍처&lt;/li&gt;
&lt;li&gt;특징: LSTM과 컨볼루션 레이어 사용&lt;/li&gt;
&lt;li&gt;장점: 매우 자연스러운 음성 생성&lt;/li&gt;
&lt;li&gt;단점: 느린 생성 속도&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;FastSpeech 2&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;구조: 피드포워드 트랜스포머&lt;/li&gt;
&lt;li&gt;특징: 병렬 처리 가능&lt;/li&gt;
&lt;li&gt;장점: 빠른 생성 속도, 음성 특성 직접 제어&lt;/li&gt;
&lt;li&gt;단점: Tacotron 2 대비 약간 낮은 자연스러움&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MelGAN&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;역할: 보코더(멜 스펙트로그램 → 실제 음성)&lt;/li&gt;
&lt;li&gt;특징: GAN 기반 구조&lt;/li&gt;
&lt;li&gt;장점: 빠른 추론 속도, 고품질 음성&lt;/li&gt;
&lt;li&gt;단점: 학습이 불안정할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 주요 TTS 라이브러리 상세 분석&lt;/h2&gt;
&lt;h3&gt;Coqui TTS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;기술적 특징&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 기본 사용 예시
from TTS.api import TTS

# 모델 초기화
tts = TTS(model_name=&amp;quot;tts_models/multilingual/multi-dataset/xtts_v2&amp;quot;)

# 텍스트를 음성으로 변환
tts.tts_to_file(
    text=&amp;quot;안녕하세요, 반갑습니다.&amp;quot;,
    file_path=&amp;quot;output.wav&amp;quot;,
    speaker=speaker_name,
    language=language_code
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;지원 기능&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다국어 지원 (1100개 이상)&lt;/li&gt;
&lt;li&gt;음성 복제 (3초 샘플로 새로운 화자 생성)&lt;/li&gt;
&lt;li&gt;감정 표현 제어&lt;/li&gt;
&lt;li&gt;실시간 음성 합성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;내장 서버 기능&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 서버 실행
tts-server --model_name &amp;quot;tts_models/multilingual/multi-dataset/xtts_v2&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TensorFlowTTS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;개발 환경 설정&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 환경 설정
conda create -n tftts python=3.7
conda activate tftts

# 설치
git clone https://github.com/TensorSpeech/TensorFlowTTS.git
cd TensorFlowTTS
pip install .&lt;/code&gt;&lt;/pre&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;GPU 가속 지원&lt;/li&gt;
&lt;li&gt;모바일 최적화&lt;/li&gt;
&lt;li&gt;TFLite 변환 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 실제 구현 시 고려사항&lt;/h2&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;Coqui TTS&lt;/th&gt;
&lt;th&gt;TensorFlowTTS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Python 버전&lt;/td&gt;
&lt;td&gt;3.7-3.11&lt;/td&gt;
&lt;td&gt;3.7+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU 필수여부&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;사용 시나리오별 추천&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;빠른 프로토타입 개발&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;추천: Coqui TTS&lt;/li&gt;
&lt;li&gt;이유: 즉시 사용 가능한 API 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;커스텀 TTS 시스템 개발&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;추천: TensorFlowTTS&lt;/li&gt;
&lt;li&gt;이유: 세밀한 제어와 최적화 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 성능 최적화 팁&lt;/h2&gt;
&lt;h3&gt;Coqui TTS 최적화&lt;/h3&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;h3&gt;TensorFlowTTS 최적화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;GPU 메모리 관리&lt;/li&gt;
&lt;li&gt;모델 양자화&lt;/li&gt;
&lt;li&gt;배치 크기 조정&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>TTS</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/433</guid>
      <comments>https://syaku.tistory.com/433#entry433comment</comments>
      <pubDate>Fri, 15 Nov 2024 01:52:23 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript - Java의 enum을 대체하는 자바스크립트의 Object.freeze() 완벽 가이드</title>
      <link>https://syaku.tistory.com/432</link>
      <description>&lt;h1&gt;[JavaScript] Java의 enum을 대체하는 Object.freeze() 완벽 가이드&lt;/h1&gt;
&lt;p&gt;자바스크립트에서 자바의 enum과 같은 상수를 관리하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;Object.freeze()를 사용한 기본 구현&lt;/h2&gt;
&lt;p&gt;자바스크립트에서는 enum 타입이 없지만, Object.freeze()를 사용하여 비슷한 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const Season = {
    SPRING: &amp;quot;spring&amp;quot;,
    SUMMER: &amp;quot;summer&amp;quot;, 
    AUTUMN: &amp;quot;autumn&amp;quot;,
    WINTER: &amp;quot;winter&amp;quot;
};

Object.freeze(Season);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Object.freeze()의 특징&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 불변성 보장&lt;/strong&gt;&lt;/p&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;p&gt;&lt;strong&gt;2. 사용 이점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드의 가독성 향상&lt;/li&gt;
&lt;li&gt;타입 안정성 제공&lt;/li&gt;
&lt;li&gt;IDE 자동완성 지원&lt;/li&gt;
&lt;li&gt;오타나 실수 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Object.freeze()의 한계: 얕은 동결&lt;/h2&gt;
&lt;p&gt;Object.freeze()의 주요 한계점은 얕은 동결(shallow freeze)만 제공한다는 것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const nested = {
    outer: {
        inner: &amp;quot;value&amp;quot;
    }
};

Object.freeze(nested);
nested.outer.inner = &amp;quot;changed&amp;quot;; // 변경 가능!&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;deepFreeze 구현으로 완벽한 동결 만들기&lt;/h2&gt;
&lt;p&gt;중첩된 객체까지 완벽하게 동결하기 위해서는 deepFreeze를 구현해야 합니다[4].&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function deepFreeze(object) {
    Object.keys(object).forEach(prop =&amp;gt; {
        if (typeof object[prop] === &amp;#39;object&amp;#39; &amp;amp;&amp;amp; !Object.isFrozen(object[prop])) {
            deepFreeze(object[prop]);
        }
    });
    return Object.freeze(object);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;실제 사용 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const PaymentType = {
    CREDIT_CARD: {
        code: &amp;quot;CC&amp;quot;,
        description: &amp;quot;신용카드&amp;quot;
    },
    BANK_TRANSFER: {
        code: &amp;quot;BT&amp;quot;,
        description: &amp;quot;계좌이체&amp;quot;
    }
};

deepFreeze(PaymentType);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Object.seal()과의 차이점&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;Object.freeze()&lt;/th&gt;
&lt;th&gt;Object.seal()&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;h2&gt;사용 시 주의사항&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;strict 모드에서는 수정 시도 시 TypeError가 발생합니다&lt;/li&gt;
&lt;li&gt;중첩 객체가 있는 경우 반드시 deepFreeze 사용을 고려해야 합니다&lt;/li&gt;
&lt;li&gt;성능에 민감한 경우 사용을 신중히 검토해야 합니다&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;Object.freeze()는 자바의 enum을 대체할 수 있는 효과적인 방법이지만, 깊은 동결이 필요한 경우에는 deepFreeze를 구현하여 사용하는 것이 안전합니다. 상수 관리와 코드의 안정성을 높이는데 매우 유용한 도구입니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/432</guid>
      <comments>https://syaku.tistory.com/432#entry432comment</comments>
      <pubDate>Sat, 9 Nov 2024 15:57:04 +0900</pubDate>
    </item>
    <item>
      <title>nodejs - 효율적인 에러 처리: try-catch 사용 전략</title>
      <link>https://syaku.tistory.com/431</link>
      <description>&lt;h2&gt;JavaScript/nodejs 의 효율적인 에러 처리: try-catch 사용 전략&lt;/h2&gt;
&lt;h3&gt;1. 들어가며&lt;/h3&gt;
&lt;p&gt;Java와 달리 JavaScript/Node.js에서는 모든 코드를 try 블록으로 감싸지 않아도 됩니다. 이는 JavaScript의 특성과 최신 개발 패러다임을 반영한 것으로, 더 효율적인 에러 처리를 가능하게 합니다.&lt;/p&gt;
&lt;h3&gt;2. JavaScript의 에러 처리 특징&lt;/h3&gt;
&lt;h4&gt;2.1 빠른 실패(Fail Fast) 원칙&lt;/h4&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;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function processUser(user) {
  // 빠른 실패: 입력값 검증
  if (!user) throw new Error(&amp;#39;User is required&amp;#39;);
  if (!user.name) throw new Error(&amp;#39;User name is required&amp;#39;);

  // 메인 로직
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2 예외와 에러의 구분&lt;/h4&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;li&gt;네트워크 연결 상태&lt;/li&gt;
&lt;li&gt;try-catch 없이 if문으로 처리 가능&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;li&gt;try-catch로 처리 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 성능 최적화 관점&lt;/h3&gt;
&lt;h4&gt;3.1 try-catch 블록의 영향&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript 엔진의 최적화를 방해할 수 있음&lt;/li&gt;
&lt;li&gt;try 블록 내부 코드는 최적화가 제한됨&lt;/li&gt;
&lt;li&gt;필요한 곳에만 선별적으로 사용 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.2 최적화된 에러 처리 예시&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function saveDocument(data) {
  // 1. 입력값 검증 (try-catch 불필요)
  if (!data.content) {
    throw new Error(&amp;#39;Content is required&amp;#39;);
  }

  // 2. 비즈니스 로직 검증 (try-catch 불필요)
  if (data.content.length &amp;gt; MAX_CONTENT_LENGTH) {
    throw new Error(&amp;#39;Content exceeds maximum length&amp;#39;);
  }

  // 3. 외부 작업 (try-catch 필요)
  try {
    const result = await database.save(data);
    await notificationService.send(result.id);
    return result;
  } catch (error) {
    logger.error(&amp;#39;Document save failed:&amp;#39;, error);
    throw new Error(&amp;#39;Failed to save document&amp;#39;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 실제 적용 사례&lt;/h3&gt;
&lt;h4&gt;4.1 파일 처리 서비스&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const fileProcessingService = async (fileData) =&amp;gt; {
  // 1. 입력값 검증
  if (!fileData || !fileData.buffer) {
    throw new Error(&amp;#39;Invalid file data&amp;#39;);
  }

  // 2. 파일 형식 검증
  const fileType = await getFileType(fileData.buffer);
  if (!ALLOWED_TYPES.includes(fileType)) {
    throw new Error(&amp;#39;Unsupported file type&amp;#39;);
  }

  // 3. 파일 처리 (외부 작업)
  try {
    const processedFile = await processFile(fileData);
    const savedLocation = await saveToStorage(processedFile);

    return {
      location: savedLocation,
      size: processedFile.size,
      type: fileType,
      processedAt: new Date().toISOString()
    };
  } catch (error) {
    logger.error(&amp;#39;File processing failed:&amp;#39;, error);
    throw new Error(`File processing failed: ${error.message}`);
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 에러 처리 모범 사례&lt;/h3&gt;
&lt;h4&gt;5.1 커스텀 에러 클래스 활용&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = &amp;#39;ValidationError&amp;#39;;
  }
}

class DatabaseError extends Error {
  constructor(message) {
    super(message);
    this.name = &amp;#39;DatabaseError&amp;#39;;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5.2 에러 처리 미들웨어 (Express.js)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;app.use((error, req, res, next) =&amp;gt; {
  if (error instanceof ValidationError) {
    return res.status(400).json({
      error: error.message
    });
  }

  if (error instanceof DatabaseError) {
    return res.status(503).json({
      error: &amp;#39;Service temporarily unavailable&amp;#39;
    });
  }

  // 기타 예상치 못한 에러
  logger.error(&amp;#39;Unexpected error:&amp;#39;, error);
  res.status(500).json({
    error: &amp;#39;Internal server error&amp;#39;
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 결론&lt;/h3&gt;
&lt;p&gt;JavaScript에서의 효율적인 에러 처리는 예상 가능한 상황과 예상치 못한 에러를 구분하고, 적절한 위치에 try-catch를 사용하는 것이 핵심입니다. 이는 코드의 가독성을 높이고, 성능을 최적화하며, 더 견고한 애플리케이션을 만드는데 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;이러한 접근 방식은 현대적인 JavaScript 애플리케이션 개발에서 매우 중요하며, 특히 Node.js 기반의 서버 애플리케이션에서 더욱 그러합니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <category>nodejs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/431</guid>
      <comments>https://syaku.tistory.com/431#entry431comment</comments>
      <pubDate>Fri, 8 Nov 2024 10:16:23 +0900</pubDate>
    </item>
    <item>
      <title>2024년 금값 급등의 완벽한 분석: 원인과 전망</title>
      <link>https://syaku.tistory.com/430</link>
      <description>&lt;h1&gt;2024년 금값 급등의 완벽한 분석: 원인과 전망&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;금값 상승의 주요 원인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 지정학적 불안&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이스라엘-하마스 전쟁&lt;/b&gt;: 중동 지역의 불안정성이 고조되며 글로벌 원자재 시장에 직접적 영향&lt;/li&gt;
&lt;li&gt;&lt;b&gt;러시아-우크라이나 전쟁&lt;/b&gt;: 2년 이상 지속되는 전쟁으로 글로벌 공급망 불안과 원자재 가격 변동성 확대&lt;/li&gt;
&lt;li&gt;&lt;b&gt;글로벌 정치 불확실성&lt;/b&gt;: 2024년 미국 대선을 포함한 60개국 이상의 선거로 인한 정책 불확실성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 글로벌 경제 환경&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인플레이션과 금의 관계&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인플레이션 시기에 화폐 가치는 하락하지만 금의 실질 가치는 유지&lt;/li&gt;
&lt;li&gt;역사적으로 금은 인플레이션 헤지 수단으로 인정받음&lt;/li&gt;
&lt;li&gt;현재 글로벌 인플레이션율 4~5% 수준 유지로 금 수요 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;미 연준 통화정책&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금리인하 기대감으로 채권 수익률 하락&lt;/li&gt;
&lt;li&gt;실질금리 하락은 무이자 자산인 금의 매력도 증가&lt;/li&gt;
&lt;li&gt;2024년 하반기 예상되는 금리인하로 금값 추가 상승 전망&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 중앙은행들의 전략적 금 매입&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중국의 금 매입 배경&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미-중 갈등 속 달러 의존도 축소 전략&lt;/li&gt;
&lt;li&gt;위안화 국제화 지원을 위한 금 보유량 확대&lt;/li&gt;
&lt;li&gt;2023년 한 해 동안 225톤 이상 순매입 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인도의 금 매입 전략&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경제 규모 확대에 따른 외환보유고 다변화&lt;/li&gt;
&lt;li&gt;자국 통화 루피화 가치 방어&lt;/li&gt;
&lt;li&gt;문화적으로도 금에 대한 수요가 높은 특성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재 금값 동향과 시장 상황&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최근 가격 추이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역사적 고점 갱신&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2024년 트로이온스당 2,700달러 돌파&lt;/li&gt;
&lt;li&gt;2023년 대비 30% 이상 상승&lt;/li&gt;
&lt;li&gt;월간 변동성 15% 이상으로 확대&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;투자 수요 구조 변화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기관투자자 동향&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;헤지펀드의 금 선물 순매수 포지션 증가&lt;/li&gt;
&lt;li&gt;연기금의 포트폴리오 다변화 수단으로 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개인투자자 참여&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금 ETF 일평균 거래량 전년 대비 150% 증가&lt;/li&gt;
&lt;li&gt;실물 금 거래 계좌 120만개 돌파&lt;/li&gt;
&lt;li&gt;MZ세대의 안전자산 선호 현상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;향후 전망&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 투자은행 예측&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;골드만삭스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2024년 말까지 2,700달러 전망&lt;/li&gt;
&lt;li&gt;중앙은행 매입과 지정학적 리스크 지속 예상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UBS&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2024년 3분기까지 2,900달러 예상&lt;/li&gt;
&lt;li&gt;미 달러 약세와 실질금리 하락 근거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뱅크오브아메리카&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;12-18개월 내 3,000달러 돌파 전망&lt;/li&gt;
&lt;li&gt;글로벌 경기침체 우려 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;투자 방법별 상세 분석&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실물 금 투자&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실물 자산으로 안전성 최고&lt;/li&gt;
&lt;li&gt;인플레이션 직접 헤지&lt;/li&gt;
&lt;li&gt;금융시스템 리스크 회피&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보관 비용 (금고 임대료 등)&lt;/li&gt;
&lt;li&gt;거래 수수료 (1-3% 수준)&lt;/li&gt;
&lt;li&gt;유동성 제한적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구매 방법&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;은행 골드바 구매&lt;/li&gt;
&lt;li&gt;귀금속 거래소 이용&lt;/li&gt;
&lt;li&gt;순도 확인 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;금 ETF 투자&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주요 특징&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실시간 거래 가능&lt;/li&gt;
&lt;li&gt;적은 금액으로 시작 가능&lt;/li&gt;
&lt;li&gt;보관 비용 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;국내 대표 상품&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KODEX 골드선물(H): 환헤지 제공&lt;/li&gt;
&lt;li&gt;TIGER 금은선물(H): 금과 은 동시 투자&lt;/li&gt;
&lt;li&gt;연간 보수 0.5% 내외&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해외 ETF&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SPDR Gold Shares (GLD)&lt;/li&gt;
&lt;li&gt;iShares Gold Trust (IAU)&lt;/li&gt;
&lt;li&gt;거래소 및 증권사 해외주식 계좌 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;금 관련 펀드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;투자 대상&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금광업체 주식&lt;/li&gt;
&lt;li&gt;금 선물&lt;/li&gt;
&lt;li&gt;실물 금&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전문가 운용&lt;/li&gt;
&lt;li&gt;다양한 전략 활용&lt;/li&gt;
&lt;li&gt;정기 리밸런싱&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비용 구조&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;판매수수료: 0.5-1.0%&lt;/li&gt;
&lt;li&gt;운용보수: 연 1.0-1.5%&lt;/li&gt;
&lt;li&gt;성과보수 있는 경우도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리스크 관리 심층 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 위험 요인 분석&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;환율 리스크&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;달러 강세시 금값 하락 압력&lt;/li&gt;
&lt;li&gt;환헤지 상품 활용 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시장 리스크&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지정학적 리스크 완화시 급락 가능&lt;/li&gt;
&lt;li&gt;금리 인상기의 가격 조정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수급 리스크&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중앙은행 매입 정책 변화&lt;/li&gt;
&lt;li&gt;금광 생산량 변동&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고: 본 내용은 2024년 11월 기준으로 작성되었으며, 시장 상황에 따라 변동될 수 있습니다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>경제</category>
      <category>금</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/430</guid>
      <comments>https://syaku.tistory.com/430#entry430comment</comments>
      <pubDate>Thu, 7 Nov 2024 20:51:45 +0900</pubDate>
    </item>
    <item>
      <title>달러 강세의 주요 원인과 한국 경제에 미치는 영향</title>
      <link>https://syaku.tistory.com/429</link>
      <description>&lt;h2&gt;달러 강세의 주요 원인과 한국 경제에 미치는 영향&lt;/h2&gt;
&lt;h3&gt;달러가 오르는 주요 원인&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 미국 경제 요인&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;높은 기준금리&lt;/strong&gt;: 미국 연방준비제도(Fed)는 인플레이션을 억제하기 위해 고금리 정책을 유지하고 있습니다. 높은 금리는 미국 채권과 같은 자산의 수익률을 상승시키며, 글로벌 투자자들이 달러 자산을 선호하게 만듭니다.&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;strong&gt;지정학적 리스크&lt;/strong&gt;: 중동, 우크라이나 등에서의 지정학적 긴장감이 고조됨에 따라 안전자산으로서 달러에 대한 선호도가 높아지고 있습니다. 불확실한 상황에서 투자자들은 안정된 자산으로 분류되는 달러화에 자금을 투자하는 경향이 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 글로벌 요인&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;주요국 금리 차이&lt;/strong&gt;: 유럽중앙은행(ECB)과 일본은행(BOJ) 등 주요국은 상대적으로 낮은 금리를 유지하고 있습니다. 이는 자본이 금리가 높은 미국으로 이동하게 만들며, 달러 강세를 촉진합니다.&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;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;달러 강세가 한국 경제에 미치는 영향&lt;/h3&gt;
&lt;h4&gt;1. &lt;strong&gt;해외여행 및 관광&lt;/strong&gt;&lt;/h4&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;/ul&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;/ul&gt;
&lt;h4&gt;2. &lt;strong&gt;한국 경제 전반에 미치는 영향&lt;/strong&gt;&lt;/h4&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;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;물가 상승률 증가&lt;/strong&gt;: 한국은행에 따르면, 원/달러 환율이 1% 상승할 때마다 물가 상승률은 약 0.06%p 증가하는 경향이 있습니다. 이는 소비자들의 생활비 부담을 증가시키며, 가계의 실제 실질 구매력을 저하시킬 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;원자재 수입 비용 증가&lt;/strong&gt;: 그러나 많은 한국 기업들은 원자재를 해외에서 수입하기 때문에, 원자재 가격 상승으로 인해 생산 비용이 크게 증가할 수 있습니다. 이는 기업의 수익성을 저하시킬 우려가 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. &lt;strong&gt;외국인 투자자의 반응&lt;/strong&gt;&lt;/h4&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;/ul&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;: 환율 변동성은 외국인 투자자들이 한국 채권 시장에서 이탈하는 요인이 될 수 있습니다. 특히 WGBI(World Government Bond Index) 가입 여부와 같은 외국인 채권 투자의 주요 지표는 한국 채권 시장에 중요한 영향을 미칩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. &lt;strong&gt;소비자 물가 변화&lt;/strong&gt;&lt;/h4&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;/ul&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;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;결론 및 대응 방안&lt;/h3&gt;
&lt;p&gt;달러 강세는 한국 경제에 다면적인 영향을 미칩니다. 수출기업에게는 긍정적인 영향을 미치지만, 수입 물가 상승, 자본 유출, 소비 감소와 같은 부정적인 요소들도 존재합니다. 이에 따라 정부와 기업은 다음과 같은 대응책을 마련해야 합니다:&lt;/p&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;: 기업들은 환율 변동에 대비해 환율 헤지(hedging) 전략을 강화하고, 외화 부채를 줄이는 등 환리스크 관리에 주력해야 합니다. 이를 통해 환율 변동으로 인한 재무적 리스크를 최소화할 수 있습니다.&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;strong&gt;에너지 및 원자재 수입 다변화&lt;/strong&gt;: 에너지 및 원자재 수입처를 다변화하여 특정 국가나 통화에 대한 의존도를 줄이는 것도 중요한 대응책이 될 수 있습니다. 이는 환율 변동에 따른 물가 상승을 억제하는 데 기여할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;위와 같은 대응책을 통해 한국 경제는 달러 강세에 따른 부정적인 영향을 최소화하고, 기회를 극대화할 수 있을 것입니다.&lt;/p&gt;</description>
      <category>경제</category>
      <category>달러</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/429</guid>
      <comments>https://syaku.tistory.com/429#entry429comment</comments>
      <pubDate>Wed, 6 Nov 2024 17:26:40 +0900</pubDate>
    </item>
    <item>
      <title>Reactjs - useEffect 와 의존성 배열 완벽 가이드</title>
      <link>https://syaku.tistory.com/428</link>
      <description>&lt;h1&gt;React의 useEffect와 의존성 배열 완벽 가이드&lt;/h1&gt;
&lt;p&gt;React의 useEffect는 컴포넌트의 생명주기와 관련된 부수 효과(side effects)를 처리하는 핵심 Hook입니다. 특히 의존성 배열(dependency array)의 올바른 사용은 애플리케이션의 성능과 안정성에 큰 영향을 미칩니다.&lt;/p&gt;
&lt;h2&gt;useEffect의 기본 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  // 실행할 코드

  return () =&amp;gt; {
    // 클린업 코드
  }
}, [의존성1, 의존성2, ...]);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;의존성 배열의 세 가지 사용 패턴&lt;/h2&gt;
&lt;h3&gt;1. 빈 배열 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  console.log(&amp;#39;컴포넌트 마운트 시에만 실행&amp;#39;);
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트가 처음 마운트될 때만 실행&lt;/li&gt;
&lt;li&gt;주로 초기 데이터 fetch나 이벤트 리스너 등록에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 의존성이 있는 배열&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  console.log(`count가 ${count}로 변경됨`);
}, [count]);&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;지정된 의존성(count)이 변경될 때마다 실행&lt;/li&gt;
&lt;li&gt;특정 상태나 props의 변화에 반응해야 할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 의존성 배열 생략&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  console.log(&amp;#39;매 렌더링마다 실행&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트가 리렌더링될 때마다 실행&lt;/li&gt;
&lt;li&gt;성능 문제가 발생할 수 있어 주의 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;클린업 함수의 역할&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  const subscription = api.subscribe();

  // 클린업 함수
  return () =&amp;gt; {
    subscription.unsubscribe();
  };
}, [api]);&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;메모리 누수 방지&lt;/li&gt;
&lt;li&gt;이벤트 리스너 제거&lt;/li&gt;
&lt;li&gt;이전 상태 정리&lt;/li&gt;
&lt;li&gt;다음 경우에 실행됨:&lt;ol&gt;
&lt;li&gt;컴포넌트 언마운트 시&lt;/li&gt;
&lt;li&gt;의존성이 변경되어 effect가 재실행되기 전&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;의존성 배열 사용 시 주의사항&lt;/h2&gt;
&lt;h3&gt;1. 잘못된 의존성 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 잘못된 예
useEffect(() =&amp;gt; {
  setTotal(count * price);
}, []); // count와 price가 변경되어도 total이 업데이트되지 않음

// 올바른 예
useEffect(() =&amp;gt; {
  setTotal(count * price);
}, [count, price]);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 객체와 배열 의존성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 주의가 필요한 경우
useEffect(() =&amp;gt; {
  doSomething(options);
}, [options]); // options가 객체라면 매 렌더링마다 새로 생성될 수 있음

// 해결 방법
useEffect(() =&amp;gt; {
  doSomething(options.id, options.type);
}, [options.id, options.type]); // 필요한 프로퍼티만 의존성으로 지정&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;실제 사용 예시&lt;/h2&gt;
&lt;h3&gt;1. API 호출&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  const fetchData = async () =&amp;gt; {
    setLoading(true);
    try {
      const response = await api.getData(id);
      setData(response);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  fetchData();
}, [id]); // id가 변경될 때만 API 재호출&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 이벤트 리스너&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  const handleResize = () =&amp;gt; {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
  };

  window.addEventListener(&amp;#39;resize&amp;#39;, handleResize);

  return () =&amp;gt; {
    window.removeEventListener(&amp;#39;resize&amp;#39;, handleResize);
  };
}, []); // 마운트 시에만 리스너 등록&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;성능 최적화 팁&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;필요한 의존성만 포함하기&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  // user.name만 사용한다면
}, [user.name]); // user 객체 전체 대신 필요한 속성만 의존성으로 지정&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;함수 의존성 제거하기&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;useEffect(() =&amp;gt; {
  // 함수를 effect 내부로 이동
  const handleData = () =&amp;gt; {
    // ...
  };

  handleData();
}, [필요한_의존성만]);&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;useCallback과 함께 사용&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const memoizedCallback = useCallback(() =&amp;gt; {
  doSomething(dependency);
}, [dependency]);

useEffect(() =&amp;gt; {
  memoizedCallback();
}, [memoizedCallback]);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;useEffect와 의존성 배열의 올바른 사용은 React 애플리케이션의 성능과 안정성에 큰 영향을 미칩니다. 의존성 배열을 신중하게 설정하고, 클린업 함수를 적절히 활용하며, 성능 최적화를 고려하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>reactjs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/428</guid>
      <comments>https://syaku.tistory.com/428#entry428comment</comments>
      <pubDate>Wed, 6 Nov 2024 15:10:07 +0900</pubDate>
    </item>
    <item>
      <title>Reactjs - 드래그 앤 드롭 리스트 구현하기</title>
      <link>https://syaku.tistory.com/427</link>
      <description>&lt;h1&gt;React에서 드래그 앤 드롭 리스트 구현하기 (MUI + React-DnD)&lt;/h1&gt;
&lt;p&gt;레이어 목록을 드래그하여 순서를 변경할 수 있는 컴포넌트를 구현해보겠습니다. Material-UI와 React-DnD를 활용하여 직관적인 UI와 부드러운 드래그 앤 드롭 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;필요한 패키지 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Material-UI 관련 패키지
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled

# React-DnD 관련 패키지
npm install react-dnd react-dnd-html5-backend

# 불변성 관리를 위한 패키지
npm install immutability-helper&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;주요 기능&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;드래그 앤 드롭으로 항목 순서 변경&lt;/li&gt;
&lt;li&gt;최상단/최하단 이동 버튼&lt;/li&gt;
&lt;li&gt;한 칸 위/아래 이동 버튼&lt;/li&gt;
&lt;li&gt;드래그 중인 항목의 시각적 피드백&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;컴포넌트 구조&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;LayerList&lt;/code&gt;: 메인 컴포넌트&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Layer&lt;/code&gt;: 개별 드래그 가능한 항목 컴포넌트&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;전체 코드 설명&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState, useCallback } from &amp;#39;react&amp;#39;;
import {
  List,
  ListItem,
  ListItemIcon,
  IconButton,
  Box
} from &amp;#39;@mui/material&amp;#39;;
import {
  DragIndicator,
  KeyboardDoubleArrowUp,
  KeyboardDoubleArrowDown,
  KeyboardArrowUp,
  KeyboardArrowDown
} from &amp;#39;@mui/icons-material&amp;#39;;
import { DndProvider, useDrag, useDrop } from &amp;#39;react-dnd&amp;#39;;
import { HTML5Backend } from &amp;#39;react-dnd-html5-backend&amp;#39;;
import update from &amp;#39;immutability-helper&amp;#39;;

// 드래그 가능한 아이템 컴포넌트
const Layer = ({ id, text, index, moveItem, moveToTop, moveToBottom, moveUp, moveDown }) =&amp;gt; {
  const ref = React.useRef(null);

  // 드래그 로직 설정
  const [{ isDragging }, drag] = useDrag({
    type: &amp;#39;item&amp;#39;,
    item: { id, index },
    collect: (monitor) =&amp;gt; ({
      isDragging: monitor.isDragging(),
    }),
  });

  // 드롭 로직 설정
  const [, drop] = useDrop({
    accept: &amp;#39;item&amp;#39;,
    hover: (item, monitor) =&amp;gt; {
      if (!ref.current) return;

      const dragIndex = item.index;
      const hoverIndex = index;
      if (dragIndex === hoverIndex) return;

      moveItem(dragIndex, hoverIndex);
      item.index = hoverIndex;
    },
  });

  // ref 설정으로 드래그&amp;amp;드롭 기능 활성화
  drag(drop(ref));

  return (
    &amp;lt;ListItem
      ref={ref}
      style={{
        opacity: isDragging ? 0.5 : 1,
        cursor: &amp;#39;move&amp;#39;,
        backgroundColor: &amp;#39;#fff&amp;#39;,
        marginBottom: &amp;#39;4px&amp;#39;,
        border: &amp;#39;1px solid #eee&amp;#39;,
        borderRadius: &amp;#39;4px&amp;#39;
      }}
    &amp;gt;
      {/* 왼쪽 드래그 핸들 */}
      &amp;lt;ListItemIcon&amp;gt;
        &amp;lt;DragIndicator /&amp;gt;
      &amp;lt;/ListItemIcon&amp;gt;

      {/* 아이템 내용 */}
      &amp;lt;Box sx={{ flex: 1 }}&amp;gt;{text}&amp;lt;/Box&amp;gt;

      {/* 오른쪽 이동 버튼들 */}
      &amp;lt;Box&amp;gt;
        &amp;lt;IconButton size=&amp;quot;small&amp;quot; onClick={() =&amp;gt; moveToTop(index)}&amp;gt;
          &amp;lt;KeyboardDoubleArrowUp /&amp;gt;
        &amp;lt;/IconButton&amp;gt;
        &amp;lt;IconButton size=&amp;quot;small&amp;quot; onClick={() =&amp;gt; moveUp(index)}&amp;gt;
          &amp;lt;KeyboardArrowUp /&amp;gt;
        &amp;lt;/IconButton&amp;gt;
        &amp;lt;IconButton size=&amp;quot;small&amp;quot; onClick={() =&amp;gt; moveDown(index)}&amp;gt;
          &amp;lt;KeyboardArrowDown /&amp;gt;
        &amp;lt;/IconButton&amp;gt;
        &amp;lt;IconButton size=&amp;quot;small&amp;quot; onClick={() =&amp;gt; moveToBottom(index)}&amp;gt;
          &amp;lt;KeyboardDoubleArrowDown /&amp;gt;
        &amp;lt;/IconButton&amp;gt;
      &amp;lt;/Box&amp;gt;
    &amp;lt;/ListItem&amp;gt;
  );
};

// 메인 LayerList 컴포넌트
const LayerList = () =&amp;gt; {
  // 초기 아이템 데이터
  const [items, setItems] = useState([
    { id: 1, text: &amp;#39;Item 1&amp;#39; },
    { id: 2, text: &amp;#39;Item 2&amp;#39; },
    { id: 3, text: &amp;#39;Item 3&amp;#39; },
    { id: 4, text: &amp;#39;Item 4&amp;#39; },
  ]);

  // 드래그로 아이템 이동
  const moveItem = useCallback((dragIndex, hoverIndex) =&amp;gt; {
    setItems((prevItems) =&amp;gt;
      update(prevItems, {
        $splice: [
          [dragIndex, 1],
          [hoverIndex, 0, prevItems[dragIndex]],
        ],
      }),
    );
  }, []);

  // 최상단으로 이동
  const moveToTop = useCallback((index) =&amp;gt; {
    setItems((prevItems) =&amp;gt; {
      const newItems = [...prevItems];
      const [removed] = newItems.splice(index, 1);
      newItems.unshift(removed);
      return newItems;
    });
  }, []);

  // 최하단으로 이동
  const moveToBottom = useCallback((index) =&amp;gt; {
    setItems((prevItems) =&amp;gt; {
      const newItems = [...prevItems];
      const [removed] = newItems.splice(index, 1);
      newItems.push(removed);
      return newItems;
    });
  }, []);

  // 한칸 위로 이동
  const moveUp = useCallback((index) =&amp;gt; {
    if (index === 0) return;
    setItems((prevItems) =&amp;gt; {
      const newItems = [...prevItems];
      [newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
      return newItems;
    });
  }, []);

  // 한칸 아래로 이동
  const moveDown = useCallback((index) =&amp;gt; {
    setItems((prevItems) =&amp;gt; {
      if (index === prevItems.length - 1) return prevItems;
      const newItems = [...prevItems];
      [newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
      return newItems;
    });
  }, []);

  return (
    &amp;lt;DndProvider backend={HTML5Backend}&amp;gt;
      &amp;lt;List sx={{ width: &amp;#39;100%&amp;#39;, maxWidth: 600, margin: &amp;#39;0 auto&amp;#39; }}&amp;gt;
        {items.map((item, index) =&amp;gt; (
          &amp;lt;Layer
            key={item.id}
            id={item.id}
            text={item.text}
            index={index}
            moveItem={moveItem}
            moveToTop={moveToTop}
            moveToBottom={moveToBottom}
            moveUp={moveUp}
            moveDown={moveDown}
          /&amp;gt;
        ))}
      &amp;lt;/List&amp;gt;
    &amp;lt;/DndProvider&amp;gt;
  );
};

export default LayerList;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;주요 코드 설명&lt;/h2&gt;
&lt;h3&gt;1. Layer 컴포넌트&lt;/h3&gt;
&lt;p&gt;드래그 가능한 개별 항목을 표현하는 컴포넌트입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const Layer = ({ id, text, index, moveItem, moveToTop, moveToBottom, moveUp, moveDown }) =&amp;gt; {
  const ref = React.useRef(null);

  // useDrag: 드래그 기능 설정
  const [{ isDragging }, drag] = useDrag({
    type: &amp;#39;item&amp;#39;,
    item: { id, index },
    collect: (monitor) =&amp;gt; ({
      isDragging: monitor.isDragging(),
    }),
  });

  // useDrop: 드롭 기능 설정
  const [, drop] = useDrop({
    accept: &amp;#39;item&amp;#39;,
    hover: (item, monitor) =&amp;gt; {
      if (!ref.current) return;
      const dragIndex = item.index;
      const hoverIndex = index;
      if (dragIndex === hoverIndex) return;
      moveItem(dragIndex, hoverIndex);
      item.index = hoverIndex;
    },
  });

  // drag와 drop 기능을 ref에 연결
  drag(drop(ref));

  // ... JSX 반환
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 이동 관련 함수들&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// 드래그로 아이템 이동
const moveItem = useCallback((dragIndex, hoverIndex) =&amp;gt; {
  setItems((prevItems) =&amp;gt;
    update(prevItems, {
      $splice: [
        [dragIndex, 1],
        [hoverIndex, 0, prevItems[dragIndex]],
      ],
    }),
  );
}, []);

// 최상단으로 이동
const moveToTop = useCallback((index) =&amp;gt; {
  setItems((prevItems) =&amp;gt; {
    const newItems = [...prevItems];
    const [removed] = newItems.splice(index, 1);
    newItems.unshift(removed);
    return newItems;
  });
}, []);

// 한칸 위로 이동
const moveUp = useCallback((index) =&amp;gt; {
  if (index === 0) return;
  setItems((prevItems) =&amp;gt; {
    const newItems = [...prevItems];
    [newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
    return newItems;
  });
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;주요 라이브러리 설명&lt;/h2&gt;
&lt;h3&gt;React-DnD&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useDrag&lt;/code&gt;: 드래그 가능한 요소를 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useDrop&lt;/code&gt;: 드롭 가능한 영역을 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DndProvider&lt;/code&gt;: 드래그 앤 드롭 컨텍스트 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Material-UI (MUI)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;List/ListItem&lt;/code&gt;: 리스트 구조 제공&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IconButton&lt;/code&gt;: 이동 버튼 구현&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Box&lt;/code&gt;: 레이아웃 컨테이너&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;immutability-helper&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;불변성을 유지하면서 배열/객체 업데이트&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update()&lt;/code&gt; 함수로 효율적인 상태 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;스타일링&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;ListItem
  style={{
    opacity: isDragging ? 0.5 : 1,
    cursor: &amp;#39;move&amp;#39;,
    backgroundColor: &amp;#39;#fff&amp;#39;,
    marginBottom: &amp;#39;4px&amp;#39;,
    border: &amp;#39;1px solid #eee&amp;#39;,
    borderRadius: &amp;#39;4px&amp;#39;
  }}
&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;사용 방법&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import LayerList from &amp;#39;./components/LayerList&amp;#39;;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;LayerList /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 컴포넌트는 드래그 앤 드롭으로 항목을 재정렬할 수 있으며, 버튼을 통해 항목의 위치를 직접 조정할 수 있습니다. Material-UI의 디자인 시스템을 활용하여 깔끔하고 일관된 UI를 제공합니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>reactjs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/427</guid>
      <comments>https://syaku.tistory.com/427#entry427comment</comments>
      <pubDate>Wed, 6 Nov 2024 14:55:17 +0900</pubDate>
    </item>
    <item>
      <title>Reactjs - 이미지 첨부 컴포넌트</title>
      <link>https://syaku.tistory.com/426</link>
      <description>&lt;h1&gt;이미지 첨부 컴포넌트&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState } from &amp;#39;react&amp;#39;;
import { Button, Box, IconButton, ImageList, ImageListItem, ListSubheader, ImageListItemBar } from &amp;#39;@mui/material&amp;#39;;
import CloudUploadIcon from &amp;#39;@mui/icons-material/CloudUpload&amp;#39;;
import DeleteIcon from &amp;#39;@mui/icons-material/Delete&amp;#39;;

/**
 * 이미지 첨부 및 미리보기를 제공하는 컴포넌트
 * - 이미지 파일 선택 및 업로드
 * - 선택된 이미지 미리보기 표시
 * - 개별 이미지 삭제 기능
 */
const ImageAttachButton = () =&amp;gt; {
  // 첨부된 파일들의 정보를 저장하는 상태
  const [attachedFiles, setAttachedFiles] = useState([]);

  /**
   * 파일 선택 시 실행되는 이벤트 핸들러
   * @param {Event} event - 파일 입력 이벤트 객체
   */
  const handleFileChange = (event) =&amp;gt; {
    // FileList 객체를 배열로 변환
    const files = Array.from(event.target.files);

    // 각 파일에 대한 정보 객체 생성
    const newFiles = files.map((file) =&amp;gt; ({
      name: file.name,                                    // 파일명
      size: (file.size / 1024).toFixed(2) + &amp;#39;KB&amp;#39;,        // 파일 크기 (KB)
      preview: URL.createObjectURL(file),                 // 미리보기 URL
      file: file,                                        // 원본 파일 객체
    }));

    // 기존 파일 목록에 새로운 파일들을 추가
    setAttachedFiles((prevFiles) =&amp;gt; [...prevFiles, ...newFiles]);
  };

  /**
   * 파일 삭제 시 실행되는 이벤트 핸들러
   * @param {string} fileName - 삭제할 파일의 이름
   */
  const handleFileDelete = (fileName) =&amp;gt; {
    // 지정된 파일명과 일치하지 않는 파일들만 남김
    setAttachedFiles((prevFiles) =&amp;gt; prevFiles.filter((file) =&amp;gt; file.name !== fileName));
  };

  return (
    // 컴포넌트의 최상위 컨테이너
    &amp;lt;Box sx={{ width: &amp;#39;100%&amp;#39;, maxWidth: 350 }}&amp;gt;
      {/* 파일 업로드 버튼 영역 */}
      &amp;lt;Box sx={{ padding: 1 }}&amp;gt;
        &amp;lt;Button component=&amp;quot;label&amp;quot; variant=&amp;quot;contained&amp;quot; startIcon={&amp;lt;CloudUploadIcon /&amp;gt;} fullWidth&amp;gt;
          이미지 첨부
          &amp;lt;input
            type=&amp;quot;file&amp;quot;
            hidden
            multiple
            accept=&amp;quot;image/*&amp;quot;
            onChange={handleFileChange}
          /&amp;gt;
        &amp;lt;/Button&amp;gt;
      &amp;lt;/Box&amp;gt;

      {/* 이미지 미리보기 목록 */}
      &amp;lt;ImageList sx={{ padding: 1, margin: 0 }}&amp;gt;
        {attachedFiles.map((file, index) =&amp;gt; (
          &amp;lt;ImageListItem key={`image_attach_button_list_${index}`}&amp;gt;
            {/* 이미지 미리보기 */}
            &amp;lt;img
              src={file.preview}
              alt={file.name}
              loading=&amp;quot;lazy&amp;quot;
              style={{
                width: &amp;#39;100%&amp;#39;,
                height: &amp;#39;100%&amp;#39;,
                objectFit: &amp;#39;cover&amp;#39;
              }}
            /&amp;gt;
            {/* 이미지 정보 및 삭제 버튼 바 */}
            &amp;lt;ImageListItemBar
              title={file.name}
              subtitle={file.size}
              actionIcon={
                &amp;lt;IconButton
                  sx={{ color: &amp;#39;rgba(255, 255, 255, 0.54)&amp;#39; }}
                  aria-label={`info about ${file.name}`}
                  onClick={() =&amp;gt; handleFileDelete(file.name)}
                &amp;gt;
                  &amp;lt;DeleteIcon /&amp;gt;
                &amp;lt;/IconButton&amp;gt;
              }
            /&amp;gt;
          &amp;lt;/ImageListItem&amp;gt;
        ))}
      &amp;lt;/ImageList&amp;gt;
    &amp;lt;/Box&amp;gt;
  );
};

// App 컴포넌트에서 세로 스크롤과 함께 사용
function App() {
  return (
    &amp;lt;div style={{
      height: &amp;#39;100vh&amp;#39;,     // 전체 화면 높이
      overflowY: &amp;#39;auto&amp;#39;,   // 세로 스크롤
    }}&amp;gt;
      &amp;lt;ImageAttachButton /&amp;gt;
      &amp;lt;ImageAttachButton /&amp;gt;
      &amp;lt;ImageAttachButton /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;상세 설명&lt;/h2&gt;
&lt;h3&gt;1. 컴포넌트 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Material-UI(MUI) 컴포넌트를 활용한 이미지 첨부 기능 구현&lt;/li&gt;
&lt;li&gt;파일 선택 버튼과 선택된 이미지 미리보기 목록으로 구성&lt;/li&gt;
&lt;li&gt;세로 스크롤이 있는 컨테이너에 배치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 주요 기능&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;파일 선택&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;input type=&amp;quot;file&amp;quot;&lt;/code&gt; 요소를 통해 이미지 파일 선택&lt;/li&gt;
&lt;li&gt;다중 선택 가능 (&lt;code&gt;multiple&lt;/code&gt; 속성)&lt;/li&gt;
&lt;li&gt;이미지 파일만 선택 가능 (&lt;code&gt;accept=&amp;quot;image/*&amp;quot;&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;ul&gt;
&lt;li&gt;파일명, 크기, 미리보기 URL, 원본 파일 객체 저장&lt;/li&gt;
&lt;li&gt;useState를 통한 상태 관리&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;ImageList 컴포넌트를 사용하여 그리드 형태로 표시&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;/ol&gt;
&lt;h3&gt;3. 스크롤 구현&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;height: &amp;#39;100vh&amp;#39;&lt;/code&gt;로 전체 화면 높이 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;overflowY: &amp;#39;auto&amp;#39;&lt;/code&gt;로 세로 스크롤 자동 생성&lt;/li&gt;
&lt;li&gt;내용이 화면 높이를 초과할 경우에만 스크롤바 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 스타일링&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Box 컴포넌트로 전체 너비 제한 (최대 350px)&lt;/li&gt;
&lt;li&gt;이미지 미리보기는 cover 속성으로 비율 유지&lt;/li&gt;
&lt;li&gt;삭제 버튼은 반투명한 흰색으로 스타일링&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 사용 방법&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import App from &amp;#39;./App&amp;#39;;

// 다른 컴포넌트에서 사용
function ParentComponent() {
  return &amp;lt;App /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 구현하면 전체 화면 높이를 사용하면서 내용이 많아질 경우 자동으로 스크롤이 생성되는 이미지 첨부 컴포넌트를 만들 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>reactjs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/426</guid>
      <comments>https://syaku.tistory.com/426#entry426comment</comments>
      <pubDate>Wed, 6 Nov 2024 11:39:18 +0900</pubDate>
    </item>
    <item>
      <title>Apache NiFi</title>
      <link>https://syaku.tistory.com/425</link>
      <description>&lt;h1 id=&quot;apache-nifi-&quot;&gt;Apache NiFi: 데이터 흐름 자동화를 위한 강력한 도구&lt;/h1&gt;
&lt;h2 id=&quot;1-apache-nifi-&quot;&gt;1. Apache NiFi 소개&lt;/h2&gt;
&lt;p&gt;Apache NiFi는 시스템 간 데이터 흐름을 자동화하고 관리하기 위한 강력한 오픈소스 소프트웨어입니다. 실시간 데이터 처리에 최적화되어 있으며, 다양한 데이터 소스와 프로토콜을 지원합니다.&lt;/p&gt;
&lt;h3 id=&quot;1-1-&quot;&gt;1.1 주요 특징&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;: HTTP, FTP, SFTP, Kafka 등 다양한 프로토콜과 데이터 소스를 지원합니다.&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;/ul&gt;
&lt;h3 id=&quot;1-2-&quot;&gt;1.2 주요 구성 요소&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FlowFile&lt;/strong&gt;: NiFi가 처리하는 데이터의 기본 단위입니다. 실제 콘텐츠와 속성으로 구성됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processor&lt;/strong&gt;: 데이터를 수집, 변환, 라우팅, 저장하는 작업을 수행하는 핵심 구성 요소입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection&lt;/strong&gt;: 프로세서 간 데이터 전달 및 큐잉을 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flow Controller&lt;/strong&gt;: 전체 데이터 흐름을 관리하고 스케줄링합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Server&lt;/strong&gt;: 사용자 인터페이스를 제공하여 데이터 흐름을 시각적으로 관리할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository&lt;/strong&gt;: 데이터 및 메타데이터를 저장하는 공간입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-nifi-&quot;&gt;2. NiFi 사용 시 주의사항&lt;/h2&gt;
&lt;p&gt;NiFi를 효과적으로 사용하기 위해서는 다음 사항들에 주의해야 합니다:&lt;/p&gt;
&lt;h3 id=&quot;2-1-&quot;&gt;2.1 메모리 관리&lt;/h3&gt;
&lt;p&gt;NiFi는 메모리를 많이 사용하는 애플리케이션입니다. 기본 설정인 512MB로는 대규모 데이터 처리 시 Out of Memory 오류가 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;권장 사항&lt;/strong&gt;: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최소 8GB 이상의 메모리를 할당하세요.&lt;/li&gt;
&lt;li&gt;JVM 힙 메모리 설정을 적절히 조정하세요. (예: -Xms4g -Xmx8g)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-2-&quot;&gt;2.2 성능 최적화&lt;/h3&gt;
&lt;p&gt;NiFi의 성능은 시스템 설정에 크게 영향을 받습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;권장 사항&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux 환경에서 실행 시, 파일 핸들 수와 프로세스 수 제한을 늘리세요.&lt;/li&gt;
&lt;li&gt;Swapping을 비활성화하여 성능을 개선하세요.&lt;/li&gt;
&lt;li&gt;SSD 등 고성능 스토리지를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-3-&quot;&gt;2.3 보안 설정&lt;/h3&gt;
&lt;p&gt;NiFi는 기본적으로 보안 설정이 되어 있지 않습니다. 운영 환경에서는 반드시 보안 설정을 해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;권장 사항&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTPS를 설정하여 통신을 암호화하세요.&lt;/li&gt;
&lt;li&gt;사용자 인증 및 권한 관리를 구현하세요.&lt;/li&gt;
&lt;li&gt;초기 관리자 계정을 설정하고, 강력한 비밀번호를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-4-&quot;&gt;2.4 백신 소프트웨어 설정&lt;/h3&gt;
&lt;p&gt;백신 소프트웨어가 NiFi의 성능에 영향을 줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;권장 사항&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NiFi 디렉토리를 백신 검사 대상에서 제외하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-5-&quot;&gt;2.5 데이터 흐름 설계&lt;/h3&gt;
&lt;p&gt;복잡한 데이터 흐름은 성능 저하의 원인이 될 수 있습니다.&lt;/p&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;복잡한 연산은 Spark 등 외부 시스템과 연계하여 처리하세요.&lt;/li&gt;
&lt;li&gt;배치 작업과 실시간 처리를 적절히 분리하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-nifi-&quot;&gt;3. NiFi 성능 최적화 방법&lt;/h2&gt;
&lt;p&gt;NiFi의 성능을 최적화하기 위해 다음과 같은 방법을 사용할 수 있습니다:&lt;/p&gt;
&lt;h3 id=&quot;3-1-&quot;&gt;3.1 하드웨어 리소스 최적화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU: 멀티코어 프로세서를 사용하세요. 노드당 최소 8코어 이상을 권장합니다.&lt;/li&gt;
&lt;li&gt;메모리: 충분한 RAM을 할당하세요. 노드당 최소 16GB 이상을 권장합니다.&lt;/li&gt;
&lt;li&gt;디스크: SSD 등 고성능 스토리지를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-2-nifi-&quot;&gt;3.2 NiFi 설정 최적화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배치 크기(Max Batch Size) 증가: 프로세서의 처리 단위를 늘려 효율성을 높입니다.&lt;/li&gt;
&lt;li&gt;동시 작업 수(Concurrent Tasks) 조정: 프로세서의 병렬 처리 수준을 조절합니다.&lt;/li&gt;
&lt;li&gt;백 프레셔 임계값 조정: 데이터 흐름의 균형을 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-3-&quot;&gt;3.3 클러스터 구성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;노드 수 증가: 처리량에 따라 클러스터 노드 수를 늘려 선형적인 성능 향상을 도모합니다.&lt;/li&gt;
&lt;li&gt;로드 밸런싱: 외부 로드 밸런서를 사용하여 트래픽을 분산시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-nifi-&quot;&gt;4. NiFi 클러스터에서의 데이터 분산 처리 최적화&lt;/h2&gt;
&lt;h3 id=&quot;4-1-&quot;&gt;4.1 로드 밸런싱 활용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;외부 로드 밸런서를 사용하여 들어오는 데이터를 클러스터 노드들에 균등하게 분배합니다.&lt;/li&gt;
&lt;li&gt;NiFi의 Load-Balanced Connections 기능을 사용하여 클러스터 내 데이터 재분배를 최적화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-2-site-to-site-&quot;&gt;4.2 Site-to-Site 프로토콜 사용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;NiFi 인스턴스 간 데이터 전송 시 Site-to-Site 프로토콜을 활용하여 자동으로 클러스터 노드 간 데이터를 균등하게 분배합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-3-&quot;&gt;4.3 분산 처리 설계&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ListHDFS와 FetchHDFS 같은 프로세서를 사용할 때, Primary Node에서 리스팅을 수행하고 결과를 클러스터 전체에 분배하여 병렬 처리를 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-nifi-&quot;&gt;5. NiFi 권장 사양&lt;/h2&gt;
&lt;h3 id=&quot;5-1-&quot;&gt;5.1 하드웨어 요구사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU: 노드당 최소 8코어 이상 권장&lt;/li&gt;
&lt;li&gt;메모리: 노드당 최소 16GB RAM 이상 권장&lt;/li&gt;
&lt;li&gt;디스크: 노드당 최소 6개의 하드디스크 권장 (SSD 선호)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-2-&quot;&gt;5.2 소프트웨어 요구사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Java 8 또는 Java 11&lt;/li&gt;
&lt;li&gt;지원 운영체제: Linux, Unix, Windows, macOS&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-3-&quot;&gt;5.3 클러스터 구성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기본 권장 클러스터 크기: 3노드 (고가용성을 위한 Zookeeper Quorum 구성)&lt;/li&gt;
&lt;li&gt;처리량에 따라 노드 수 확장 가능 (선형적 확장성 제공)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-docker-compose-nifi-zookeeper-&quot;&gt;6. Docker Compose를 사용한 NiFi 클러스터 설치 (Zookeeper 포함)&lt;/h2&gt;
&lt;p&gt;다음은 Docker Compose를 사용하여 NiFi 클러스터와 Zookeeper를 함께 설치하는 방법입니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Docker Compose 파일 생성:
프로젝트 디렉토리에 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일을 생성하고 다음 내용을 추가합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;version: '3'
services:
  zookeeper:
    image: 'bitnami/zookeeper:latest'
    container_name: zookeeper
    ports:
      -&lt;span class=&quot;ruby&quot;&gt; &lt;span class=&quot;hljs-string&quot;&gt;'2181:2181'&lt;/span&gt;
&lt;/span&gt;    environment:
      -&lt;span class=&quot;ruby&quot;&gt; ALLOW_ANONYMOUS_LOGIN=yes
&lt;/span&gt;    networks:
      -&lt;span class=&quot;ruby&quot;&gt; nifi-network
&lt;/span&gt;
  nifi:
    image: apache/nifi:latest
    container_name: nifi
    ports:
      -&lt;span class=&quot;ruby&quot;&gt; &lt;span class=&quot;hljs-string&quot;&gt;'8080:8080'&lt;/span&gt;
&lt;/span&gt;    environment:
      -&lt;span class=&quot;ruby&quot;&gt; NIFI_WEB_HTTP_PORT=&lt;span class=&quot;hljs-number&quot;&gt;8080&lt;/span&gt;
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; NIFI_CLUSTER_IS_NODE=&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; NIFI_CLUSTER_NODE_PROTOCOL_PORT=&lt;span class=&quot;hljs-number&quot;&gt;8082&lt;/span&gt;
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; NIFI_ZK_CONNECT_STRING=&lt;span class=&quot;hljs-symbol&quot;&gt;zookeeper:&lt;/span&gt;&lt;span class=&quot;hljs-number&quot;&gt;2181&lt;/span&gt;
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; NIFI_ELECTION_MAX_WAIT=&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt; min
&lt;/span&gt;    volumes:
      -&lt;span class=&quot;ruby&quot;&gt; ./nifi/&lt;span class=&quot;hljs-symbol&quot;&gt;database_repository:&lt;/span&gt;/opt/nifi/nifi-current/database_repository
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; ./nifi/&lt;span class=&quot;hljs-symbol&quot;&gt;flowfile_repository:&lt;/span&gt;/opt/nifi/nifi-current/flowfile_repository
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; ./nifi/&lt;span class=&quot;hljs-symbol&quot;&gt;content_repository:&lt;/span&gt;/opt/nifi/nifi-current/content_repository
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; ./nifi/&lt;span class=&quot;hljs-symbol&quot;&gt;provenance_repository:&lt;/span&gt;/opt/nifi/nifi-current/provenance_repository
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; ./nifi/&lt;span class=&quot;hljs-symbol&quot;&gt;state:&lt;/span&gt;/opt/nifi/nifi-current/state
&lt;/span&gt;      -&lt;span class=&quot;ruby&quot;&gt; ./nifi/&lt;span class=&quot;hljs-symbol&quot;&gt;logs:&lt;/span&gt;/opt/nifi/nifi-current/logs
&lt;/span&gt;    networks:
      -&lt;span class=&quot;ruby&quot;&gt; nifi-network
&lt;/span&gt;
networks:
  nifi-network:
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NiFi 및 Zookeeper 실행:
터미널에서 다음 명령어를 실행하여 NiFi와 Zookeeper 컨테이너를 시작합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up &lt;span class=&quot;hljs-_&quot;&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NiFi 접속:
브라우저에서 &lt;code&gt;http://localhost:8080/nifi&lt;/code&gt;로 접속하여 NiFi 웹 인터페이스에 접근할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NiFi 및 Zookeeper 중지 및 제거:
서비스를 중지하고 컨테이너를 제거하려면 다음 명령어를 사용합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;docker-compose down&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 설정으로 NiFi 클러스터의 기본 구성이 가능합니다. 실제 운영 환경에서는 보안 설정, 리소스 할당, 고가용성 구성 등 추가적인 설정이 필요할 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>nifi</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/425</guid>
      <comments>https://syaku.tistory.com/425#entry425comment</comments>
      <pubDate>Fri, 1 Nov 2024 14:07:22 +0900</pubDate>
    </item>
    <item>
      <title>성공을 위한 계획 수립: 오타니와 일론 머스크의 방식</title>
      <link>https://syaku.tistory.com/424</link>
      <description>&lt;div class=&quot;mb-md&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border-color: oklch(var(--border-color-100)/.5); border-image: initial; border-style: solid; border-width: 0px; box-sizing: border-box; margin-bottom: var(--size-md); scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;div class=&quot;relative default font-sans text-base text-textMain dark:text-textMainDark selection:bg-super/50 selection:text-textMain dark:selection:bg-superDuper/10 dark:selection:text-superDark&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-text-opacity: 1; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; color: oklch(var(--text-color-100)/var(--tw-text-opacity)); font-family: var(--font-fk-grotesk-neue),ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,&amp;quot;Segoe UI&amp;quot;,Roboto,Helvetica Neue,Arial,&amp;quot;Noto Sans&amp;quot;,sans-serif,&amp;quot;Apple Color Emoji&amp;quot;,&amp;quot;Segoe UI Emoji&amp;quot;,&amp;quot;Segoe UI Symbol&amp;quot;,&amp;quot;Noto Color Emoji&amp;quot;; font-size: 1rem; line-height: 1.5rem; position: relative; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;div class=&quot;min-w-0 break-words [word-break:break-word]&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; min-width: 0px; overflow-wrap: break-word; scrollbar-color: auto; scrollbar-width: auto; word-break: break-word;&quot;&gt;&lt;div dir=&quot;auto&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;div class=&quot;prose dark:prose-invert inline leading-normal break-words min-w-0 [word-break:break-word]&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-prose-body: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-bold: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-bullets: oklch(var(--text-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-captions: oklch(var(--text-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-code: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-counters: oklch(var(--text-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-headings: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-hr: oklch(var(--border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-body: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-bold: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-bullets: oklch(var(--dark-text-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-captions: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-code: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-counters: oklch(var(--dark-text-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-headings: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-hr: oklch(var(--dark-border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-kbd-shadows: 255 255 255; --tw-prose-invert-kbd: #fff; --tw-prose-invert-lead: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-links: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-pre-bg: oklch(var(--dark-background-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-pre-code: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-quote-borders: oklch(var(--dark-border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-quotes: oklch(var(--dark-text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-td-borders: oklch(var(--dark-border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-invert-th-borders: oklch(var(--dark-border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-kbd-shadows: 17 24 39; --tw-prose-kbd: #111827; --tw-prose-lead: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-links: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-pre-bg: oklch(var(--background-color-200)/&amp;lt;alpha-value&amp;gt;); --tw-prose-pre-code: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-quote-borders: oklch(var(--border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-quotes: oklch(var(--text-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-td-borders: oklch(var(--border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-prose-th-borders: oklch(var(--border-color-100)/&amp;lt;alpha-value&amp;gt;); --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; color: var(--tw-prose-body); display: inline; font-size: 1rem; line-height: 1.5; max-width: 65ch; min-width: 0px; overflow-wrap: break-word; scrollbar-color: auto; scrollbar-width: auto; word-break: break-word;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;성공한 사람들의 계획 수립 방식을 배우는 것은 우리의 목표 달성에 큰 도움이 될 수 있습니다. 오늘은 야구 스타 오타니 쇼헤이와 기업가 일론 머스크가 사용한 유명한 계획 수립 방식을 소개하겠습니다.&lt;/span&gt;&lt;h2 class=&quot;mb-2 mt-6 text-lg first:mt-3&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; color: var(--tw-prose-headings); font-family: var(--font-fk-grotesk); font-size: 1.125rem; font-weight: 500; line-height: 1.75rem; margin: 1.5rem 0px 0.5rem; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;오타니 쇼헤이의 만다라트 기법&lt;/h2&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-top: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;오타니가 사용한 '만다라트 기법'은 목표를 체계적으로 세분화하는 방법입니다.&lt;/span&gt;&lt;span class=&quot;mt-md block&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: block; margin-top: var(--size-md); scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span color=&quot;var(--tw-prose-bold)&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;주요 특징:&lt;/span&gt;&lt;/span&gt;&lt;ul class=&quot;marker:text-textOff list-disc pl-8&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; list-style-image: initial; list-style-position: initial; margin: 0px; padding-inline-start: 1.625em; padding: 0px 0px 0px 2rem; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;li index=&quot;0&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;3x3 격자로 구성된 9개의 사각형 사용&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;1&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;중앙에 핵심 목표 배치&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;2&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;주변 8개 사각형에 세부 목표 작성&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;3&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;각 세부 목표 사각형 안에 8개의 구체적 실천 과제 기입&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;오타니는 이 방법으로 '8개 구단 드래프트 1순위'라는 목표를 세우고 성공적으로 달성했습니다.&lt;/span&gt;&lt;h2 class=&quot;mb-2 mt-6 text-lg first:mt-3&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; color: var(--tw-prose-headings); font-family: var(--font-fk-grotesk); font-size: 1.125rem; font-weight: 500; line-height: 1.75rem; margin: 1.5rem 0px 0.5rem; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;일론 머스크의 5분 단위 시간 계획&lt;/h2&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-top: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;머스크는 극도로 세밀한 시간 관리 방식을 사용합니다.&lt;/span&gt;&lt;span class=&quot;mt-md block&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: block; margin-top: var(--size-md); scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span color=&quot;var(--tw-prose-bold)&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;핵심 요소:&lt;/span&gt;&lt;/span&gt;&lt;ul class=&quot;marker:text-textOff list-disc pl-8&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; list-style-image: initial; list-style-position: initial; margin: 0px; padding-inline-start: 1.625em; padding: 0px 0px 0px 2rem; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;li index=&quot;0&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;하루를 5분 단위로 쪼개어 계획&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;1&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;매우 정밀한 시간 관리&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;2&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;방해 요소 최소화 (예: 통화 대신 이메일 선호)&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;이 방식으로 머스크는 생산성을 극대화하고 여러 회사를 동시에 운영할 수 있습니다.&lt;/span&gt;&lt;h2 class=&quot;mb-2 mt-6 text-lg first:mt-3&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; color: var(--tw-prose-headings); font-family: var(--font-fk-grotesk); font-size: 1.125rem; font-weight: 500; line-height: 1.75rem; margin: 1.5rem 0px 0.5rem; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;공통점과 교훈&lt;/h2&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-top: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;두 방식 모두 목표 설정과 실행에 있어 매우 구체적이고 체계적입니다. 이를 통해 우리는 다음과 같은 교훈을 얻을 수 있습니다:&lt;/span&gt;&lt;ol class=&quot;marker:text-textOff list-decimal pl-8&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; list-style-image: initial; list-style-position: initial; margin: 0px; padding-inline-start: 1.625em; padding: 0px 0px 0px 2rem; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;li index=&quot;0&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;명확한 목표 설정의 중요성&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;1&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;세부적인 실행 계획의 필요성&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;2&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;시간 관리의 중요성&lt;/span&gt;&lt;/li&gt;&lt;li index=&quot;3&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0.5em; margin-top: 0.5em; padding-inline-start: 0.375em; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;지속적인 노력과 집중의 가치&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; margin-bottom: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;이러한 방식을 참고하여 자신만의 계획 수립 방법을 개발한다면, 우리도 더 큰 성공을 향해 나아갈 수 있을 것입니다.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;mt-sm flex items-center justify-between&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border-color: oklch(var(--border-color-100)/.5); border-image: initial; border-style: solid; border-width: 0px; box-sizing: border-box; display: flex; justify-content: space-between; margin-top: var(--size-sm); scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;div class=&quot;-ml-sm flex items-center gap-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: flex; gap: var(--size-xs); margin-left: calc(var(--size-sm) * -1); scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;button class=&quot;focus-visible:bg-offsetPlus dark:focus-visible:bg-offsetPlusDark md:hover:bg-offsetPlus text-textOff dark:text-textOffDark md:hover:text-textMain dark:md:hover:bg-offsetPlusDark dark:md:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm px-sm font-medium h-8&quot; data-state=&quot;closed&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-text-opacity: 1; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; animation-duration: 0.3s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); appearance: button; background-image: none; border-color: rgb(229, 231, 235); border-radius: 9999px; border-style: solid; border-width: 0px; cursor: pointer; display: inline-flex; font-family: var(--font-fk-grotesk-neue),ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,&amp;quot;Segoe UI&amp;quot;,Roboto,Helvetica Neue,Arial,&amp;quot;Noto Sans&amp;quot;,sans-serif,&amp;quot;Apple Color Emoji&amp;quot;,&amp;quot;Segoe UI Emoji&amp;quot;,&amp;quot;Segoe UI Symbol&amp;quot;,&amp;quot;Noto Color Emoji&amp;quot;; font-feature-settings: inherit; font-size: 0.875rem; font-variation-settings: inherit; font-weight: 500; height: 2rem; justify-content: center; letter-spacing: inherit; line-height: 1.25rem; margin: 0px; outline-offset: 2px; outline: transparent solid 2px; padding-bottom: 0px; padding-left: var(--size-sm); padding-right: var(--size-sm); padding-top: 0px; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-wrap: nowrap; transform-origin: center center; transition-duration: 0.3s; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); user-select: none;&quot; type=&quot;button&quot;&gt;&lt;div class=&quot;flex items-center min-w-0 justify-center gap-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: flex; gap: var(--size-xs); justify-content: center; min-width: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;svg-inline--fa fa-share fa-fw fa-1x&quot; data-icon=&quot;share&quot; data-prefix=&quot;far&quot; focusable=&quot;false&quot; role=&quot;img&quot; viewbox=&quot;0 0 512 512&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;path d=&quot;M288 240H192c-66.2 0-122 44.7-138.8 105.5C49.9 333.1 48 319.3 48 304c0-70.7 57.3-128 128-128H288h24c13.3 0 24-10.7 24-24V128 99.9L456.1 208 336 316.1V288 264c0-13.3-10.7-24-24-24H288zm0 48v48 16c0 12.6 7.4 24.1 19 29.2s25 3 34.4-5.4l160-144c6.7-6.1 10.6-14.7 10.6-23.8s-3.8-17.7-10.6-23.8l-160-144c-9.4-8.5-22.9-10.6-34.4-5.4s-19 16.6-19 29.2V80v48H240 176C78.8 128 0 206.8 0 304c0 78 38.6 126.2 68.7 152.1c4.1 3.5 8.1 6.6 11.7 9.3c3.2 2.4 6.2 4.4 8.9 6.2c4.5 3 8.3 5.1 10.8 6.5c2.5 1.4 5.3 1.9 8.1 1.9c10.9 0 19.7-8.9 19.7-19.7c0-6.8-3.6-13.2-8.3-18.1c-.5-.5-.9-.9-1.4-1.4c-2.4-2.3-5.1-5.1-7.7-8.6c-1.7-2.3-3.4-5-5-7.9c-5.3-9.7-9.5-22.9-9.5-40.2c0-53 43-96 96-96h48 48z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;div class=&quot;text-align-center relative truncate leading-loose&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; line-height: 2; overflow: hidden; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-overflow: ellipsis;&quot;&gt;공유&lt;/div&gt;&lt;/div&gt;&lt;/button&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;button class=&quot;focus-visible:bg-offsetPlus dark:focus-visible:bg-offsetPlusDark md:hover:bg-offsetPlus text-textOff dark:text-textOffDark md:hover:text-textMain dark:md:hover:bg-offsetPlusDark dark:md:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm px-sm font-medium h-8&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-text-opacity: 1; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; animation-duration: 0.3s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); appearance: button; background-image: none; border-color: rgb(229, 231, 235); border-radius: 9999px; border-style: solid; border-width: 0px; cursor: pointer; display: inline-flex; font-family: var(--font-fk-grotesk-neue),ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,&amp;quot;Segoe UI&amp;quot;,Roboto,Helvetica Neue,Arial,&amp;quot;Noto Sans&amp;quot;,sans-serif,&amp;quot;Apple Color Emoji&amp;quot;,&amp;quot;Segoe UI Emoji&amp;quot;,&amp;quot;Segoe UI Symbol&amp;quot;,&amp;quot;Noto Color Emoji&amp;quot;; font-feature-settings: inherit; font-size: 0.875rem; font-variation-settings: inherit; font-weight: 500; height: 2rem; justify-content: center; letter-spacing: inherit; line-height: 1.25rem; margin: 0px; outline-offset: 2px; outline: transparent solid 2px; padding-bottom: 0px; padding-left: var(--size-sm); padding-right: var(--size-sm); padding-top: 0px; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-wrap: nowrap; transform-origin: center center; transition-duration: 0.3s; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); user-select: none;&quot; type=&quot;button&quot;&gt;&lt;div class=&quot;flex items-center min-w-0 justify-center gap-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: flex; gap: var(--size-xs); justify-content: center; min-width: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;svg-inline--fa fa-repeat fa-fw fa-1x&quot; data-icon=&quot;repeat&quot; data-prefix=&quot;far&quot; focusable=&quot;false&quot; role=&quot;img&quot; viewbox=&quot;0 0 512 512&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;path d=&quot;M22.8 280C9.6 279.3-.6 268 0 254.8l.4-8C5.3 148.9 86.2 72 184.2 72H304l0-36.4C304 15.9 319.9 0 339.6 0c8.8 0 17.3 3.3 23.8 9.1l76.7 69c5.1 4.6 7.9 11 7.9 17.8s-2.9 13.3-7.9 17.8l-76.7 69c-6.5 5.9-15 9.1-23.8 9.1c-19.6 0-35.6-15.9-35.6-35.6V120H184.2C111.7 120 52 176.8 48.4 249.2l-.4 8C47.3 270.4 36 280.6 22.8 280zM352 128.5L388.1 96 352 63.5l0 65zM489.2 232c13.2 .7 23.4 11.9 22.8 25.2l-.4 8C506.7 363.1 425.8 440 327.8 440L208 440v36.4c0 19.6-15.9 35.6-35.6 35.6c-8.8 0-17.3-3.3-23.8-9.1l-76.7-69c-5.1-4.6-7.9-11-7.9-17.8s2.9-13.3 7.9-17.8l76.7-69c6.5-5.9 15-9.1 23.8-9.1c19.6 0 35.6 15.9 35.6 35.6V392l119.8 0c72.5 0 132.2-56.8 135.8-129.2l.4-8c.7-13.2 11.9-23.4 25.2-22.8zM160 383.5L123.9 416 160 448.5v-65z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;div class=&quot;text-align-center relative truncate leading-loose&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; line-height: 2; overflow: hidden; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-overflow: ellipsis;&quot;&gt;다시 쓰기&lt;/div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;flex items-center gap-x-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; column-gap: var(--size-xs); display: flex; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;button aria-label=&quot;Copy&quot; class=&quot;focus-visible:bg-offsetPlus dark:focus-visible:bg-offsetPlusDark md:hover:bg-offsetPlus text-textOff dark:text-textOffDark md:hover:text-textMain dark:md:hover:bg-offsetPlusDark dark:md:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm aspect-square h-8&quot; data-state=&quot;closed&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-text-opacity: 1; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; animation-duration: 0.3s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); appearance: button; aspect-ratio: 1 / 1; background-image: none; border-color: rgb(229, 231, 235); border-radius: 9999px; border-style: solid; border-width: 0px; cursor: pointer; display: inline-flex; font-family: var(--font-fk-grotesk-neue),ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,&amp;quot;Segoe UI&amp;quot;,Roboto,Helvetica Neue,Arial,&amp;quot;Noto Sans&amp;quot;,sans-serif,&amp;quot;Apple Color Emoji&amp;quot;,&amp;quot;Segoe UI Emoji&amp;quot;,&amp;quot;Segoe UI Symbol&amp;quot;,&amp;quot;Noto Color Emoji&amp;quot;; font-feature-settings: inherit; font-size: 0.875rem; font-variation-settings: inherit; font-weight: inherit; height: 2rem; justify-content: center; letter-spacing: inherit; line-height: 1.25rem; margin: 0px; outline-offset: 2px; outline: transparent solid 2px; padding: 0px; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-wrap: nowrap; transform-origin: center center; transition-duration: 0.3s; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); user-select: none;&quot; type=&quot;button&quot;&gt;&lt;div class=&quot;flex items-center min-w-0 justify-center gap-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: flex; gap: var(--size-xs); justify-content: center; min-width: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;svg-inline--fa fa-clipboard fa-fw fa-1x&quot; data-icon=&quot;clipboard&quot; data-prefix=&quot;far&quot; focusable=&quot;false&quot; role=&quot;img&quot; viewbox=&quot;0 0 384 512&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;path d=&quot;M280 64h40c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128C0 92.7 28.7 64 64 64h40 9.6C121 27.5 153.3 0 192 0s71 27.5 78.4 64H280zM64 112c-8.8 0-16 7.2-16 16V448c0 8.8 7.2 16 16 16H320c8.8 0 16-7.2 16-16V128c0-8.8-7.2-16-16-16H304v24c0 13.3-10.7 24-24 24H192 104c-13.3 0-24-10.7-24-24V112H64zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt;&lt;/button&gt;&lt;button aria-label=&quot;쿼리 편집&quot; class=&quot;focus-visible:bg-offsetPlus dark:focus-visible:bg-offsetPlusDark md:hover:bg-offsetPlus text-textOff dark:text-textOffDark md:hover:text-textMain dark:md:hover:bg-offsetPlusDark dark:md:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm aspect-square h-8&quot; data-state=&quot;closed&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-text-opacity: 1; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; animation-duration: 0.3s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); appearance: button; aspect-ratio: 1 / 1; background-image: none; border-color: rgb(229, 231, 235); border-radius: 9999px; border-style: solid; border-width: 0px; cursor: pointer; display: inline-flex; font-family: var(--font-fk-grotesk-neue),ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,&amp;quot;Segoe UI&amp;quot;,Roboto,Helvetica Neue,Arial,&amp;quot;Noto Sans&amp;quot;,sans-serif,&amp;quot;Apple Color Emoji&amp;quot;,&amp;quot;Segoe UI Emoji&amp;quot;,&amp;quot;Segoe UI Symbol&amp;quot;,&amp;quot;Noto Color Emoji&amp;quot;; font-feature-settings: inherit; font-size: 0.875rem; font-variation-settings: inherit; font-weight: inherit; height: 2rem; justify-content: center; letter-spacing: inherit; line-height: 1.25rem; margin: 0px; outline-offset: 2px; outline: transparent solid 2px; padding: 0px; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-wrap: nowrap; transform-origin: center center; transition-duration: 0.3s; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); user-select: none;&quot; type=&quot;button&quot;&gt;&lt;div class=&quot;flex items-center min-w-0 justify-center gap-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: flex; gap: var(--size-xs); justify-content: center; min-width: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;svg-inline--fa fa-pen-to-square fa-fw fa-1x&quot; data-icon=&quot;pen-to-square&quot; data-prefix=&quot;far&quot; focusable=&quot;false&quot; role=&quot;img&quot; viewbox=&quot;0 0 512 512&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;path d=&quot;M441 58.9L453.1 71c9.4 9.4 9.4 24.6 0 33.9L424 134.1 377.9 88 407 58.9c9.4-9.4 24.6-9.4 33.9 0zM209.8 256.2L344 121.9 390.1 168 255.8 302.2c-2.9 2.9-6.5 5-10.4 6.1l-58.5 16.7 16.7-58.5c1.1-3.9 3.2-7.5 6.1-10.4zM373.1 25L175.8 222.2c-8.7 8.7-15 19.4-18.3 31.1l-28.6 100c-2.4 8.4-.1 17.4 6.1 23.6s15.2 8.5 23.6 6.1l100-28.6c11.8-3.4 22.5-9.7 31.1-18.3L487 138.9c28.1-28.1 28.1-73.7 0-101.8L474.9 25C446.8-3.1 401.2-3.1 373.1 25zM88 64C39.4 64 0 103.4 0 152V424c0 48.6 39.4 88 88 88H360c48.6 0 88-39.4 88-88V312c0-13.3-10.7-24-24-24s-24 10.7-24 24V424c0 22.1-17.9 40-40 40H88c-22.1 0-40-17.9-40-40V152c0-22.1 17.9-40 40-40H200c13.3 0 24-10.7 24-24s-10.7-24-24-24H88z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt;&lt;/button&gt;&lt;div style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: oklch(0.99 0.004 106.471); border: 0px solid rgb(229, 231, 235); box-sizing: border-box; font-size: 16px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;span style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;button class=&quot;focus-visible:bg-offsetPlus dark:focus-visible:bg-offsetPlusDark md:hover:bg-offsetPlus text-textOff dark:text-textOffDark md:hover:text-textMain dark:md:hover:bg-offsetPlusDark dark:md:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm aspect-square h-8&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-text-opacity: 1; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; animation-duration: 0.3s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); appearance: button; aspect-ratio: 1 / 1; background-image: none; border-color: rgb(229, 231, 235); border-radius: 9999px; border-style: solid; border-width: 0px; cursor: pointer; display: inline-flex; font-family: var(--font-fk-grotesk-neue),ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,&amp;quot;Segoe UI&amp;quot;,Roboto,Helvetica Neue,Arial,&amp;quot;Noto Sans&amp;quot;,sans-serif,&amp;quot;Apple Color Emoji&amp;quot;,&amp;quot;Segoe UI Emoji&amp;quot;,&amp;quot;Segoe UI Symbol&amp;quot;,&amp;quot;Noto Color Emoji&amp;quot;; font-feature-settings: inherit; font-size: 0.875rem; font-variation-settings: inherit; font-weight: inherit; height: 2rem; justify-content: center; letter-spacing: inherit; line-height: 1.25rem; margin: 0px; outline-offset: 2px; outline: transparent solid 2px; padding: 0px; position: relative; scrollbar-color: auto; scrollbar-width: auto; text-wrap: nowrap; transform-origin: center center; transition-duration: 0.3s; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); user-select: none;&quot; type=&quot;button&quot;&gt;&lt;div class=&quot;flex items-center min-w-0 justify-center gap-xs&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; align-items: center; border: 0px solid rgb(229, 231, 235); box-sizing: border-box; display: flex; gap: var(--size-xs); justify-content: center; min-width: 0px; scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; class=&quot;svg-inline--fa fa-ellipsis fa-fw fa-1x&quot; data-icon=&quot;ellipsis&quot; data-prefix=&quot;far&quot; focusable=&quot;false&quot; role=&quot;img&quot; viewbox=&quot;0 0 448 512&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;path d=&quot;M432 256a48 48 0 1 1 -96 0 48 48 0 1 1 96 0zm-160 0a48 48 0 1 1 -96 0 48 48 0 1 1 96 0zM64 304a48 48 0 1 1 0-96 48 48 0 1 1 0 96z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt;&lt;/button&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;hidden rounded border p-xs md:block border-borderMain/50 ring-borderMain/50 divide-borderMain/50 dark:divide-borderMainDark/50 dark:ring-borderMainDark/50 dark:border-borderMainDark/50 bg-transparent&quot; style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: oklch(var(--border-color-100)/0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 #0000; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: oklch(0.99 0.004 106.471); border-color: oklch(var(--border-color-100)/.5); border-image: initial; border-radius: 0.25rem; border-style: solid; border-width: 1px; box-sizing: border-box; font-size: 16px; padding: var(--size-xs); scrollbar-color: auto; scrollbar-width: auto;&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description>
      <category>생활</category>
      <category>계획</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/424</guid>
      <comments>https://syaku.tistory.com/424#entry424comment</comments>
      <pubDate>Fri, 1 Nov 2024 14:06:40 +0900</pubDate>
    </item>
    <item>
      <title>HTTP/2 통신에 대한 정리</title>
      <link>https://syaku.tistory.com/423</link>
      <description>&lt;p&gt;HTTP/2 통신에 대한 정리&lt;/p&gt;
&lt;h2 id=&quot;http-2-&quot;&gt;HTTP/2란?&lt;/h2&gt;
&lt;p&gt;HTTP/2는 웹 성능을 개선하기 위해 설계된 HTTP 프로토콜의 주요 개정 버전입니다. 멀티플렉싱, 헤더 압축, 서버 푸시 등의 기능을 통해 웹 페이지 로딩 속도를 크게 향상시킵니다.&lt;/p&gt;
&lt;h2 id=&quot;http-2-&quot;&gt;HTTP/2의 주요 특징&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;멀티플렉싱: 단일 TCP 연결로 여러 요청을 동시에 처리&lt;/li&gt;
&lt;li&gt;헤더 압축: HPACK 압축으로 HTTP 헤더 크기 감소&lt;/li&gt;
&lt;li&gt;서버 푸시: 클라이언트 요청 없이 서버가 리소스를 미리 전송&lt;/li&gt;
&lt;li&gt;스트림 우선순위 지정: 중요한 리소스를 먼저 로드&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;http-2-&quot;&gt;HTTP/2 성능 향상&lt;/h2&gt;
&lt;p&gt;벤치마크 테스트 결과, HTTP/2는 HTTP/1.1에 비해 약 31% 낮은 latency를 보여줍니다. 그러나 실제 성능 향상은 네트워크 조건, 웹사이트 구조, 리소스 유형 등에 따라 다를 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;-spring-boot-&quot;&gt;서버 구현 (Spring Boot)&lt;/h2&gt;
&lt;p&gt;Spring Boot에서 HTTP/2를 활성화하려면 다음 설정이 필요합니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;application.properties 파일에 설정 추가:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server&lt;span class=&quot;hljs-selector-class&quot;&gt;.http2&lt;/span&gt;&lt;span class=&quot;hljs-selector-class&quot;&gt;.enabled&lt;/span&gt;=true
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSL/TLS 설정 (HTTPS 필요)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;적절한 의존성 추가 (예: spring-boot-starter-web)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;-javascript-&quot;&gt;클라이언트 구현 (JavaScript)&lt;/h2&gt;
&lt;p&gt;JavaScript에서는 HTTP/2를 명시적으로 요청할 필요가 없습니다. 브라우저가 자동으로 처리합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;fetch&lt;/span&gt;('https://example.com/api/&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;data&lt;/span&gt;')&lt;/span&gt;
  .&lt;span class=&quot;hljs-keyword&quot;&gt;then&lt;/span&gt;(response =&amp;gt; response.json())
  .&lt;span class=&quot;hljs-keyword&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;data&lt;/span&gt; =&amp;gt; console.log(&lt;span class=&quot;hljs-title&quot;&gt;data&lt;/span&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;http-2-&quot;&gt;HTTP/2 사용 확인 방법&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Chrome 개발자 도구: Network 탭에서 Protocol 열 확인 (&amp;quot;h2&amp;quot; 표시)&lt;/li&gt;
&lt;li&gt;브라우저 확장 프로그램: &amp;quot;HTTP/2 and SPDY Indicator&amp;quot; 등 사용&lt;/li&gt;
&lt;li&gt;온라인 도구: keycdn.com 등의 HTTP/2 테스트 도구 활용&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;http-2-&quot;&gt;HTTP/2 사용을 위한 요구사항&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;HTTPS 사용 (대부분의 브라우저에서 필수)&lt;/li&gt;
&lt;li&gt;서버의 HTTP/2 지원&lt;/li&gt;
&lt;li&gt;최신 브라우저 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;-ssl-&quot;&gt;무료 SSL 인증서&lt;/h2&gt;
&lt;p&gt;HTTP/2 사용을 위한 HTTPS 설정에 무료 SSL 인증서를 활용할 수 있습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let&amp;#39;s Encrypt: 가장 인기 있는 무료 SSL 제공 업체&lt;/li&gt;
&lt;li&gt;ZeroSSL: 90일 유효기간, 브라우저 호환성 우수&lt;/li&gt;
&lt;li&gt;Cloudflare: CDN 서비스와 함께 제공&lt;/li&gt;
&lt;li&gt;SSL For Free: 상업적 사용 가능, 서브도메인 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;무료 SSL 인증서는 대부분 90일 유효기간을 가지며, 정기적인 갱신이 필요합니다. 개인 웹사이트나 소규모 프로젝트에 적합하지만, 대규모 비즈니스의 경우 유료 SSL 인증서를 고려해야 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;HTTP/2는 웹 성능을 크게 향상시키는 강력한 도구입니다. 서버 설정과 HTTPS 사용만으로도 대부분의 최신 브라우저에서 자동으로 활용될 수 있어, 웹 개발자들이 쉽게 도입할 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>http2</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/423</guid>
      <comments>https://syaku.tistory.com/423#entry423comment</comments>
      <pubDate>Fri, 1 Nov 2024 14:05:49 +0900</pubDate>
    </item>
    <item>
      <title>Visual Studio Code에서 llama 3.2 사용하기</title>
      <link>https://syaku.tistory.com/422</link>
      <description>&lt;p&gt;Visual Studio Code에서 Ollama를 사용하는 방법은 다음과 같습니다. Ollama는 다양한 AI 모델을 로컬에서 실행하고 관리할 수 있는 도구로, 특히 코딩 작업에 유용합니다.&lt;/p&gt;
&lt;h2 id=&quot;ollama-&quot;&gt;Ollama 사용 준비&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ollama 설치&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MacOS&lt;/strong&gt;: Homebrew를 통해 설치할 수 있습니다. 터미널에서 다음 명령어를 실행하세요:&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;brew &lt;/span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;install &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux 및 WSL2&lt;/strong&gt;: 공식 웹사이트에서 설치 스크립트를 다운로드하여 실행합니다:&lt;pre&gt;&lt;code&gt;curl http&lt;span class=&quot;hljs-variable&quot;&gt;s:&lt;/span&gt;//ollama.ai/install.&lt;span class=&quot;hljs-keyword&quot;&gt;sh&lt;/span&gt; | &lt;span class=&quot;hljs-keyword&quot;&gt;sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt;: 현재 WSL2를 통해 설치 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ollama 버전 확인&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설치가 완료되면 터미널에서 &lt;code&gt;ollama -v&lt;/code&gt; 명령어로 버전을 확인하여 올바르게 설치되었는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;visual-studio-code-ollama-&quot;&gt;Visual Studio Code에서 Ollama 사용&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CodeGPT 확장 설치&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual Studio Code를 열고 확장(Extension) 탭으로 이동한 후, &amp;quot;CodeGPT&amp;quot;를 검색하여 설치합니다.&lt;/li&gt;
&lt;li&gt;설치가 완료되면 VS Code의 사이드바에 CodeGPT 아이콘이 나타납니다.&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;pre&gt;&lt;code&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;ollama&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;pull&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;llama3&lt;/span&gt;&lt;span class=&quot;hljs-selector-pseudo&quot;&gt;:8b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;다운로드가 완료되면 &lt;code&gt;ollama serve&lt;/code&gt; 명령어로 Ollama 서버를 시작하고, &lt;code&gt;ollama run llama3&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;ul&gt;
&lt;li&gt;VS Code의 CodeGPT 대시보드에서 Provider 드롭다운 메뉴를 통해 Ollama를 선택하고, 모델 드롭다운에서 다운로드한 모델을 선택합니다.&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;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; 프롬프트 이후에 텍스트를 입력하여 모델과 대화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;코드 작성, 디버깅 등의 작업을 수행할 수 있으며, 필요한 경우 추가적인 프롬프트를 제공하여 모델의 응답을 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 과정을 통해 Visual Studio Code 환경에서 Ollama와 다양한 AI 모델을 활용하여 코딩 작업을 보다 효율적으로 수행할 수 있습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <category>vscode</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/422</guid>
      <comments>https://syaku.tistory.com/422#entry422comment</comments>
      <pubDate>Fri, 1 Nov 2024 14:05:05 +0900</pubDate>
    </item>
    <item>
      <title>게임 영상 편집을 위한 프로그램 추천</title>
      <link>https://syaku.tistory.com/421</link>
      <description>&lt;p&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); font-family: __fkGroteskNeue_598ab8, __fkGroteskNeue_Fallback_598ab8, ui-sans-serif, system-ui, -apple-system, &amp;quot;system-ui&amp;quot;, &amp;quot;Segoe UI&amp;quot;, Roboto, &amp;quot;Helvetica Neue&amp;quot;, Arial, &amp;quot;Noto Sans&amp;quot;, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; font-size: 16px;&quot;&gt;이 글에서는 게임 영상 편집에 적합한 다양한 프로그램을 소개하고, 각 프로그램의 추천 이유, 장점과 단점, 가격, 지원 파일 형식(WebM 포함), 그리고 권장 사양을 자세히 설명합니다. 초보자부터 전문가까지 다양한 사용자 요구를 충족할 수 있는 필모라, 다빈치 리졸브, VSDC Free Video Editor, OpenShot, iMovie, Adobe Premiere Pro 등의 프로그램을 비교하여 자신의 필요에 맞는 최적의 선택을 할 수 있도록 돕습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;-1-filmora-&quot;&gt;&lt;strong&gt;1. 필모라 (Filmora)&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추천 이유&lt;/strong&gt;: 필모라는 직관적인 인터페이스와 다양한 효과 및 템플릿을 제공하여 초보자도 쉽게 사용할 수 있습니다. 또한, AI 기반의 다양한 기능을 통해 빠르고 쉽게 편집할 수 있습니다.&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;/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;WebM 지원 제한&lt;/strong&gt;: WebM 파일 형식을 기본적으로 지원하지 않음.&lt;/li&gt;
&lt;/ul&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;: MP4, MOV, AVI, MKV 등 일반적인 비디오 형식 지원.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권장 사양&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;운영 체제: Windows 10 (64비트)&lt;/li&gt;
&lt;li&gt;프로세서: Intel i5 이상&lt;/li&gt;
&lt;li&gt;메모리: 8GB RAM&lt;/li&gt;
&lt;li&gt;그래픽 카드: Intel HD Graphics 5000 이상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;macOS&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;운영 체제: macOS v10.12 이상&lt;/li&gt;
&lt;li&gt;프로세서: Intel i5 이상&lt;/li&gt;
&lt;li&gt;메모리: 8GB RAM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;-2-davinci-resolve-&quot;&gt;&lt;strong&gt;2. 다빈치 리졸브 (DaVinci Resolve)&lt;/strong&gt;&lt;/h2&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;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;WebM 지원 제한&lt;/strong&gt;: WebM은 기본적으로 지원되지 않으며, 특정 코덱으로 인코딩된 경우에만 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가격&lt;/strong&gt;: 기본 버전은 무료이며, 스튜디오 버전은 $295.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지원 파일 형식&lt;/strong&gt;: MP4, MOV, AVI 등 다양한 포맷 지원.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권장 사양&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows 및 macOS 공통&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;운영 체제: Windows 10 (64비트), macOS 10.14.6 이상&lt;/li&gt;
&lt;li&gt;메모리: 최소 16GB, 권장 32GB&lt;/li&gt;
&lt;li&gt;그래픽 카드: 최소 GPU 메모리 2GB, 최신 버전 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;-3-vsdc-free-video-editor-&quot;&gt;&lt;strong&gt;3. VSDC Free Video Editor&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추천 이유&lt;/strong&gt;: VSDC는 무료로 제공되면서도 다양한 비디오 편집 기능을 제공하여 예산이 제한된 사용자에게 적합합니다. 특히 게임 영상 편집에 추천됩니다.&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;: WebM 포함 다양한 비디오 형식 지원.&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;macOS 미지원&lt;/strong&gt;: macOS에서는 사용할 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가격&lt;/strong&gt;: 기본 버전은 무료이며, 프리미엄 기능은 $19.9.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지원 파일 형식&lt;/strong&gt;: MP4, AVI, MKV 등 다양한 비디오 형식 지원.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권장 사양&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;운영 체제: Windows XP SP3 이상&lt;/li&gt;
&lt;li&gt;프로세서: Intel 또는 AMD 프로세서&lt;/li&gt;
&lt;li&gt;메모리: 최소 2GB RAM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;-4-openshot-&quot;&gt;&lt;strong&gt;4. OpenShot&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추천 이유&lt;/strong&gt;: OpenShot은 무료 및 오픈 소스 소프트웨어로 다양한 플랫폼에서 사용할 수 있으며 간단한 편집 작업에 적합합니다.&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;WebM 지원 포함&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;가격 100% 무료&lt;/li&gt;
&lt;li&gt;지원 파일 형식 MP4, MOV, AVI 등 대부분의 비디오 형식 지원&lt;/li&gt;
&lt;li&gt;권장 사양 
 운영 체제 Windows, macOS, Linux 
 프로세서 멀티코어 프로세서 
 메모리 최소 4GB RAM &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;-5-imovie-&quot;&gt;&lt;strong&gt;5. 아이무비 (iMovie)&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추천 이유&lt;/strong&gt;: iMovie는 Apple 기기 사용자에게 적합하며 간단한 비디오 편집 작업에 유용합니다. iOS와 macOS 간의 원활한 통합이 장점입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;직관적인 인터페이스&lt;/strong&gt;: Apple 제품과의 통합성이 뛰어남.&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;WebM 미지원&lt;/strong&gt;: WebM 파일 형식을 지원하지 않음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows 미지원&lt;/strong&gt;: Windows에서는 사용할 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가격&lt;/strong&gt;: 무료 (Apple 기기 전용).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지원 파일 형식&lt;/strong&gt;: MP4, MOV 등 Apple 표준 포맷 지원.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권장 사양&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS 전용&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;iMovie는 기본적으로 macOS에 포함되어 있으며, Mac 컴퓨터에서 사용 가능합니다.&lt;/li&gt;
&lt;li&gt;최소 사양으로는 macOS와 호환되는 모든 Mac에서 사용할 수 있으며, 고성능을 위해서는 최신 Mac 모델을 권장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;-6-adobe-premiere-pro-&quot;&gt;&lt;strong&gt;6. 어도비 프리미어 프로 (Adobe Premiere Pro)&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추천 이유&lt;/strong&gt;: Adobe Premiere Pro는 전문적인 비디오 편집에 널리 사용되며 다양한 고급 기능을 제공합니다. 협업 작업에 특히 유리합니다.&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;/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;WebM은 기본적으로 지원되지 않지만 플러그인을 통해 가능&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가격&lt;/strong&gt;: 월 $20.99부터 시작하는 구독형 모델.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지원 파일 형식&lt;/strong&gt;: 거의 모든 비디오 및 오디오 포맷 지원 (MP4, MOV, AVI 등).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;권장 사양&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;프로세서: Intel® 7세대 이상 CPU 또는 AMD Ryzen™ 3000 시리즈 이상&lt;/li&gt;
&lt;li&gt;운영 체제: Windows 10(64비트) V20H2 이상&lt;/li&gt;
&lt;li&gt;메모리: 16GB (HD 미디어용), 32GB (4K 미디어용)&lt;/li&gt;
&lt;li&gt;그래픽 카드: NVIDIA GeForce GTX 1060 이상&lt;/li&gt;
&lt;li&gt;디스플레이: 1920 x 1080 이상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;macOS&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;프로세서: Intel® 7세대 이상 CPU 또는 Apple silicon M1 이상&lt;/li&gt;
&lt;li&gt;운영 체제: macOS 11.0(Big Sur) 이상&lt;/li&gt;
&lt;li&gt;메모리 및 그래픽 카드: Apple silicon 통합 메모리 16GB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;각 프로그램은 특정 요구 사항과 기술 수준에 따라 선택할 수 있으며 자신의 필요에 맞는 프로그램을 선택하는 것이 중요합니다. 고해상도 영상 편집이나 복잡한 프로젝트를 진행할 경우 권장 사양 이상의 시스템을 사용하는 것이 좋습니다.&lt;/p&gt;</description>
      <category>생활</category>
      <category>영상</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/421</guid>
      <comments>https://syaku.tistory.com/421#entry421comment</comments>
      <pubDate>Fri, 1 Nov 2024 14:03:55 +0900</pubDate>
    </item>
    <item>
      <title>국채 금리 완벽 이해하기</title>
      <link>https://syaku.tistory.com/420</link>
      <description>&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://img.freepik.com/free-vector/finance-financial-performance-concept-illustration_53876-40450.jpg&quot; alt=&quot;금융 시장을 상징하는 그래프와 차트가 있는 이미지&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 분들이 뉴스나 경제 기사에서 자주 접하는 '국채 금리'. 과연 이것이 무엇이고, 우리 실생활에 어떤 영향을 미치는지 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;국채란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국채는 국가가 자금을 조달하기 위해 발행하는 채권입니다. 쉽게 말해 정부가 돈을 빌리는 증서라고 생각하면 됩니다. 국가가 보증하기 때문에 가장 안전한 투자 수단으로 여겨집니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://d2u1z1lopyfwlx.cloudfront.net/thumbnails/66a99f7a-2fc1-5395-879e-61b4f2bd81c9/10f1907b-2567-564d-b1ad-0ade96af1083.jpg&quot; alt=&quot;국채와 금융 관련 서류를 보여주는 이미지&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;국채 금리의 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국채 금리는 크게 두 가지로 나눌 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;표면 금리&lt;/b&gt;: 채권 발행 시 정해진 고정 이자율&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실질 수익률&lt;/b&gt;: 시장에서 거래될 때의 실제 수익률&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;국채 금리 상승의 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국채 금리가 오른다는 것은 다음과 같은 상황을 의미합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 직접적 영향&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정부의 이자 부담 증가&lt;/li&gt;
&lt;li&gt;시중 대출 금리 상승&lt;/li&gt;
&lt;li&gt;기업 자금조달 비용 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 경제적 의미&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경기 회복 신호일 수 있음&lt;/li&gt;
&lt;li&gt;물가 상승 압력 존재&lt;/li&gt;
&lt;li&gt;기준금리 인상 가능성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://burst.shopifycdn.com/photos/making-a-budget-tracking-finances.jpg?exif=0&amp;amp;format=pjpg&amp;amp;iptc=0&amp;amp;width=1000&quot; alt=&quot;금리와 경제 관계를 보여주는 그래프&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;국채 금리와 가격의 관계: 실제 예시로 이해하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황을 가정해보겠습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작년에 발행된 국채: 연 5% 이자(금리) 제공&lt;/li&gt;
&lt;li&gt;올해 새로 발행되는 국채: 연 6% 이자(금리) 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서 투자자들은 당연히 6% 이자를 주는 새로운 국채를 선호하게 됩니다. 그러면 기존의 5% 국채는 어떻게 될까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가격 하락의 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 5% 국채의 소유자가 이를 팔려고 할 때, 아무도 정가로 사려 하지 않을 것입니다. 왜냐하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 돈으로 6% 수익을 주는 새 국채를 살 수 있기 때문&lt;/li&gt;
&lt;li&gt;따라서 5% 국채는 할인된 가격에 팔 수밖에 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 포인트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;국채 금리와 가격은 &lt;b&gt;반비례 관계&lt;/b&gt;입니다&lt;/li&gt;
&lt;li&gt;금리가 오르면 &amp;rarr; 기존 채권의 가치는 떨어짐&lt;/li&gt;
&lt;li&gt;금리가 내리면 &amp;rarr; 기존 채권의 가치는 올라감&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 관계는 마치 시소와 같아서, 한쪽이 오르면 다른 쪽은 반드시 내려가는 구조를 가지고 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국채 금리는 단순한 숫자가 아닌, 경제 전반의 흐름을 보여주는 중요한 지표입니다. 금리 변동은 우리의 일상생활, 특히 대출이나 투자와 밀접한 관련이 있으므로, 기본적인 개념을 이해하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>경제</category>
      <category>국채</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/420</guid>
      <comments>https://syaku.tistory.com/420#entry420comment</comments>
      <pubDate>Fri, 1 Nov 2024 13:58:40 +0900</pubDate>
    </item>
    <item>
      <title>In Kotlin, using @field:NotNull instead of @NotNull is important.</title>
      <link>https://syaku.tistory.com/419</link>
      <description>&lt;p&gt;In Kotlin, using &lt;code&gt;@field:NotNull&lt;/code&gt; instead of &lt;code&gt;@NotNull&lt;/code&gt; is important when you want to ensure that annotations are applied directly to the field level, rather than just the constructor parameter. This distinction is crucial for interoperability with Java frameworks that rely on field-level annotations for validation, such as Spring.&lt;/p&gt;
&lt;h3 id=&quot;why-use-field-notnull-&quot;&gt;Why Use &lt;code&gt;@field:NotNull&lt;/code&gt;?&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Field-Level Annotation&lt;/strong&gt;: By default, Kotlin applies annotations like &lt;code&gt;@NotNull&lt;/code&gt; to the constructor parameters. This means that if you annotate a property with &lt;code&gt;@NotNull&lt;/code&gt;, it might not be applied to the field itself, which can be problematic when using Java-based validation frameworks that expect field-level annotations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Java Interoperability&lt;/strong&gt;: Many Java frameworks, including Spring, perform validation by inspecting field annotations. If the annotation is only present on the constructor parameter, these frameworks may not trigger the expected validation logic. By using &lt;code&gt;@field:NotNull&lt;/code&gt;, you explicitly apply the annotation to the underlying field.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation Frameworks&lt;/strong&gt;: As highlighted in the Stack Overflow discussion and other resources, using &lt;code&gt;@field:NotNull&lt;/code&gt; ensures that validation frameworks like Bean Validation (JSR 303/380) correctly recognize and enforce constraints on fields during runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;example-usage&quot;&gt;Example Usage&lt;/h3&gt;
&lt;p&gt;Here's how you would typically use &lt;code&gt;@field:NotNull&lt;/code&gt; in a Kotlin data class to ensure proper validation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-kotlin&quot;&gt;&lt;span class=&quot;hljs-selector-tag&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-selector-tag&quot;&gt;User&lt;/span&gt;(
    &lt;span class=&quot;hljs-variable&quot;&gt;@field&lt;/span&gt;:NotNull val &lt;span class=&quot;hljs-attribute&quot;&gt;name&lt;/span&gt;: String,
    &lt;span class=&quot;hljs-variable&quot;&gt;@field&lt;/span&gt;:NotBlank val &lt;span class=&quot;hljs-attribute&quot;&gt;email&lt;/span&gt;: String
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, both &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; fields are annotated with their respective validation constraints at the field level, ensuring that any Java-based validation framework can correctly apply these constraints.&lt;/p&gt;
&lt;h3 id=&quot;insights-from-stack-overflow&quot;&gt;Insights from Stack Overflow&lt;/h3&gt;
&lt;p&gt;The Stack Overflow discussion emphasizes that in Kotlin, non-nullable types (&lt;code&gt;String&lt;/code&gt;, not &lt;code&gt;String?&lt;/code&gt;) inherently provide null safety without needing &lt;code&gt;@NotNull&lt;/code&gt;. However, when integrating with Java frameworks that rely on reflection to enforce constraints, using site-specific targets like &lt;code&gt;@field:NotNull&lt;/code&gt; becomes necessary.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Using &lt;code&gt;@field:NotNull&lt;/code&gt; is essential for ensuring compatibility with Java libraries and frameworks that expect field-level annotations. It provides a clear way to apply constraints directly to fields, enabling proper validation and avoiding potential pitfalls associated with constructor parameter annotations in Kotlin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/41993706/is-notnull-needed-on-kotlin/41994512&quot;&gt;https://stackoverflow.com/questions/41993706/is-notnull-needed-on-kotlin/41994512&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발</category>
      <category>Kotlin</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/419</guid>
      <comments>https://syaku.tistory.com/419#entry419comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:59:54 +0900</pubDate>
    </item>
    <item>
      <title>Basic Usage of URL Pattern API</title>
      <link>https://syaku.tistory.com/418</link>
      <description>&lt;h1 id=&quot;basic-usage-of-url-pattern-api&quot;&gt;&lt;br /&gt;&lt;/h1&gt;
&lt;p&gt;The URL Pattern API provides a way to define and match URL patterns on the web platform. It supports wildcards, named groups, and regular expression groups, allowing you to match entire URLs or specific components.&lt;/p&gt;&lt;p&gt;https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API&lt;/p&gt;
&lt;h2 id=&quot;basic-usage&quot;&gt;Basic Usage&lt;/h2&gt;
&lt;h3 id=&quot;1-creating-a-urlpattern&quot;&gt;1. Creating a URLPattern&lt;/h3&gt;
&lt;p&gt;You can create a &lt;code&gt;URLPattern&lt;/code&gt; object to define a URL pattern. Patterns can be specified using an object for each URL component or as a string for the entire URL.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Define pattern using an object&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; pattern = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; URLPattern({ pathname: &lt;span class=&quot;hljs-string&quot;&gt;'/books/:id'&lt;/span&gt; });

&lt;span class=&quot;hljs-comment&quot;&gt;// Define pattern using a string&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; patternString = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; URLPattern(&lt;span class=&quot;hljs-string&quot;&gt;'https://example.com/books/:id'&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-matching-patterns&quot;&gt;2. Matching Patterns&lt;/h3&gt;
&lt;p&gt;Use the &lt;code&gt;test()&lt;/code&gt; method to check if a specific URL matches the pattern.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;console&lt;/span&gt;.log(pattern.test(&lt;span class=&quot;hljs-string&quot;&gt;'https://example.com/books/123'&lt;/span&gt;)); &lt;span class=&quot;hljs-regexp&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;console&lt;/span&gt;.log(patternString.test(&lt;span class=&quot;hljs-string&quot;&gt;'https://example.com/books/456'&lt;/span&gt;)); &lt;span class=&quot;hljs-regexp&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-extracting-groups&quot;&gt;3. Extracting Groups&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;exec()&lt;/code&gt; method allows you to extract matching parts, which is useful for named groups.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;result&lt;/span&gt; = pattern.exec(&lt;span class=&quot;hljs-string&quot;&gt;'https://example.com/books/123'&lt;/span&gt;);
console.log(&lt;span class=&quot;hljs-keyword&quot;&gt;result&lt;/span&gt;.pathname.groups.id); &lt;span class=&quot;hljs-comment&quot;&gt;// '123'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-using-wildcards-and-regular-expressions&quot;&gt;4. Using Wildcards and Regular Expressions&lt;/h3&gt;
&lt;p&gt;Patterns can include wildcards (&lt;code&gt;*&lt;/code&gt;) or regular expressions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; wildcardPattern = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; URLPattern({ &lt;span class=&quot;hljs-attr&quot;&gt;hostname&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;'*'&lt;/span&gt;, &lt;span class=&quot;hljs-attr&quot;&gt;pathname&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;'/foo/*'&lt;/span&gt; });
&lt;span class=&quot;hljs-built_in&quot;&gt;console&lt;/span&gt;.log(wildcardPattern.test(&lt;span class=&quot;hljs-string&quot;&gt;'https://anydomain.com/foo/bar'&lt;/span&gt;)); &lt;span class=&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; regexPattern = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; URLPattern(&lt;span class=&quot;hljs-string&quot;&gt;'/post/:id(\\d+)'&lt;/span&gt;);
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; namedGroup = regexPattern.exec(&lt;span class=&quot;hljs-string&quot;&gt;'https://example.com/post/123'&lt;/span&gt;).pathname.groups;
&lt;span class=&quot;hljs-built_in&quot;&gt;console&lt;/span&gt;.log(namedGroup.id); &lt;span class=&quot;hljs-comment&quot;&gt;// '123'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;considerations&quot;&gt;Considerations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Browser Support&lt;/strong&gt;: This API is supported in some browsers like Chrome, Edge, and Opera.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experimental Feature&lt;/strong&gt;: Ensure compatibility before using it in production environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The URL Pattern API helps standardize routing in web applications by providing a structured way to handle URLs.&lt;/p&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/418</guid>
      <comments>https://syaku.tistory.com/418#entry418comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:59:24 +0900</pubDate>
    </item>
    <item>
      <title>Setting Up Jest for Testing in Node.js</title>
      <link>https://syaku.tistory.com/417</link>
      <description>&lt;h3 id=&quot;setting-up-jest-for-testing-in-node-js&quot;&gt;Setting Up Jest for Testing in Node.js&lt;/h3&gt;
&lt;p&gt;When using Jest to test Node.js applications, especially those utilizing ES modules, configuring your environment correctly is crucial. Here's a detailed guide on why and how to set up Jest with Babel for such scenarios:&lt;/p&gt;
&lt;h3 id=&quot;why-use-babel-with-jest-for-es-modules&quot;&gt;Why Use Babel with Jest for ES Modules&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compatibility with ES Modules&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js traditionally uses CommonJS, but recent versions support ES modules. Jest defaults to CommonJS, so Babel helps by transforming ES module syntax into CommonJS, ensuring compatibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modern JavaScript Syntax&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Babel allows you to use the latest JavaScript features by converting them into a compatible format for the current JavaScript standard supported by your environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jest's Default Behavior&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jest automatically uses &lt;code&gt;babel-jest&lt;/code&gt; if a Babel configuration exists, allowing you to write tests using modern syntax without compatibility issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling Non-Standard Syntax&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your code or dependencies use non-standard syntax, Babel can transform this into valid JavaScript that Jest can execute.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;how-to-set-up-jest-with-babel&quot;&gt;How to Set Up Jest with Babel&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install Dependencies&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;npm &lt;span class=&quot;hljs-keyword&quot;&gt;install &lt;/span&gt;--save-dev &lt;span class=&quot;hljs-keyword&quot;&gt;jest &lt;/span&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;babel-jest &lt;/span&gt;@&lt;span class=&quot;hljs-keyword&quot;&gt;babel/core &lt;/span&gt;@&lt;span class=&quot;hljs-keyword&quot;&gt;babel/preset-env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a Babel Configuration&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;babel.config.js&lt;/code&gt;:&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;module&lt;/span&gt;.exports = {
  presets: &lt;span class=&quot;hljs-string&quot;&gt;[['@babel/preset-env', { targets: { node: 'current' } }]]&lt;/span&gt;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure Jest&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ensure your &lt;code&gt;jest.config.js&lt;/code&gt; or &lt;code&gt;jest.config.cjs&lt;/code&gt; includes Babel transformation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;module.exports = {
&lt;span class=&quot;hljs-string&quot;&gt;verbose:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,
&lt;span class=&quot;hljs-string&quot;&gt;testEnvironment:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'node'&lt;/span&gt;,
&lt;span class=&quot;hljs-string&quot;&gt;testRegex:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'(/__tests__/.*|(\\.|/)(test|spec))\\.js$'&lt;/span&gt;,
&lt;span class=&quot;hljs-string&quot;&gt;moduleFileExtensions:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;'js'&lt;/span&gt;],
&lt;span class=&quot;hljs-string&quot;&gt;transform:&lt;/span&gt; {
&lt;span class=&quot;hljs-string&quot;&gt;'^.+\\.js$'&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;'babel-jest'&lt;/span&gt;,
&lt;span class=&quot;hljs-string&quot;&gt;'^.+\\.mjs$'&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;'babel-jest'&lt;/span&gt;,
},
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run Tests&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a script in your &lt;code&gt;package.json&lt;/code&gt;:&lt;pre&gt;&lt;code class=&quot;lang-json&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&quot;scripts&quot;&lt;/span&gt;: {
  &lt;span class=&quot;hljs-string&quot;&gt;&quot;test&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;jest&quot;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By setting up Jest with Babel, you ensure that your tests can run smoothly using modern JavaScript features, even if those features are not natively supported in all environments yet. This setup is particularly beneficial for projects needing compatibility across different JavaScript versions and environments.&lt;/p&gt;</description>
      <category>개발</category>
      <category>jest</category>
      <category>nodejs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/417</guid>
      <comments>https://syaku.tistory.com/417#entry417comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:58:44 +0900</pubDate>
    </item>
    <item>
      <title>O(1)과 O(n) 시간 복잡도: 알고리즘 효율성의 핵심</title>
      <link>https://syaku.tistory.com/416</link>
      <description>&lt;p&gt;O(1)과 O(n)은 알고리즘의 시간 복잡도를 나타내는 Big O 표기법의 두 가지 중요한 유형입니다. 각각의 의미와 특징을 설명해드리겠습니다.&lt;/p&gt;
&lt;h2 id=&quot;o-1-&quot;&gt;O(1) - 상수 시간 복잡도&lt;/h2&gt;
&lt;p&gt;O(1)은 입력 크기에 관계없이 항상 일정한 시간이 걸리는 알고리즘을 나타냅니다.&lt;/p&gt;
&lt;h3 id=&quot;-&quot;&gt;특징:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;입력 데이터의 크기가 증가해도 실행 시간이 일정합니다.&lt;/li&gt;
&lt;li&gt;가장 효율적인 시간 복잡도입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-&quot;&gt;예시:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배열의 특정 인덱스에 접근하는 연산&lt;/li&gt;
&lt;li&gt;해시 테이블에서 키로 값을 조회하는 연산&lt;/li&gt;
&lt;li&gt;스택의 push와 pop 연산&lt;/li&gt;
&lt;li&gt;링크드 리스트의 맨 앞에 요소 추가/삭제&lt;/li&gt;
&lt;li&gt;큐의 enqueue와 dequeue 연산&lt;/li&gt;
&lt;li&gt;배열의 크기 확인&lt;/li&gt;
&lt;li&gt;간단한 수학적 연산&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-&quot;&gt;구현 시 주의사항:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;입력 크기에 독립적인 연산을 사용해야 합니다.&lt;/li&gt;
&lt;li&gt;루프 사용을 제한하거나, 사용 시 반복 횟수가 고정되어야 합니다.&lt;/li&gt;
&lt;li&gt;재귀 호출 사용 시 주의가 필요합니다.&lt;/li&gt;
&lt;li&gt;과도한 메모리 사용을 피해야 합니다.&lt;/li&gt;
&lt;li&gt;해시 테이블 사용 시 충돌 처리에 주의해야 합니다.&lt;/li&gt;
&lt;li&gt;일부 연산의 실제 복잡성을 고려해야 합니다.&lt;/li&gt;
&lt;li&gt;O(1)이 반드시 &quot;빠르다&quot;는 것을 의미하지는 않습니다.&lt;/li&gt;
&lt;li&gt;병렬 처리 시 실제 실행 시간이 달라질 수 있습니다.&lt;/li&gt;
&lt;li&gt;코드의 가독성과 유지보수성을 고려해야 합니다.&lt;/li&gt;
&lt;li&gt;실제 환경에서의 성능 테스트가 필요합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;o-n-&quot;&gt;O(n) - 선형 시간 복잡도&lt;/h2&gt;
&lt;p&gt;O(n)은 입력 크기에 비례하여 실행 시간이 선형적으로 증가하는 알고리즘을 나타냅니다.&lt;/p&gt;
&lt;h3 id=&quot;-&quot;&gt;특징:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;입력 데이터의 크기가 2배가 되면 실행 시간도 대략 2배가 됩니다.&lt;/li&gt;
&lt;li&gt;입력 데이터를 한 번씩 순회하는 알고리즘에서 주로 나타납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-&quot;&gt;예시:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배열에서 특정 요소를 찾는 선형 검색&lt;/li&gt;
&lt;li&gt;배열의 모든 요소를 순회하는 연산&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;o-1-o-n-&quot;&gt;O(1)과 O(n)의 주요 차이점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;성능&lt;/strong&gt;: O(1)이 O(n)보다 일반적으로 더 효율적입니다.&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;O(1): 입력 크기와 무관&lt;/li&gt;
&lt;li&gt;O(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;O(1): 항상 일정한 시간&lt;/li&gt;
&lt;li&gt;O(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;O(1): 직접 접근이 가능한 연산에 주로 사용&lt;/li&gt;
&lt;li&gt;O(n): 전체 데이터를 순회해야 하는 연산에 주로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;알고리즘을 설계할 때는 가능한 O(1)의 시간 복잡도를 갖도록 하는 것이 좋지만, 문제의 특성에 따라 O(n)이 불가피한 경우도 많습니다. 상황에 따라 적절한 시간 복잡도를 선택하는 것이 중요합니다.&lt;/p&gt;
&lt;h2 id=&quot;o-1-&quot;&gt;O(1) 시간 복잡도의 추가 예시&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;스택(Stack)의 push와 pop 연산:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;Stack&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-built_in&quot;&gt;Integer&lt;/span&gt;&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;stack&lt;/span&gt; = &lt;span class=&quot;hljs-literal&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;Stack&lt;/span&gt;&amp;lt;&amp;gt;();
&lt;span class=&quot;hljs-built_in&quot;&gt;stack&lt;/span&gt;.push(&lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
int top = &lt;span class=&quot;hljs-built_in&quot;&gt;stack&lt;/span&gt;.pop(); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;해시 테이블(Hash Table)의 삽입, 검색, 삭제 연산:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;HashMap&amp;lt;&lt;span class=&quot;hljs-keyword&quot;&gt;String&lt;/span&gt;, Integer&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;map&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; HashMap&amp;lt;&amp;gt;();
&lt;span class=&quot;hljs-built_in&quot;&gt;map&lt;/span&gt;.&lt;span class=&quot;hljs-built_in&quot;&gt;put&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;key&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;int&lt;/span&gt; value = &lt;span class=&quot;hljs-built_in&quot;&gt;map&lt;/span&gt;.&lt;span class=&quot;hljs-built_in&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;key&quot;&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;map&lt;/span&gt;.&lt;span class=&quot;hljs-built_in&quot;&gt;remove&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;key&quot;&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;링크드 리스트(Linked List)의 맨 앞에 요소 추가/삭제:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;LinkedList&amp;lt;Integer&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;list&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; LinkedList&amp;lt;&amp;gt;();
&lt;span class=&quot;hljs-built_in&quot;&gt;list&lt;/span&gt;.addFirst(&lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;int&lt;/span&gt; first = &lt;span class=&quot;hljs-built_in&quot;&gt;list&lt;/span&gt;.removeFirst(); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;큐(Queue)의 enqueue와 dequeue 연산:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;Queue&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-built_in&quot;&gt;Integer&lt;/span&gt;&amp;gt; &lt;span class=&quot;hljs-built_in&quot;&gt;queue&lt;/span&gt; = &lt;span class=&quot;hljs-literal&quot;&gt;new&lt;/span&gt; LinkedList&amp;lt;&amp;gt;();
&lt;span class=&quot;hljs-built_in&quot;&gt;queue&lt;/span&gt;.offer(&lt;span class=&quot;hljs-number&quot;&gt;10&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
int front = &lt;span class=&quot;hljs-built_in&quot;&gt;queue&lt;/span&gt;.poll(); &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&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;lang-java&quot;&gt;int[]&lt;span class=&quot;hljs-built_in&quot;&gt; array &lt;/span&gt;= {1, 2, 3, 4, 5};&lt;span class=&quot;hljs-built_in&quot;&gt;
int &lt;/span&gt;length = array.length; // O(1)
&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;lang-java&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;int&lt;/span&gt; a = &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;, b = &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;;
&lt;span class=&quot;hljs-keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;sum&lt;/span&gt; = a + b; &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;int&lt;/span&gt; product = a * b; &lt;span class=&quot;hljs-comment&quot;&gt;// O(1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 연산들은 입력의 크기와 관계없이 항상 일정한 시간에 수행되므로 O(1) 시간 복잡도를 가집니다. 이는 알고리즘이나 데이터 구조를 설계할 때 매우 효율적인 특성으로 간주됩니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>알고리즘</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/416</guid>
      <comments>https://syaku.tistory.com/416#entry416comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:58:11 +0900</pubDate>
    </item>
    <item>
      <title>Spring REST Docs Easy: Efficient API Documentation Tool (spring-restdocs-easy)</title>
      <link>https://syaku.tistory.com/415</link>
      <description>&lt;h1 id=&quot;spring-rest-docs-easy-efficient-api-documentation-tool&quot;&gt;Spring REST Docs Easy: Efficient API Documentation Tool&lt;/h1&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Spring REST Docs Easy extends Spring REST Docs to simplify API documentation. It combines test-driven documentation with internationalization support to create accurate and easily manageable API documentation.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/syakuis/spring-restdocs-easy&quot;&gt;https://github.com/syakuis/spring-restdocs-easy&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;
&lt;h3 id=&quot;gradle&quot;&gt;Gradle&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-gradle&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;dependencies&lt;/span&gt; {
    &lt;span class=&quot;hljs-attribute&quot;&gt;testImplementation&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'io.github.syakuis:spring-restdocs-easy:1.0.0'&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;practical-usage-examples&quot;&gt;Practical Usage Examples&lt;/h2&gt;
&lt;h3 id=&quot;1-basic-configuration&quot;&gt;1. Basic Configuration&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@WebMvcTest&lt;/span&gt;(MemberRestController.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;@AutoConfigureMvcRestDocs&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MemberRestControllerTest&lt;/span&gt; {&lt;/span&gt;
    &lt;span class=&quot;hljs-meta&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; MockMvc mockMvc;

    &lt;span class=&quot;hljs-meta&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; RestDocs restDocs;

    &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; String RESTDOCS_PATH = &lt;span class=&quot;hljs-string&quot;&gt;&quot;members/{method-name}&quot;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-documenting-get-request-list-retrieval-&quot;&gt;2. Documenting GET Request (List Retrieval)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;@&lt;span class=&quot;hljs-keyword&quot;&gt;Test&lt;/span&gt;
void list() throws Exception {
    mockMvc.perform(get(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/members&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;stela&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;age&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;10&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;job&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;ENGINEER&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;email&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;email@email.com&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .accept&lt;/span&gt;(MediaType.APPLICATION_JSON))
&lt;span class=&quot;hljs-meta&quot;&gt;        .andExpect&lt;/span&gt;(status().isOk())
&lt;span class=&quot;hljs-meta&quot;&gt;        .andDo&lt;/span&gt;(document(RESTDOCS_PATH,
            // Document request/response headers
            restDocs.headers()
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
&lt;span class=&quot;hljs-meta&quot;&gt;                .requestHeaders&lt;/span&gt;(),
            restDocs.headers()
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
&lt;span class=&quot;hljs-meta&quot;&gt;                .responseHeaders&lt;/span&gt;(),

            // Document query parameters
            restDocs.params()
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name parameter&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;age&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;{io.github.syakuis.spring.restdocs.easy.examples.adapter.web.controller.model.MemberRequest.age}&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;job&quot;&lt;/span&gt;, restDocs.generate(Job.class).join())
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;email&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;{email}&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;                .queryParameters&lt;/span&gt;(),

            // Document response fields (array response)
            restDocs.generate(&lt;span class=&quot;hljs-string&quot;&gt;&quot;[].&quot;&lt;/span&gt;, MemberResponse.class)
&lt;span class=&quot;hljs-meta&quot;&gt;                .addAll&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;[].locationAddress.&quot;&lt;/span&gt;, LocationAddress.class)
&lt;span class=&quot;hljs-meta&quot;&gt;                .responseFields&lt;/span&gt;()
        ))&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-documenting-post-request-registration-&quot;&gt;3. Documenting POST Request (Registration)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; register() throws Exception {
    MemberRequest request = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; MemberRequest(&lt;span class=&quot;hljs-string&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;30&lt;/span&gt;, Job.ACCOUNTANT, 
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;List&lt;/span&gt;.of(&lt;span class=&quot;hljs-string&quot;&gt;&quot;tag1&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;tag2&quot;&lt;/span&gt;), 
        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; LocationAddress(&lt;span class=&quot;hljs-string&quot;&gt;&quot;123 Main St&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;City&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;Country&quot;&lt;/span&gt;));

    mockMvc.perform(post(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/members&quot;&lt;/span&gt;)
            .contentType(MediaType.APPLICATION_JSON)
            .header(HttpHeaders.AUTHORIZATION, &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk())
        .andDo(&lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;(RESTDOCS_PATH,
            &lt;span class=&quot;hljs-comment&quot;&gt;// Document request headers&lt;/span&gt;
            restDocs.headers()
                .add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
                .add(HttpHeaders.AUTHORIZATION, &lt;span class=&quot;hljs-string&quot;&gt;&quot;{http.headers.authorization.BASIC_AUTHORIZATION}&quot;&lt;/span&gt;)
                .requestHeaders(),

            &lt;span class=&quot;hljs-comment&quot;&gt;// Document request body fields&lt;/span&gt;
            restDocs.generate(MemberRequest.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
                .requestFields()
                .andWithPrefix(&lt;span class=&quot;hljs-string&quot;&gt;&quot;locationAddress.&quot;&lt;/span&gt;, restDocs.generate(LocationAddress.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;).toField()),

            &lt;span class=&quot;hljs-comment&quot;&gt;// Document response body fields&lt;/span&gt;
            restDocs.generate(MemberResponse.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
                .responseFields()
                .andWithPrefix(&lt;span class=&quot;hljs-string&quot;&gt;&quot;locationAddress.&quot;&lt;/span&gt;, restDocs.generate(LocationAddress.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;).toField())
        ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-documenting-single-item-retrieval-with-path-variable-&quot;&gt;4. Documenting Single Item Retrieval (with Path Variable)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;@&lt;span class=&quot;hljs-keyword&quot;&gt;Test&lt;/span&gt;
void &lt;span class=&quot;hljs-keyword&quot;&gt;view&lt;/span&gt;() throws Exception {
    mockMvc.perform(&lt;span class=&quot;hljs-built_in&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/members/{id}&quot;&lt;/span&gt;, 953))
        .andExpect(status().isOk())
        .andDo(document(RESTDOCS_PATH,
            &lt;span class=&quot;hljs-comment&quot;&gt;// Document path variables&lt;/span&gt;
            restDocs.params().add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;).pathParameters(),

            &lt;span class=&quot;hljs-comment&quot;&gt;// Document response fields&lt;/span&gt;
            restDocs.&lt;span class=&quot;hljs-keyword&quot;&gt;generate&lt;/span&gt;(MemberResponse.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
                .responseFields()
                .andWithPrefix(&lt;span class=&quot;hljs-string&quot;&gt;&quot;locationAddress.&quot;&lt;/span&gt;, 
                    restDocs.&lt;span class=&quot;hljs-keyword&quot;&gt;generate&lt;/span&gt;(LocationAddress.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;).toField())
        ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;key-features&quot;&gt;Key Features&lt;/h2&gt;
&lt;h3 id=&quot;1-comprehensive-documentation-support&quot;&gt;1. Comprehensive Documentation Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Request/Response headers (&lt;code&gt;headers()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Query parameters (&lt;code&gt;params()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Path variables (&lt;code&gt;pathParameters()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Request/Response fields (&lt;code&gt;requestFields()&lt;/code&gt;, &lt;code&gt;responseFields()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Nested objects (&lt;code&gt;andWithPrefix()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Array responses (&lt;code&gt;[]&lt;/code&gt; notation)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-message-source-utilization&quot;&gt;2. Message Source Utilization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Direct string descriptions supported&lt;/li&gt;
&lt;li&gt;Internationalization through message keys (&lt;code&gt;{key}&lt;/code&gt; format)&lt;/li&gt;
&lt;li&gt;Automatic field description references from classes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-nested-structure-handling&quot;&gt;3. Nested Structure Handling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;addAll()&lt;/code&gt;: Add nested object fields&lt;/li&gt;
&lt;li&gt;&lt;code&gt;andWithPrefix()&lt;/code&gt;: Specify nested field paths&lt;/li&gt;
&lt;li&gt;Array notation(&lt;code&gt;[].&lt;/code&gt;) for collection handling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;advantages&quot;&gt;Advantages&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Minimizes code duplication&lt;/li&gt;
&lt;li&gt;Intuitive API&lt;/li&gt;
&lt;li&gt;Flexible documentation options&lt;/li&gt;
&lt;li&gt;Maintains Spring REST Docs benefits&lt;/li&gt;
&lt;li&gt;Internationalization support&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Spring REST Docs Easy extends Spring REST Docs while providing an easy-to-use API. It significantly simplifies documentation tasks, especially for complex APIs with nested object structures or array responses.&lt;/p&gt;</description>
      <category>개발</category>
      <category>Spring</category>
      <category>spring-restdocs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/415</guid>
      <comments>https://syaku.tistory.com/415#entry415comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:57:19 +0900</pubDate>
    </item>
    <item>
      <title>Spring REST Docs Easy: 효율적인 API 문서화 도구 (spring-restdocs-easy)</title>
      <link>https://syaku.tistory.com/414</link>
      <description>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 id=&quot;spring-rest-docs-easy-api-&quot;&gt;Spring REST Docs Easy: 효율적인 API 문서화 도구&lt;/h1&gt;
&lt;h2 id=&quot;-&quot;&gt;소개&lt;/h2&gt;
&lt;p&gt;Spring REST Docs Easy는 Spring REST Docs를 확장하여 API 문서화를 더 쉽게 만드는 라이브러리입니다. 테스트 기반 문서화와 국제화 지원을 결합하여 정확하고 관리하기 쉬운 API 문서를 생성할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/syakuis/spring-restdocs-easy&quot;&gt;https://github.com/syakuis/spring-restdocs-easy&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;-&quot;&gt;설치 방법&lt;/h2&gt;
&lt;h3 id=&quot;gradle&quot;&gt;Gradle&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-gradle&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;dependencies&lt;/span&gt; {
    &lt;span class=&quot;hljs-attribute&quot;&gt;testImplementation&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'io.github.syakuis:spring-restdocs-easy:1.0.0'&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;-&quot;&gt;실제 사용 예제&lt;/h2&gt;
&lt;h3 id=&quot;1-&quot;&gt;1. 기본 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@WebMvcTest&lt;/span&gt;(MemberRestController.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;@AutoConfigureMvcRestDocs&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MemberRestControllerTest&lt;/span&gt; {&lt;/span&gt;
    &lt;span class=&quot;hljs-meta&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; MockMvc mockMvc;

    &lt;span class=&quot;hljs-meta&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; RestDocs restDocs;

    &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;final&lt;/span&gt; String RESTDOCS_PATH = &lt;span class=&quot;hljs-string&quot;&gt;&quot;members/{method-name}&quot;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-get-&quot;&gt;2. GET 요청 문서화 (목록 조회)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;@&lt;span class=&quot;hljs-keyword&quot;&gt;Test&lt;/span&gt;
void list() throws Exception {
    mockMvc.perform(get(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/members&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;stela&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;age&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;10&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;job&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;ENGINEER&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .param&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;email&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;email@email.com&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;            .accept&lt;/span&gt;(MediaType.APPLICATION_JSON))
&lt;span class=&quot;hljs-meta&quot;&gt;        .andExpect&lt;/span&gt;(status().isOk())
&lt;span class=&quot;hljs-meta&quot;&gt;        .andDo&lt;/span&gt;(document(RESTDOCS_PATH,
            // 요청/응답 헤더 문서화
            restDocs.headers()
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
&lt;span class=&quot;hljs-meta&quot;&gt;                .requestHeaders&lt;/span&gt;(),
            restDocs.headers()
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
&lt;span class=&quot;hljs-meta&quot;&gt;                .responseHeaders&lt;/span&gt;(),

            // 쿼리 파라미터 문서화
            restDocs.params()
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;name parameter&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;age&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;{io.github.syakuis.spring.restdocs.easy.examples.adapter.web.controller.model.MemberRequest.age}&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;job&quot;&lt;/span&gt;, restDocs.generate(Job.class).join())
&lt;span class=&quot;hljs-meta&quot;&gt;                .add&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;email&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;{email}&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-meta&quot;&gt;                .queryParameters&lt;/span&gt;(),

            // 응답 필드 문서화 (배열 응답)
            restDocs.generate(&lt;span class=&quot;hljs-string&quot;&gt;&quot;[].&quot;&lt;/span&gt;, MemberResponse.class)
&lt;span class=&quot;hljs-meta&quot;&gt;                .addAll&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;[].locationAddress.&quot;&lt;/span&gt;, LocationAddress.class)
&lt;span class=&quot;hljs-meta&quot;&gt;                .responseFields&lt;/span&gt;()
        ))&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-post-&quot;&gt;3. POST 요청 문서화 (등록)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; register() throws Exception {
    MemberRequest request = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; MemberRequest(&lt;span class=&quot;hljs-string&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;30&lt;/span&gt;, Job.ACCOUNTANT, 
        &lt;span class=&quot;hljs-string&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;, &lt;span class=&quot;hljs-built_in&quot;&gt;List&lt;/span&gt;.of(&lt;span class=&quot;hljs-string&quot;&gt;&quot;tag1&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;tag2&quot;&lt;/span&gt;), 
        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; LocationAddress(&lt;span class=&quot;hljs-string&quot;&gt;&quot;123 Main St&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;City&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;Country&quot;&lt;/span&gt;));

    mockMvc.perform(post(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/members&quot;&lt;/span&gt;)
            .contentType(MediaType.APPLICATION_JSON)
            .header(HttpHeaders.AUTHORIZATION, &lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk())
        .andDo(&lt;span class=&quot;hljs-built_in&quot;&gt;document&lt;/span&gt;(RESTDOCS_PATH,
            &lt;span class=&quot;hljs-comment&quot;&gt;// 요청 헤더 문서화&lt;/span&gt;
            restDocs.headers()
                .add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
                .add(HttpHeaders.AUTHORIZATION, &lt;span class=&quot;hljs-string&quot;&gt;&quot;{http.headers.authorization.BASIC_AUTHORIZATION}&quot;&lt;/span&gt;)
                .requestHeaders(),

            &lt;span class=&quot;hljs-comment&quot;&gt;// 요청 본문 필드 문서화&lt;/span&gt;
            restDocs.generate(MemberRequest.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
                .requestFields()
                .andWithPrefix(&lt;span class=&quot;hljs-string&quot;&gt;&quot;locationAddress.&quot;&lt;/span&gt;, restDocs.generate(LocationAddress.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;).toField()),

            &lt;span class=&quot;hljs-comment&quot;&gt;// 응답 본문 필드 문서화&lt;/span&gt;
            restDocs.generate(MemberResponse.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
                .responseFields()
                .andWithPrefix(&lt;span class=&quot;hljs-string&quot;&gt;&quot;locationAddress.&quot;&lt;/span&gt;, restDocs.generate(LocationAddress.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;).toField())
        ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-&quot;&gt;4. 단일 조회 문서화 (경로 변수 포함)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-java&quot;&gt;@&lt;span class=&quot;hljs-keyword&quot;&gt;Test&lt;/span&gt;
void &lt;span class=&quot;hljs-keyword&quot;&gt;view&lt;/span&gt;() throws Exception {
    mockMvc.perform(&lt;span class=&quot;hljs-built_in&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;/members/{id}&quot;&lt;/span&gt;, 953))
        .andExpect(status().isOk())
        .andDo(document(RESTDOCS_PATH,
            &lt;span class=&quot;hljs-comment&quot;&gt;// 경로 변수 문서화&lt;/span&gt;
            restDocs.params().add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;id&quot;&lt;/span&gt;).pathParameters(),

            &lt;span class=&quot;hljs-comment&quot;&gt;// 응답 필드 문서화&lt;/span&gt;
            restDocs.&lt;span class=&quot;hljs-keyword&quot;&gt;generate&lt;/span&gt;(MemberResponse.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;)
                .responseFields()
                .andWithPrefix(&lt;span class=&quot;hljs-string&quot;&gt;&quot;locationAddress.&quot;&lt;/span&gt;, 
                    restDocs.&lt;span class=&quot;hljs-keyword&quot;&gt;generate&lt;/span&gt;(LocationAddress.&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;).toField())
        ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;-&quot;&gt;주요 기능 설명&lt;/h2&gt;
&lt;h3 id=&quot;1-&quot;&gt;1. 다양한 문서화 지원&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요청/응답 헤더 (&lt;code&gt;headers()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;쿼리 파라미터 (&lt;code&gt;params()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;경로 변수 (&lt;code&gt;pathParameters()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;요청/응답 필드 (&lt;code&gt;requestFields()&lt;/code&gt;, &lt;code&gt;responseFields()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;중첩 객체 (&lt;code&gt;andWithPrefix()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;배열 응답 (&lt;code&gt;[]&lt;/code&gt; 표기법)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-&quot;&gt;2. 메시지 소스 활용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;직접 문자열 설명 사용 가능&lt;/li&gt;
&lt;li&gt;메시지 키를 통한 국제화 지원 (&lt;code&gt;{key}&lt;/code&gt; 형식)&lt;/li&gt;
&lt;li&gt;클래스의 필드 설명 자동 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-&quot;&gt;3. 중첩 구조 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;addAll()&lt;/code&gt;: 중첩 객체의 필드 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;andWithPrefix()&lt;/code&gt;: 중첩 필드 경로 지정&lt;/li&gt;
&lt;li&gt;배열 표기법(&lt;code&gt;[].&lt;/code&gt;)을 통한 컬렉션 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;-&quot;&gt;장점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;코드 중복 최소화&lt;/li&gt;
&lt;li&gt;직관적인 API&lt;/li&gt;
&lt;li&gt;유연한 문서화 옵션&lt;/li&gt;
&lt;li&gt;Spring REST Docs의 장점 유지&lt;/li&gt;
&lt;li&gt;국제화 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;-&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;Spring REST Docs Easy는 Spring REST Docs의 기능을 확장하면서도 사용하기 쉬운 API를 제공합니다. 특히 중첩된 객체 구조나 배열 응답의 문서화를 간단하게 처리할 수 있어, 복잡한 API의 문서화 작업을 크게 단순화합니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>Spring</category>
      <category>spring-restdocs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/414</guid>
      <comments>https://syaku.tistory.com/414#entry414comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:56:43 +0900</pubDate>
    </item>
    <item>
      <title>2024 Comprehensive Guide to AI Coding Tools: Comparing 8 Tools from GitHub Copilot to Cursor AI</title>
      <link>https://syaku.tistory.com/413</link>
      <description>&lt;h2 id=&quot;1-github-copilot&quot;&gt;1. GitHub Copilot&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://github.com/features/copilot&quot;&gt;https://github.com/features/copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI code auto-completion tool based on OpenAI&amp;#39;s Codex model&lt;/li&gt;
&lt;li&gt;Supported IDEs: Visual Studio Code, Visual Studio, JetBrains IDEs, Neovim, etc.&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Real-time code suggestions and auto-completion&lt;/li&gt;
&lt;li&gt;Code generation based on natural language comments&lt;/li&gt;
&lt;li&gt;Code refactoring, documentation comment generation, unit test creation&lt;/li&gt;
&lt;li&gt;Code error detection and correction suggestions&lt;/li&gt;
&lt;li&gt;Code translation between programming languages (GitHub Copilot Labs)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing:&lt;ul&gt;
&lt;li&gt;Individual: $10/month or $100/year&lt;/li&gt;
&lt;li&gt;Copilot Business: $19/user/month&lt;/li&gt;
&lt;li&gt;Copilot Enterprise: $39/user/month&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href=&quot;https://www.youtube.com/watch?v=q0PorpN6SQM&quot;&gt;https://www.youtube.com/watch?v=q0PorpN6SQM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-amazon-codewhisperer&quot;&gt;2. Amazon CodeWhisperer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://aws.amazon.com/codewhisperer/&quot;&gt;https://aws.amazon.com/codewhisperer/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI coding assistant developed by AWS&lt;/li&gt;
&lt;li&gt;Supported IDEs: VS Code, IntelliJ IDEA, PyCharm, WebStorm, AWS Cloud9&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Real-time code auto-completion and suggestions&lt;/li&gt;
&lt;li&gt;Function generation based on natural language prompts&lt;/li&gt;
&lt;li&gt;Code security vulnerability scanning&lt;/li&gt;
&lt;li&gt;Open-source code tracking and license information provision&lt;/li&gt;
&lt;li&gt;Code translation between programming languages&lt;/li&gt;
&lt;li&gt;Automatic generation of code explanation comments&lt;/li&gt;
&lt;li&gt;AI chatbot functionality&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing:&lt;ul&gt;
&lt;li&gt;Individual users: Free&lt;/li&gt;
&lt;li&gt;Professional: $19/user/month&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href=&quot;https://www.youtube.com/watch?v=mrWAaLKxJsE&quot;&gt;https://www.youtube.com/watch?v=mrWAaLKxJsE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-tabnine&quot;&gt;3. Tabnine&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://www.tabnine.com/&quot;&gt;https://www.tabnine.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI-based code auto-completion and prediction tool&lt;/li&gt;
&lt;li&gt;Supported IDEs: VS Code, IntelliJ IDEA, PyCharm, WebStorm, and many others&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Personalized code suggestions&lt;/li&gt;
&lt;li&gt;Function completion capability&lt;/li&gt;
&lt;li&gt;Conversion of natural language prompts to code blocks&lt;/li&gt;
&lt;li&gt;Offline mode and private model support&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing:&lt;ul&gt;
&lt;li&gt;Free: Basic AI code generation features&lt;/li&gt;
&lt;li&gt;Pro: $12/user/month&lt;/li&gt;
&lt;li&gt;Enterprise: $39/user/month (with 1-year commitment)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href=&quot;https://www.youtube.com/watch?v=JbH9usJzVIA&quot;&gt;https://www.youtube.com/watch?v=JbH9usJzVIA&lt;/a&gt; (Comparison of GitHub Copilot, Tabnine, and Cursor AI)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-cody-by-sourcegraph&quot;&gt;4. Cody by Sourcegraph&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://sourcegraph.com/cody&quot;&gt;https://sourcegraph.com/cody&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI-based code understanding and generation tool&lt;/li&gt;
&lt;li&gt;Supported IDEs: VSCode, JetBrains IDEs (IntelliJ, PyCharm, etc.)&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Question-answering capability for entire codebases&lt;/li&gt;
&lt;li&gt;Code generation and editing using natural language&lt;/li&gt;
&lt;li&gt;Bug fixing and code improvement suggestions&lt;/li&gt;
&lt;li&gt;Code explanation and documentation support&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing: Free for individual users and open-source projects&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href=&quot;https://www.youtube.com/watch?v=sa4n65hzrsc&quot;&gt;https://www.youtube.com/watch?v=sa4n65hzrsc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-mintlify&quot;&gt;5. Mintlify&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://www.mintlify.com/&quot;&gt;https://www.mintlify.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI-based automatic code documentation tool&lt;/li&gt;
&lt;li&gt;Supported IDEs: VSCode, JetBrains IDEs&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Automatic document generation through code analysis&lt;/li&gt;
&lt;li&gt;Improvement and updating of existing documentation&lt;/li&gt;
&lt;li&gt;Support for various document formats (Markdown, JSDoc, OpenAPI, etc.)&lt;/li&gt;
&lt;li&gt;Document sharing and version control for team collaboration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing:&lt;ul&gt;
&lt;li&gt;Free: Basic features for individual users&lt;/li&gt;
&lt;li&gt;Startup: $120/user/month (advanced features for small teams)&lt;/li&gt;
&lt;li&gt;Enterprise: Custom pricing (for large teams and corporations)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href=&quot;https://www.youtube.com/watch?v=zmoveFAiGPU&quot;&gt;https://www.youtube.com/watch?v=zmoveFAiGPU&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-what-the-diff&quot;&gt;6. What The Diff&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://whatthediff.ai/&quot;&gt;https://whatthediff.ai/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI-based code review and quality improvement tool&lt;/li&gt;
&lt;li&gt;Supported platforms: Integrates with GitHub, GitLab, Bitbucket&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Automatic code review and improvement suggestions&lt;/li&gt;
&lt;li&gt;Security vulnerability detection&lt;/li&gt;
&lt;li&gt;Code style and consistency checking&lt;/li&gt;
&lt;li&gt;Performance optimization suggestions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing:&lt;ul&gt;
&lt;li&gt;Free Plan: Free trial (limited features)&lt;/li&gt;
&lt;li&gt;Pro Plan: Tiered pricing based on analyzed code volume&lt;ul&gt;
&lt;li&gt;Up to 200k tokens: $19/month&lt;/li&gt;
&lt;li&gt;Up to 500k tokens: $49/month&lt;/li&gt;
&lt;li&gt;Up to 1.5M tokens: $99/month&lt;/li&gt;
&lt;li&gt;Unlimited tokens: $199/month&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-replit-ghostwriter&quot;&gt;7. Replit Ghostwriter&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://replit.com/site/ghostwriter&quot;&gt;https://replit.com/site/ghostwriter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Replit&amp;#39;s AI-based code writing and completion tool&lt;/li&gt;
&lt;li&gt;Fully integrated with Replit&amp;#39;s online IDE&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Real-time code auto-completion&lt;/li&gt;
&lt;li&gt;Code generation through natural language prompts&lt;/li&gt;
&lt;li&gt;Code explanation and documentation support&lt;/li&gt;
&lt;li&gt;Bug fixing and code optimization suggestions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing: Available for an additional $10/month on top of the Replit plan ($20/month), totaling $30/month&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href=&quot;https://www.youtube.com/watch?v=N6-eFp-3qs4&quot;&gt;https://www.youtube.com/watch?v=N6-eFp-3qs4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8-cursor-ai&quot;&gt;8. Cursor AI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Official website: &lt;a href=&quot;https://www.cursor.com/&quot;&gt;https://www.cursor.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI-based code editor integrating advanced AI models like GPT-4 and Claude 3.5 Sonnet&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;AI-based code auto-completion&lt;/li&gt;
&lt;li&gt;Code generation and editing&lt;/li&gt;
&lt;li&gt;Debugging support&lt;/li&gt;
&lt;li&gt;Real-time code explanation&lt;/li&gt;
&lt;li&gt;Code modification using natural language&lt;/li&gt;
&lt;li&gt;Codebase understanding and analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pricing:&lt;ul&gt;
&lt;li&gt;Hobby (Free):&lt;ul&gt;
&lt;li&gt;2000 code completions&lt;/li&gt;
&lt;li&gt;50 slow premium requests&lt;/li&gt;
&lt;li&gt;2-week Pro trial&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pro ($20/month):&lt;ul&gt;
&lt;li&gt;Unlimited code completions&lt;/li&gt;
&lt;li&gt;500 fast premium requests per month&lt;/li&gt;
&lt;li&gt;Unlimited slow premium requests&lt;/li&gt;
&lt;li&gt;10 Claude Opus uses per day&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Business ($40/user/month):&lt;ul&gt;
&lt;li&gt;All Pro plan features&lt;/li&gt;
&lt;li&gt;Centralized billing&lt;/li&gt;
&lt;li&gt;Admin usage dashboard&lt;/li&gt;
&lt;li&gt;Privacy mode&lt;/li&gt;
&lt;li&gt;Zero data retention&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Features:&lt;ul&gt;
&lt;li&gt;Easy adaptation with VSCode-like interface&lt;/li&gt;
&lt;li&gt;Support for existing extensions, themes, and key bindings&lt;/li&gt;
&lt;li&gt;Security verified with SOC 2 certification&lt;/li&gt;
&lt;li&gt;Use of user-specific API keys&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demo videos:&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zTVNxdHI2H8&quot;&gt;https://www.youtube.com/watch?v=zTVNxdHI2H8&lt;/a&gt; (Code Like a PRO with Cursor AI)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fjrKz4KAJQ0&quot;&gt;https://www.youtube.com/watch?v=fjrKz4KAJQ0&lt;/a&gt; (How to use Cursor AI)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/413</guid>
      <comments>https://syaku.tistory.com/413#entry413comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:55:51 +0900</pubDate>
    </item>
    <item>
      <title>2024년 최신 AI 코딩 도구 완벽 가이드: GitHub Copilot부터 Cursor AI까지 8가지 비교 분석</title>
      <link>https://syaku.tistory.com/412</link>
      <description>&lt;h1 id=&quot;ai-&quot;&gt;AI 기반 코드 작성 도구 총정리&lt;/h1&gt;
&lt;h2 id=&quot;1-github-copilot&quot;&gt;1. GitHub Copilot&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://github.com/features/copilot&quot;&gt;https://github.com/features/copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenAI의 Codex 모델 기반 AI 코드 자동완성 도구&lt;/li&gt;
&lt;li&gt;지원 IDE: Visual Studio Code, Visual Studio, JetBrains IDEs, Neovim 등&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;실시간 코드 제안 및 자동 완성&lt;/li&gt;
&lt;li&gt;자연어 주석 기반 코드 생성&lt;/li&gt;
&lt;li&gt;코드 리팩토링, 문서 주석 생성, 단위 테스트 생성&lt;/li&gt;
&lt;li&gt;코드 오류 감지 및 수정 제안&lt;/li&gt;
&lt;li&gt;프로그래밍 언어 간 코드 번역 (GitHub Copilot Labs)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격:&lt;ul&gt;
&lt;li&gt;개인용: $10/월 또는 $100/년&lt;/li&gt;
&lt;li&gt;Copilot Business: $19/사용자/월&lt;/li&gt;
&lt;li&gt;Copilot Enterprise: $39/사용자/월&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;a href=&quot;https://www.youtube.com/watch?v=q0PorpN6SQM&quot;&gt;https://www.youtube.com/watch?v=q0PorpN6SQM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-amazon-codewhisperer&quot;&gt;2. Amazon CodeWhisperer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://aws.amazon.com/codewhisperer/&quot;&gt;https://aws.amazon.com/codewhisperer/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AWS에서 개발한 AI 코딩 도우미&lt;/li&gt;
&lt;li&gt;지원 IDE: VS Code, IntelliJ IDEA, PyCharm, WebStorm, AWS Cloud9&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;실시간 코드 자동 완성 및 제안&lt;/li&gt;
&lt;li&gt;자연어 프롬프트 기반 함수 생성&lt;/li&gt;
&lt;li&gt;코드 보안 취약점 검사&lt;/li&gt;
&lt;li&gt;오픈 소스 코드 추적 및 라이선스 정보 제공&lt;/li&gt;
&lt;li&gt;프로그래밍 언어 간 코드 번역&lt;/li&gt;
&lt;li&gt;코드 설명 주석 자동 생성&lt;/li&gt;
&lt;li&gt;AI 챗봇 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격:&lt;ul&gt;
&lt;li&gt;개인 사용자: 무료&lt;/li&gt;
&lt;li&gt;전문가용: $19/사용자/월&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;a href=&quot;https://www.youtube.com/watch?v=mrWAaLKxJsE&quot;&gt;https://www.youtube.com/watch?v=mrWAaLKxJsE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-tabnine&quot;&gt;3. Tabnine&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://www.tabnine.com/&quot;&gt;https://www.tabnine.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI 기반 코드 자동완성 및 예측 도구&lt;/li&gt;
&lt;li&gt;지원 IDE: VS Code, IntelliJ IDEA, PyCharm, WebStorm 등 다수&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;개인화된 코드 제안&lt;/li&gt;
&lt;li&gt;함수 완성 기능&lt;/li&gt;
&lt;li&gt;자연어 프롬프트를 코드 블록으로 변환&lt;/li&gt;
&lt;li&gt;오프라인 모드 및 프라이빗 모델 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격:&lt;ul&gt;
&lt;li&gt;Free: 무료 (기본 AI 코드 생성 기능)&lt;/li&gt;
&lt;li&gt;Pro: $12/사용자/월&lt;/li&gt;
&lt;li&gt;Enterprise: $39/사용자/월 (1년 약정 시)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;a href=&quot;https://www.youtube.com/watch?v=JbH9usJzVIA&quot;&gt;https://www.youtube.com/watch?v=JbH9usJzVIA&lt;/a&gt; (GitHub Copilot, Tabnine, Cursor AI 비교 영상)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-cody-by-sourcegraph&quot;&gt;4. Cody by Sourcegraph&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://sourcegraph.com/cody&quot;&gt;https://sourcegraph.com/cody&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI 기반 코드 이해 및 생성 도구&lt;/li&gt;
&lt;li&gt;지원 IDE: VSCode, JetBrains IDEs (IntelliJ, PyCharm 등)&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;코드베이스 전체에 대한 질문-답변 기능&lt;/li&gt;
&lt;li&gt;자연어로 코드 생성 및 편집&lt;/li&gt;
&lt;li&gt;버그 수정 및 코드 개선 제안&lt;/li&gt;
&lt;li&gt;코드 설명 및 문서화 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격: 개인 사용자 및 오픈소스 프로젝트에 무료&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;a href=&quot;https://www.youtube.com/watch?v=sa4n65hzrsc&quot;&gt;https://www.youtube.com/watch?v=sa4n65hzrsc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-mintlify&quot;&gt;5. Mintlify&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://www.mintlify.com/&quot;&gt;https://www.mintlify.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI 기반 자동 코드 문서화 도구&lt;/li&gt;
&lt;li&gt;지원 IDE: VSCode, JetBrains IDEs&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;코드 분석을 통한 자동 문서 생성&lt;/li&gt;
&lt;li&gt;기존 문서 개선 및 업데이트&lt;/li&gt;
&lt;li&gt;다양한 문서 형식 지원 (Markdown, JSDoc, OpenAPI 등)&lt;/li&gt;
&lt;li&gt;팀 협업을 위한 문서 공유 및 버전 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격:&lt;ul&gt;
&lt;li&gt;Free: 무료 (기본 기능, 개인 사용자용)&lt;/li&gt;
&lt;li&gt;Startup: $120/사용자/월 (고급 기능, 소규모 팀용)&lt;/li&gt;
&lt;li&gt;Enterprise: 맞춤 가격 (대규모 팀 및 기업용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;a href=&quot;https://www.youtube.com/watch?v=zmoveFAiGPU&quot;&gt;https://www.youtube.com/watch?v=zmoveFAiGPU&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-what-the-diff&quot;&gt;6. What The Diff&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://whatthediff.ai/&quot;&gt;https://whatthediff.ai/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI 기반 코드 리뷰 및 품질 개선 도구&lt;/li&gt;
&lt;li&gt;지원 플랫폼: GitHub, GitLab, Bitbucket과 통합&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;자동 코드 리뷰 및 개선 제안&lt;/li&gt;
&lt;li&gt;보안 취약점 탐지&lt;/li&gt;
&lt;li&gt;코드 스타일 및 일관성 검사&lt;/li&gt;
&lt;li&gt;성능 최적화 제안&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격:&lt;ul&gt;
&lt;li&gt;Free Plan: 무료 체험 (제한된 기능)&lt;/li&gt;
&lt;li&gt;Pro Plan: 분석 코드량에 따라 차등 가격&lt;ul&gt;
&lt;li&gt;200k 토큰까지 $19/월&lt;/li&gt;
&lt;li&gt;500k 토큰까지 $49/월&lt;/li&gt;
&lt;li&gt;1.5M 토큰까지 $99/월&lt;/li&gt;
&lt;li&gt;무제한 토큰 $199/월&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-replit-ghostwriter&quot;&gt;7. Replit Ghostwriter&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://replit.com/site/ghostwriter&quot;&gt;https://replit.com/site/ghostwriter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Replit의 AI 기반 코드 작성 및 완성 도구&lt;/li&gt;
&lt;li&gt;Replit의 온라인 IDE와 완벽히 통합&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;실시간 코드 자동 완성&lt;/li&gt;
&lt;li&gt;자연어 프롬프트를 통한 코드 생성&lt;/li&gt;
&lt;li&gt;코드 설명 및 문서화 지원&lt;/li&gt;
&lt;li&gt;버그 수정 및 코드 최적화 제안&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격: Replit 플랜($20/월)에 $10/월 추가로 이용 가능, 총 $30/월&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;a href=&quot;https://www.youtube.com/watch?v=N6-eFp-3qs4&quot;&gt;https://www.youtube.com/watch?v=N6-eFp-3qs4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8-cursor-ai&quot;&gt;8. Cursor AI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 사이트: &lt;a href=&quot;https://www.cursor.com/&quot;&gt;https://www.cursor.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AI 기반 코드 에디터로 GPT-4, Claude 3.5 Sonnet 등 고급 AI 모델 통합&lt;/li&gt;
&lt;li&gt;기능:&lt;ul&gt;
&lt;li&gt;AI 기반 코드 자동 완성&lt;/li&gt;
&lt;li&gt;코드 생성 및 편집&lt;/li&gt;
&lt;li&gt;디버깅 지원&lt;/li&gt;
&lt;li&gt;실시간 코드 설명&lt;/li&gt;
&lt;li&gt;자연어를 통한 코드 수정&lt;/li&gt;
&lt;li&gt;코드베이스 이해 및 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가격:&lt;ul&gt;
&lt;li&gt;Hobby(무료):&lt;ul&gt;
&lt;li&gt;2000 코드 완성&lt;/li&gt;
&lt;li&gt;50회 느린 프리미엄 요청&lt;/li&gt;
&lt;li&gt;Pro 2주 체험판&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pro($20/월):&lt;ul&gt;
&lt;li&gt;무제한 코드 완성&lt;/li&gt;
&lt;li&gt;월 500회 빠른 프리미엄 요청&lt;/li&gt;
&lt;li&gt;무제한 느린 프리미엄 요청&lt;/li&gt;
&lt;li&gt;하루 10회 Claude Opus 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Business($40/사용자/월):&lt;ul&gt;
&lt;li&gt;Pro 플랜의 모든 기능&lt;/li&gt;
&lt;li&gt;중앙 집중식 결제&lt;/li&gt;
&lt;li&gt;관리자 사용 현황 대시보드&lt;/li&gt;
&lt;li&gt;프라이버시 모드&lt;/li&gt;
&lt;li&gt;제로 데이터 보존&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특징:&lt;ul&gt;
&lt;li&gt;VSCode와 유사한 인터페이스로 쉬운 적응&lt;/li&gt;
&lt;li&gt;기존 확장 프로그램, 테마, 키 바인딩 지원&lt;/li&gt;
&lt;li&gt;SOC 2 인증으로 보안성 검증&lt;/li&gt;
&lt;li&gt;사용자 고유 API 키 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시 영상: &lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zTVNxdHI2H8&quot;&gt;https://www.youtube.com/watch?v=zTVNxdHI2H8&lt;/a&gt; (Code Like a PRO with Cursor AI)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fjrKz4KAJQ0&quot;&gt;https://www.youtube.com/watch?v=fjrKz4KAJQ0&lt;/a&gt; (Cursor AI 사용법)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/412</guid>
      <comments>https://syaku.tistory.com/412#entry412comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:55:12 +0900</pubDate>
    </item>
    <item>
      <title>React에서 람다 함수를 선호하는 4가지 이유 (화살표 함수 활용법)</title>
      <link>https://syaku.tistory.com/411</link>
      <description>&lt;p&gt;React에서 람다 함수(화살표 함수)를 선호하는 주요 이유를 정리하겠습니다:&lt;/p&gt;
&lt;h2 id=&quot;1-&quot;&gt;1. 간결한 문법&lt;/h2&gt;
&lt;p&gt;람다 함수는 전통적인 함수 선언보다 더 간결한 문법을 제공합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 전통적인 함수 선언&lt;/span&gt;
&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;traditionalFunction&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;param&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; param + &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 람다 함수&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; lambdaFunction = &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;param&lt;/span&gt;) =&amp;gt;&lt;/span&gt; param + &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;특히 단일 표현식을 반환하는 경우, 중괄호와 return 키워드를 생략할 수 있어 코드가 더 간결해집니다.&lt;/p&gt;
&lt;h2 id=&quot;2-lexical-this-this-&quot;&gt;2. Lexical this (this 바인딩 문제 해결)&lt;/h2&gt;
&lt;p&gt;람다 함수의 가장 중요한 특징 중 하나는 &amp;quot;lexical this&amp;quot;입니다. 람다 함수는 자신만의 this 바인딩을 생성하지 않고, 대신 자신을 포함하고 있는 외부 스코프에서 this를 상속받습니다.&lt;/p&gt;
&lt;p&gt;예를 들어, 다음과 같은 React 클래스 컴포넌트가 있다고 가정해봅시다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-jsx&quot;&gt;&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MyComponent&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;React&lt;/span&gt;.&lt;span class=&quot;hljs-title&quot;&gt;Component&lt;/span&gt; &lt;/span&gt;{
  constructor(props) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;super&lt;/span&gt;(props);
    &lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;.handleClick = &lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;.handleClick.bind(&lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;);
  }

  handleClick() {
    console.log(&lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;.props.name);
  }

  render() {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &amp;lt;button onClick={&lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;.handleClick}&amp;gt;&lt;span class=&quot;hljs-type&quot;&gt;Click&lt;/span&gt; me&amp;lt;/button&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 handleClick 메서드는 일반 함수로 정의되었기 때문에 자신만의 this 바인딩을 갖습니다. 따라서 생성자에서 명시적으로 this를 바인딩해주어야 합니다. (this.handleClick = this.handleClick.bind(this);)&lt;/p&gt;
&lt;p&gt;하지만 람다 함수를 사용하면 이 과정이 필요없어집니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-jsx&quot;&gt;&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MyComponent&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;React&lt;/span&gt;.&lt;span class=&quot;hljs-title&quot;&gt;Component&lt;/span&gt; &lt;/span&gt;{
  handleClick = () =&amp;gt; {
    console.log(&lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;.props.name);
  };

  render() {
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &amp;lt;button onClick={&lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt;.handleClick}&amp;gt;&lt;span class=&quot;hljs-type&quot;&gt;Click&lt;/span&gt; me&amp;lt;/button&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 handleClick은 화살표 함수로 정의되었기 때문에 자신만의 this 바인딩을 생성하지 않고, 대신 MyComponent 클래스의 this 컨텍스트를 상속받습니다. 따라서 별도의 바인딩 과정 없이도 this.props에 접근할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;이처럼 람다 함수를 사용하면 React 컴포넌트 내부에서 콜백 함수를 정의할 때 this 바인딩 문제를 간단하게 해결할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;3-&quot;&gt;3. 콜백 함수에 적합&lt;/h2&gt;
&lt;p&gt;React에서 이벤트 핸들러나 콜백 함수를 정의할 때 람다 함수가 매우 유용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-jsx&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Component&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) &lt;/span&gt;{
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (
    &lt;span class=&quot;xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;onClick&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;{()&lt;/span&gt; =&amp;gt;&lt;/span&gt; console.log('Clicked!')}&amp;gt;
      Click me
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSX 내에서 인라인으로 정의할 때 특히 간결하고 읽기 쉬운 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;4-&quot;&gt;4. 암시적 반환&lt;/h2&gt;
&lt;p&gt;단일 표현식을 반환하는 경우, 람다 함수를 사용하면 return 키워드 없이 암시적 반환이 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 명시적 반환&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; explicitReturn = &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; a + b;
};

&lt;span class=&quot;hljs-comment&quot;&gt;// 암시적 반환&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; implicitReturn = &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) =&amp;gt;&lt;/span&gt; a + b;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 특징들로 인해 React 개발에서 람다 함수는 코드를 더 간결하고 읽기 쉽게 만들며, 특히 this 바인딩 문제를 효과적으로 해결할 수 있습니다. 그러나 성능과 사용 맥락을 고려하여 적절히 사용해야 합니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>reactjs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/411</guid>
      <comments>https://syaku.tistory.com/411#entry411comment</comments>
      <pubDate>Fri, 1 Nov 2024 00:54:22 +0900</pubDate>
    </item>
    <item>
      <title>Vite CJS 빌드 경고 해결 방법: 모듈 시스템 이해하기</title>
      <link>https://syaku.tistory.com/410</link>
      <description>&lt;h2 id=&quot;vite-cjs-&quot;&gt;Vite의 CJS 빌드 경고 해결&lt;/h2&gt;
&lt;p&gt;Vite 5에서 나타나는 다음 경고를 해결하는 방법을 설명하겠습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The CJS build of Vite's &lt;span class=&quot;hljs-keyword&quot;&gt;Node&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;API&lt;/span&gt; is deprecated. See https://vite.dev/guide/troubleshooting.html&lt;span class=&quot;hljs-comment&quot;&gt;#vite-cjs-node-api-deprecated for more details.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;-&quot;&gt;해결 방법&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;package.json에 ESM 설정 추가&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;lang-json&quot;&gt;{
  &lt;span class=&quot;hljs-attr&quot;&gt;&quot;type&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;module&quot;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&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;JavaScript: &lt;code&gt;vite.config.js&lt;/code&gt; → &lt;code&gt;vite.config.mjs&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TypeScript: &lt;code&gt;vite.config.ts&lt;/code&gt; → &lt;code&gt;vite.config.mts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;import 별칭(alias) 수정&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// 이전 (CJS 방식)&lt;/span&gt;
alias: {
  &lt;span class=&quot;hljs-string&quot;&gt;'@'&lt;/span&gt;: path.resolve(__dirname, &lt;span class=&quot;hljs-string&quot;&gt;'./src'&lt;/span&gt;)
}

&lt;span class=&quot;hljs-comment&quot;&gt;// 변경 후 (ESM 방식)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { fileURLToPath } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'url'&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { dirname, resolve } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'path'&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; __filename = fileURLToPath(&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt;.meta.url)
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; __dirname = dirname(__filename)

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; {
  &lt;span class=&quot;hljs-attr&quot;&gt;resolve&lt;/span&gt;: {
    &lt;span class=&quot;hljs-attr&quot;&gt;alias&lt;/span&gt;: {
      &lt;span class=&quot;hljs-string&quot;&gt;'@'&lt;/span&gt;: resolve(__dirname, &lt;span class=&quot;hljs-string&quot;&gt;'./src'&lt;/span&gt;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;javascript-&quot;&gt;JavaScript 모듈 시스템&lt;/h2&gt;
&lt;h3 id=&quot;1-cjs-commonjs-&quot;&gt;1. CJS (CommonJS)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 내보내기&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-keyword&quot;&gt;exports&lt;/span&gt; = { myFunction };

&lt;span class=&quot;hljs-comment&quot;&gt;// 가져오기&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { myFunction } = require(&lt;span class=&quot;hljs-string&quot;&gt;'./module'&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 기본 모듈 시스템&lt;/li&gt;
&lt;li&gt;동기적 로딩&lt;/li&gt;
&lt;li&gt;런타임에 모듈 해석&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-esm-es-modules-&quot;&gt;2. ESM (ES Modules)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// 내보내기&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; myFunction = &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;()&lt;/span&gt; =&amp;gt;&lt;/span&gt; {};

&lt;span class=&quot;hljs-comment&quot;&gt;// 가져오기&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { myFunction } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'./module'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모던 JavaScript 표준&lt;/li&gt;
&lt;li&gt;정적 분석 가능&lt;/li&gt;
&lt;li&gt;Tree-shaking 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-umd-universal-module-definition-&quot;&gt;3. UMD (Universal Module Definition)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;(&lt;span class=&quot;hljs-name&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-name&quot;&gt;root&lt;/span&gt;, factory) {
    if (&lt;span class=&quot;hljs-name&quot;&gt;typeof&lt;/span&gt; define === 'function' &amp;amp;&amp;amp; define.amd) {
        define(['dependency'], factory)&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
    } else if (&lt;span class=&quot;hljs-name&quot;&gt;typeof&lt;/span&gt; module === 'object' &amp;amp;&amp;amp; module.exports) {
        module.exports = factory(&lt;span class=&quot;hljs-name&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-name&quot;&gt;'dependency'&lt;/span&gt;))&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
    } else {
        root.myModule = factory(&lt;span class=&quot;hljs-name&quot;&gt;root.dependency&lt;/span&gt;)&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
    }
}(&lt;span class=&quot;hljs-name&quot;&gt;this&lt;/span&gt;, function(&lt;span class=&quot;hljs-name&quot;&gt;dependency&lt;/span&gt;) {
    return {}&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
}))&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;html-&quot;&gt;HTML 스크립트 로딩 방식&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 1. 동기 로딩 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 2. 비동기 로딩 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 3. 지연 로딩 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 4. ES 모듈 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;module&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 5. 인라인 스크립트 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;javascript&quot;&gt;
  &lt;span class=&quot;hljs-built_in&quot;&gt;console&lt;/span&gt;.log(&lt;span class=&quot;hljs-string&quot;&gt;'즉시 실행'&lt;/span&gt;);
&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-&quot;&gt;각 방식의 특징:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;동기 로딩&lt;/strong&gt;: &lt;/li&gt;
&lt;li&gt;HTML 파싱 중단&lt;/li&gt;
&lt;li&gt;&lt;p&gt;순차적 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;async&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;비동기 로딩&lt;/li&gt;
&lt;li&gt;실행 순서 불확실&lt;/li&gt;
&lt;li&gt;&lt;p&gt;독립적 스크립트에 적합&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;defer&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;HTML 파싱 후 실행&lt;/li&gt;
&lt;li&gt;실행 순서 보장&lt;/li&gt;
&lt;li&gt;&lt;p&gt;의존성 있는 스크립트에 적합&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;type=&amp;quot;module&amp;quot;&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;자동 defer 적용&lt;/li&gt;
&lt;li&gt;strict mode 기본&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CORS 규칙 적용&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;/li&gt;
&lt;li&gt;캐싱 불가&lt;/li&gt;
&lt;li&gt;추가 요청 없음&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발</category>
      <category>Vite</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/410</guid>
      <comments>https://syaku.tistory.com/410#entry410comment</comments>
      <pubDate>Wed, 30 Oct 2024 14:41:09 +0900</pubDate>
    </item>
    <item>
      <title>How to Resolve Vite CJS Build Warning: Uanderstanding Module Systems</title>
      <link>https://syaku.tistory.com/409</link>
      <description>&lt;h1 id=&quot;complete-guide-to-vite-module-system-from-cjs-warning-resolution-to-esm-migration&quot;&gt;Complete Guide to Vite Module System: From CJS Warning Resolution to ESM Migration&lt;/h1&gt;
&lt;h2 id=&quot;resolving-vite-s-cjs-build-warning&quot;&gt;Resolving Vite&amp;#39;s CJS Build Warning&lt;/h2&gt;
&lt;p&gt;In Vite 5, you might encounter this warning:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The CJS build of Vite's &lt;span class=&quot;hljs-keyword&quot;&gt;Node&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;API&lt;/span&gt; is deprecated. See https://vite.dev/guide/troubleshooting.html&lt;span class=&quot;hljs-comment&quot;&gt;#vite-cjs-node-api-deprecated for more details.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;solutions&quot;&gt;Solutions&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add ESM Configuration to package.json&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-json&quot;&gt;{
&lt;span class=&quot;hljs-attr&quot;&gt;&quot;type&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;module&quot;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change Configuration File Extension&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JavaScript: &lt;code&gt;vite.config.js&lt;/code&gt; → &lt;code&gt;vite.config.mjs&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TypeScript: &lt;code&gt;vite.config.ts&lt;/code&gt; → &lt;code&gt;vite.config.mts&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modify Import Aliases&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Before (CJS style)&lt;/span&gt;
alias: {
  &lt;span class=&quot;hljs-string&quot;&gt;'@'&lt;/span&gt;: path.resolve(__dirname, &lt;span class=&quot;hljs-string&quot;&gt;'./src'&lt;/span&gt;)
}

&lt;span class=&quot;hljs-comment&quot;&gt;// After (ESM style)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { fileURLToPath } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'url'&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { dirname, resolve } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'path'&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; __filename = fileURLToPath(&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt;.meta.url)
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; __dirname = dirname(__filename)

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; {
  &lt;span class=&quot;hljs-attr&quot;&gt;resolve&lt;/span&gt;: {
    &lt;span class=&quot;hljs-attr&quot;&gt;alias&lt;/span&gt;: {
      &lt;span class=&quot;hljs-string&quot;&gt;'@'&lt;/span&gt;: resolve(__dirname, &lt;span class=&quot;hljs-string&quot;&gt;'./src'&lt;/span&gt;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;javascript-module-systems&quot;&gt;JavaScript Module Systems&lt;/h2&gt;
&lt;h3 id=&quot;1-cjs-commonjs-&quot;&gt;1. CJS (CommonJS)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Exporting&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-keyword&quot;&gt;exports&lt;/span&gt; = { myFunction };

&lt;span class=&quot;hljs-comment&quot;&gt;// Importing&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { myFunction } = require(&lt;span class=&quot;hljs-string&quot;&gt;'./module'&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Characteristics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js default module system&lt;/li&gt;
&lt;li&gt;Synchronous loading&lt;/li&gt;
&lt;li&gt;Runtime module resolution&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-esm-es-modules-&quot;&gt;2. ESM (ES Modules)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Exporting&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; myFunction = &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;()&lt;/span&gt; =&amp;gt;&lt;/span&gt; {};

&lt;span class=&quot;hljs-comment&quot;&gt;// Importing&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { myFunction } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;'./module'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Characteristics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modern JavaScript standard&lt;/li&gt;
&lt;li&gt;Static analysis capability&lt;/li&gt;
&lt;li&gt;Tree-shaking support&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-umd-universal-module-definition-&quot;&gt;3. UMD (Universal Module Definition)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;(&lt;span class=&quot;hljs-name&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-name&quot;&gt;root&lt;/span&gt;, factory) {
    if (&lt;span class=&quot;hljs-name&quot;&gt;typeof&lt;/span&gt; define === 'function' &amp;amp;&amp;amp; define.amd) {
        define(['dependency'], factory)&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
    } else if (&lt;span class=&quot;hljs-name&quot;&gt;typeof&lt;/span&gt; module === 'object' &amp;amp;&amp;amp; module.exports) {
        module.exports = factory(&lt;span class=&quot;hljs-name&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-name&quot;&gt;'dependency'&lt;/span&gt;))&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
    } else {
        root.myModule = factory(&lt;span class=&quot;hljs-name&quot;&gt;root.dependency&lt;/span&gt;)&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
    }
}(&lt;span class=&quot;hljs-name&quot;&gt;this&lt;/span&gt;, function(&lt;span class=&quot;hljs-name&quot;&gt;dependency&lt;/span&gt;) {
    return {}&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
}))&lt;span class=&quot;hljs-comment&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;html-script-loading-methods&quot;&gt;HTML Script Loading Methods&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 1. Synchronous Loading --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 2. Asynchronous Loading --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 3. Deferred Loading --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 4. ES Module --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;module&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;script.js&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;undefined&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;&amp;lt;!-- 5. Inline Script --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;javascript&quot;&gt;
  &lt;span class=&quot;hljs-built_in&quot;&gt;console&lt;/span&gt;.log(&lt;span class=&quot;hljs-string&quot;&gt;'Immediate execution'&lt;/span&gt;);
&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;characteristics-of-each-method-&quot;&gt;Characteristics of Each Method:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Synchronous Loading&lt;/strong&gt;: &lt;/li&gt;
&lt;li&gt;Blocks HTML parsing&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sequential execution guaranteed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;async&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Asynchronous loading&lt;/li&gt;
&lt;li&gt;Execution order not guaranteed&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suitable for independent scripts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;defer&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Executes after HTML parsing&lt;/li&gt;
&lt;li&gt;Execution order guaranteed&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ideal for dependent scripts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;type=&amp;quot;module&amp;quot;&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Automatic defer behavior&lt;/li&gt;
&lt;li&gt;Strict mode by default&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CORS rules applied&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;inline&lt;/strong&gt;: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Immediate execution&lt;/li&gt;
&lt;li&gt;No caching&lt;/li&gt;
&lt;li&gt;No additional requests&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발</category>
      <category>Vite</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/409</guid>
      <comments>https://syaku.tistory.com/409#entry409comment</comments>
      <pubDate>Wed, 30 Oct 2024 14:40:13 +0900</pubDate>
    </item>
    <item>
      <title>Node.js 프로젝트의 폴더 구조 가이드: 단수와 복수 네이밍 컨벤션</title>
      <link>https://syaku.tistory.com/408</link>
      <description>&lt;h1 id=&quot;node-js-&quot;&gt;Node.js 프로젝트의 폴더 구조 가이드: 단수와 복수 네이밍 컨벤션&lt;/h1&gt;
&lt;p&gt;Node.js 프로젝트를 시작할 때 가장 기본이 되는 것은 폴더 구조입니다. 잘 설계된 폴더 구조는 프로젝트의 확장성과 유지보수성을 높여줍니다. 특히 폴더 이름을 단수로 할지 복수로 할지는 많은 개발자들이 고민하는 부분입니다.&lt;/p&gt;
&lt;h2 id=&quot;-&quot;&gt;기본 폴더 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;lang-plaintext&quot;&gt;
src/
├── config/           &lt;span class=&quot;hljs-meta&quot;&gt;# 설정 관련 파일&lt;/span&gt;
├── middleware/       &lt;span class=&quot;hljs-meta&quot;&gt;# 미들웨어 파일&lt;/span&gt;
├── util/            &lt;span class=&quot;hljs-meta&quot;&gt;# 유틸리티 함수&lt;/span&gt;
├── core/            &lt;span class=&quot;hljs-meta&quot;&gt;# 핵심 기능&lt;/span&gt;
├── controllers/     &lt;span class=&quot;hljs-meta&quot;&gt;# 컨트롤러 파일들&lt;/span&gt;
├── services/        &lt;span class=&quot;hljs-meta&quot;&gt;# 서비스 파일들&lt;/span&gt;
├── models/          &lt;span class=&quot;hljs-meta&quot;&gt;# 모델 파일들&lt;/span&gt;
├── routes/          &lt;span class=&quot;hljs-meta&quot;&gt;# 라우트 파일들&lt;/span&gt;
└── types/           &lt;span class=&quot;hljs-meta&quot;&gt;# 타입 정의 파일들&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;-&quot;&gt;폴더명 네이밍 규칙&lt;/h2&gt;
&lt;h3 id=&quot;1-&quot;&gt;1. 단수형 사용 (개념/시스템)&lt;/h3&gt;
&lt;p&gt;특정 개념이나 시스템을 나타내는 폴더는 단수형을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-plaintext&quot;&gt;
src/
├── config/          &lt;span class=&quot;hljs-meta&quot;&gt;# 애플리케이션 설정&lt;/span&gt;
│   ├── database.js  &lt;span class=&quot;hljs-meta&quot;&gt;# 데이터베이스 설정&lt;/span&gt;
│   └── auth.js      &lt;span class=&quot;hljs-meta&quot;&gt;# 인증 설정&lt;/span&gt;
│
├── middleware/      &lt;span class=&quot;hljs-meta&quot;&gt;# 미들웨어 시스템&lt;/span&gt;
│   ├── auth.js      &lt;span class=&quot;hljs-meta&quot;&gt;# 인증 미들웨어&lt;/span&gt;
│   └── logger.js    &lt;span class=&quot;hljs-meta&quot;&gt;# 로깅 미들웨어&lt;/span&gt;
│
├── util/           &lt;span class=&quot;hljs-meta&quot;&gt;# 유틸리티 개념&lt;/span&gt;
│   ├── &lt;span class=&quot;hljs-keyword&quot;&gt;date&lt;/span&gt;.js     &lt;span class=&quot;hljs-meta&quot;&gt;# 날짜 관련 유틸&lt;/span&gt;
│   └── format.js   &lt;span class=&quot;hljs-meta&quot;&gt;# 포맷 관련 유틸&lt;/span&gt;
│
└── core/           &lt;span class=&quot;hljs-meta&quot;&gt;# 핵심 기능&lt;/span&gt;
    ├── &lt;span class=&quot;hljs-keyword&quot;&gt;server&lt;/span&gt;.js   &lt;span class=&quot;hljs-meta&quot;&gt;# 서버 설정&lt;/span&gt;
    └── database.js &lt;span class=&quot;hljs-meta&quot;&gt;# DB 연결&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-&quot;&gt;2. 복수형 사용 (유사 파일들의 그룹)&lt;/h3&gt;
&lt;p&gt;유사한 역할을 하는 파일들의 모음을 나타내는 폴더는 복수형을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-plaintext&quot;&gt;
src/
├── controllers/           &lt;span class=&quot;hljs-comment&quot;&gt;# 컨트롤러 그룹&lt;/span&gt;
│   ├── &lt;span class=&quot;hljs-keyword&quot;&gt;user&lt;/span&gt;Controller.js  &lt;span class=&quot;hljs-comment&quot;&gt;# 사용자 관련 컨트롤러&lt;/span&gt;
│   └── postController.js  &lt;span class=&quot;hljs-comment&quot;&gt;# 게시물 관련 컨트롤러&lt;/span&gt;
│
├── services/             &lt;span class=&quot;hljs-comment&quot;&gt;# 서비스 그룹&lt;/span&gt;
│   ├── &lt;span class=&quot;hljs-keyword&quot;&gt;user&lt;/span&gt;Service.js    &lt;span class=&quot;hljs-comment&quot;&gt;# 사용자 관련 서비스&lt;/span&gt;
│   └── postService.js    &lt;span class=&quot;hljs-comment&quot;&gt;# 게시물 관련 서비스&lt;/span&gt;
│
├── models/               &lt;span class=&quot;hljs-comment&quot;&gt;# 모델 그룹&lt;/span&gt;
│   ├── &lt;span class=&quot;hljs-keyword&quot;&gt;user&lt;/span&gt;Model.js      &lt;span class=&quot;hljs-comment&quot;&gt;# 사용자 모델&lt;/span&gt;
│   └── postModel.js      &lt;span class=&quot;hljs-comment&quot;&gt;# 게시물 모델&lt;/span&gt;
│
└── routes/              &lt;span class=&quot;hljs-comment&quot;&gt;# 라우트 그룹&lt;/span&gt;
    ├── &lt;span class=&quot;hljs-keyword&quot;&gt;user&lt;/span&gt;Routes.js     &lt;span class=&quot;hljs-comment&quot;&gt;# 사용자 관련 라우트&lt;/span&gt;
    └── postRoutes.js     &lt;span class=&quot;hljs-comment&quot;&gt;# 게시물 관련 라우트&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;-&quot;&gt;파일명 네이밍 규칙&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;단수형 사용&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;각 파일은 하나의 책임을 가지므로 단수형 사용&lt;/li&gt;
&lt;li&gt;클래스/객체 이름과 일치&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;lang-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// userController.js&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;UserController&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;
}

&lt;span class=&quot;hljs-comment&quot;&gt;// postService.js&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;PostService&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;일관된 접미사&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Controller, Service, Model 등의 접미사 사용&lt;/li&gt;
&lt;li&gt;역할을 명확히 표현&lt;pre&gt;&lt;code class=&quot;lang-plaintext&quot;&gt;userController&lt;span class=&quot;hljs-selector-class&quot;&gt;.js&lt;/span&gt;
userService&lt;span class=&quot;hljs-selector-class&quot;&gt;.js&lt;/span&gt;
userModel&lt;span class=&quot;hljs-selector-class&quot;&gt;.js&lt;/span&gt;
userRoutes.js
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;-&quot;&gt;네이밍 컨벤션의 장점&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;명확한 책임과 역할&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;폴더명으로 해당 그룹의 성격 파악 가능&lt;/li&gt;
&lt;li&gt;&lt;p&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;/li&gt;
&lt;li&gt;&lt;p&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;/li&gt;
&lt;li&gt;&lt;p&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;/li&gt;
&lt;li&gt;확장이 용이한 구조&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;-&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;폴더 구조를 설계할 때는 &quot;이 폴더가 개념/시스템을 나타내는가, 아니면 유사 파일들의 그룹인가&quot;를 기준으로 단수/복수를 결정하면 됩니다. 이러한 일관된 규칙은 프로젝트의 구조를 더 명확하고 이해하기 쉽게 만들어줍니다.&lt;/p&gt;
&lt;p&gt;이 가이드를 따르면 프로젝트 구조가 더 체계적이고 관리하기 쉬워질 것입니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>nodejs</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/408</guid>
      <comments>https://syaku.tistory.com/408#entry408comment</comments>
      <pubDate>Wed, 30 Oct 2024 14:38:55 +0900</pubDate>
    </item>
    <item>
      <title>error: RPC failed; HTTP 400 curl 22 The requested URL returned error: 400</title>
      <link>https://syaku.tistory.com/407</link>
      <description>&lt;p&gt;이 오류는 일반적으로 큰 파일을 push할 때 발생하는 HTTP 400 오류입니다. 해결 방법은 다음과 같습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Git 버퍼 크기 증가&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global http.postBuffer 524288000&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Git 압축 비활성화&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global http.compression 0&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;.gitattributes 설정&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# .gitattributes
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
*.eot binary
*.woff binary
*.pyc binary
*.pdf binary&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;큰 파일 추적 중지&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git rm --cached path/to/large/file&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;strong&gt;SSL 검증 비활성화&lt;/strong&gt; (임시 해결책)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global http.sslVerify false&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;추가로 시도해볼 수 있는 것들:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 캐시 정리
git gc
git prune

# 리포지토리 최적화
git repack -a -d --depth=250 --window=250&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 방법들을 순차적으로 시도해보시기 바랍니다. 특히 http.postBuffer 설정이 가장 일반적인 해결책입니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>git</category>
      <author>syaku</author>
      <guid isPermaLink="true">https://syaku.tistory.com/407</guid>
      <comments>https://syaku.tistory.com/407#entry407comment</comments>
      <pubDate>Wed, 30 Oct 2024 14:25:51 +0900</pubDate>
    </item>
  </channel>
</rss>