<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>슬 개발일지</title>
    <link>https://ye-seul0-0.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 11 Apr 2026 00:20:29 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>yeseul-kim01</managingEditor>
    <image>
      <title>슬 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/8432419/attach/824b56931eeb4e379a1dec500a35cd1d</url>
      <link>https://ye-seul0-0.tistory.com</link>
    </image>
    <item>
      <title>[빅데이터 분석기사] 1과목 개념 정리</title>
      <link>https://ye-seul0-0.tistory.com/307</link>
      <description>&lt;h1&gt;빅데이터 분석 기획&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DIKW - 데이터 정보 지식 지혜&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;byte 크기 순 - 킬 메 기 테 페 엑 제 요&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빅데이터의 3v - Volume Varieyl Velocity&lt;/h2&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;/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;/li&gt;
&lt;li&gt;균형 성과표 (BSC : balanced Score Card) 의 4가지 관점 - 과거 성과를 바탕으로 미래 성과를 창출 (재무적 , 고객 , 업무 프로세스 , 학습과 성장) 관점&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;빅데이터 플랫폼&lt;/h1&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;수집에는 ETL , 크롤러 , EAI 등이 있음.&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;html , xml , csv , json&lt;/li&gt;
&lt;li&gt;xml 은 sgml 문서 형식을 가진 마크업 언어를 만들 때 사용하는 다목적 마크업 언어고, 태그를 사용한다.&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;R(시각화) , 우지 , 플럼 , Hbase , 스쿱&lt;/li&gt;
&lt;li&gt;우지 는 워크플로우 관리 ! 스케줄링이나 모니터링 등등.. 맵 리듀스나 피그와 같은 액션들로 구성된 워크 플로우를 제어함.&lt;/li&gt;
&lt;li&gt;플럼은 데이터 수집할 때 !- 이벤트 에이전트 활용함!!&lt;/li&gt;
&lt;li&gt;분산 데이터 베이스인 Hbase 는 컬럼 기반 저장소로 HDFS 와 인터페이스를 제공함.&lt;/li&gt;
&lt;li&gt;스쿱(하둡에서 관계형 디비로 데이터 보냄. 커넥터!!)은 정형 데이터 수집. sqoop , 커넥터를 사용해서 하둡 파일 시스템으로 데이터를 수집하거나 파일 시스템에서 관계형 데이터베이스로 데이터를 보내는 기능을 수행함.&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;/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;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;피그는 맵 리듀스 api 를 단순화 시킴. sql 과 유사한 형태 , 피그 라틴이라는 자체 언어 제공&lt;/li&gt;
&lt;li&gt;하이브 ; hiveQL 라는 쿼리를 제공하며 내부적으로 맵 리듀스로 변환되어 실행된다.&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;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;h1&gt;데이터 분석 계획&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분석 로드맵 설정&lt;/h2&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;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;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-02 오전 12.36.21.png&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHrAI/dJMcabXZSXS/kX2bolfQV9AGb1wdCkUdM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHrAI/dJMcabXZSXS/kX2bolfQV9AGb1wdCkUdM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHrAI/dJMcabXZSXS/kX2bolfQV9AGb1wdCkUdM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHrAI%2FdJMcabXZSXS%2FkX2bolfQV9AGb1wdCkUdM1%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;356&quot; height=&quot;294&quot; data-filename=&quot;스크린샷 2026-04-02 오전 12.36.21.png&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 : 현 수준에서 과제 바로 적용하기 어렵지만 전략적 중요도가 젤 높고 시급하게 추진해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2 : 전략도 중요도가 높지는 않지만 중장기적 관점에선 추진해야함. 난이도가 높음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3 : 우선순위를 곧바로 적용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4 : 적용은 가능하지만, 전략도 중요도는 낮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3 사분면 영역이 가장 우선적으로 적용 해야 한다.&lt;/p&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;KDD 분석 방법론 : 데이터 세트 선택 - 전처리 - 데이터 변환 - 데이터 마이닝 - 평가&lt;/li&gt;
&lt;li&gt;CRISP-DM 분석 방법론 : 업무 이해 - 데이터 이해 - 데이터 주비 - 모델링 - 평가 - 배포&lt;/li&gt;
&lt;li&gt;SEMMA 분석 방법론 : 샘플링 - 탐색 - 수정 - 모델링 - 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;데이터 수집 및 저장 계획&lt;/h1&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;li&gt;내부 데이터 , 외부 데이터수집 방식 및 기술&lt;/li&gt;
&lt;li&gt;ETL : 데이터 웨어하우스 및 데이터 마트로 이동시키기 위해 필요한 원본 데이터를 추출하고 변환해 적재하는 기술&lt;/li&gt;
&lt;li&gt;FTP : TCPIP 프로토콜을 기반으로 해 서버 , 클라이언트 사이에서 파일 송수신&lt;/li&gt;
&lt;li&gt;Sqoop : 커넥터를 활용해 관계형 데이터베이스 시스템에서 하둡 파일 시스템으로 전송&lt;/li&gt;
&lt;li&gt;API : 실시간 데이터 수신 가능 , 인터페이스 기술임&lt;/li&gt;
&lt;li&gt;RSYNC : 1:1 로 파일 과 디렉토리를 동기화 하는 응용 프로그램&lt;/li&gt;
&lt;li&gt;크롤링 : 웹상 수집&lt;/li&gt;
&lt;li&gt;RSS : XML 기반으로 정보를 배포하는 프로토콜을 활용해 데이터를 수집&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;
&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;데이터 적시성 - 비기능적 요구사항이 잘 대처되고 있는 지&lt;/li&gt;
&lt;li&gt;데이터 보안성 - 보호성, 책임성 , 안정성&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HDFS 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hadoop distributed file system (하둡 분산 파일 시스템) - 빅데이터 관리 플랫폼;&lt;br /&gt;데이터를 수집하여 활용 가능한 형태의 데이터로 관리하기 위해 수집하고 저장하고 처리, 관리 등을 수행하는 소프트웨어 플랫폼이다.&lt;br /&gt;하둡 , HDFS , 맵리듀스 , Spark 등이 있다.&lt;br /&gt;GFS 와 동일한 소스코드를 사용한다. 복제 횟수는 관리자가 설정할 수 있으며 &lt;b&gt;네임노드는 메타데이터를 별도&lt;/b&gt;로 관리한다.&lt;br /&gt;관리하는 2개의 마스터 노드와 처리하는 1개의 슬레이브 노드로 이루어져있다.&lt;br /&gt;&lt;b&gt;복제&lt;/b&gt; 시에는 하나의 파일을 기본적으로 3개의 서버에 복제한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ETL - extraction , transformation , load : 3단계를 통해 DB에 적재한다. 데이터 수집 기술 중 하나&lt;/h2&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;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;HDFS , MapReduce , GFS 등이 있다.&lt;/li&gt;
&lt;li&gt;맵 리듀스는 분산 병렬 처리 모델로 map - shuffle- reduce , input -&amp;gt; spliting -&amp;gt; mapping -&amp;gt; shuffing -&amp;gt; reducing&lt;/li&gt;
&lt;li&gt;맵리듀스는 입력 데이터를 쪼개서 맵핑하고 섞은 후 분류하기 때문에 중복 제거와 합계 계산에 유용하며, 하둡에서 채택한 프로그래밍 모델이다.&lt;/li&gt;
&lt;li&gt;GFS 는 파일을 여러 조각으로 나누어 저장했고, 하둡에 영향을 줬음! - 동일한 소스 코드 - 쓰기보다 읽기 위주!하둡 - 오픈소스 , 빅데이터 플랫폼의 핵심 기술&lt;/li&gt;
&lt;li&gt;자바 기반 프레임 워크 ,&lt;/li&gt;
&lt;li&gt;분산 파일 시스템 + 맵리듀스 모듈로 구성&lt;/li&gt;
&lt;li&gt;하둡은 저장과 처리의 기본적인 기능만 제공 (실시간 데이터 처리 한계 , 일괄 처리임 , 복잡한 연산 처리 한계 , 64메가바이트 이하의 작은 파일 저장 시 관리가 힘들다. 데이터 백업 낮음. 3개의 복제본 파일 관리 방식이라 디스크 공간 낭비 , 단일 고장점이 존재함- 하나 멈추면 전체 중단)&lt;/li&gt;
&lt;li&gt;하둡의 기능을 보완하는 오픈소스 프로그램 등이 많이 있음.YARM - 리소스 관리 , 분산 컴퓨팅 환경 , 컴퓨팅 자원 관리 , 스케쥴링 사용 관리 , 리소스 매니저임Zookeeper - 빅데이터 서버 시스템 관리 , 분산 환경 서버들 간의 상호 조정 서비스데이터 마이닝 - Mahout분산 데이터 베이스 - Hbase (HDFS 기반의 nosql 데베) , Cassandra (컬럼 중심 DB와 행 중심 DB의 복합형, Nosql 의 하나)&lt;/li&gt;
&lt;li&gt;스트리밍 데이터 - Flume , Scribe , Chuckwa&lt;/li&gt;
&lt;li&gt;데이터 분석 - Hive , Pig(MapReduce 대신 자체 언어 Pig Latin 제공)&lt;/li&gt;
&lt;li&gt;워크 플로우 관리 - Oozie - 하둡 작업을 관리 , 빅데이터 처리 과정을 관리&lt;/li&gt;
&lt;li&gt;직렬화 - Avro - RPC 와 데이터 직렬화를 지원함.&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;비즈니스 성관 및 ROI (return on Investment) : 투자 대비 수익률을 관점으로 함. 비즈니스 관점임. 투자비용 요소 관점 - 다양성,속도,규모&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;h2 data-ke-size=&quot;size26&quot;&gt;빅데이터 분석 기획의 절차&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위 설정 - 정의 - 수행 - 위험&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개인정보 비동의 시에도 사용 가능한 경우&lt;/h2&gt;
&lt;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;정보 주체나 제 3자의 이익,생명 을 위해 필요할 시&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개인 정보 법 제도 - 개인정보보호,정보통신,신용정보(개정신)&lt;/h2&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;/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;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빅데이터의 3V - Volume, variety,velocity : 규모 다양성 속도&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4V 는 Value 추가됨!&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빅데이터 활용 3대 요소는 인력,자원(데이터),기술&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1 제타바이트는 2의 70 승 바이트임!&lt;/h2&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정형 vs 비정형&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;/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;/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;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;차등프라이버시 - 개인정보차등보호 ; 데이터 노이즈 추가해 보호 및 분석 가능하게 함.&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 변환 기술 - 직렬화&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 시각화 기술 - 가시화&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 저장 기술 - nosql , 비디스크 기반 DBMS , 분산 파일 시스템&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산 파일 시스템 - 하둡 , 구글 파일 , 아마존 S3 파일 시스템&lt;/li&gt;
&lt;li&gt;Nosql - 키값 모델 기반 Dynamo 이랑 Membase , 열 기반 Bigtable Hbase Cassandra , 문서 기반 couchDB MongoDB 가 있음.&lt;/li&gt;
&lt;li&gt;분산 메인 메모리 기반 DBMS - SAP hana voltDb&lt;/li&gt;
&lt;li&gt;플레시 메모리 활용 관리 시스템 - orcale smart flash cache 등이 있음.&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;/li&gt;
&lt;li&gt;구글의 맵리듀스 , 하둡의 맵리듀스 , 마이크로소프트의 dryard&lt;/li&gt;
&lt;li&gt;처리 프로그래밍 기술로는 sawzall, pig , 어파치 하이브 등이 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 접근 기술 - JDBC&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 분석 기술 - OLAP (online analytical processing)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;olap :&lt;/li&gt;
&lt;li&gt;데이터 마이닝&lt;/li&gt;
&lt;li&gt;연관 분석&lt;/li&gt;
&lt;li&gt;sns 분석&lt;/li&gt;
&lt;li&gt;전통적 통계 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;hadoop : 대용량 데이터를 분산 처리하기 위한 대표적인 프레임 워크&lt;/h2&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개인 정보는 정보 주체 동의 하에 원본 그대로 사용할 수 있다. 가명 익명 처리는 2차적 활용할 때 적용임.&lt;/h2&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;/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;/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;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;h2 data-ke-size=&quot;size26&quot;&gt;로그 스트리밍 수집 - flume , logstash&lt;/h2&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;/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;p data-ke-size=&quot;size16&quot;&gt;최적화 - 모두 알 때&lt;br /&gt;통찰 - 방법은 모름 ; 군집 분석 기법을 사용해서 고객을 그룹화해서 각 그룹의 특징을 발견하는 활동&lt;br /&gt;해결/솔루션 - 방법은 모르는데 대상은 암&lt;br /&gt;발견/탐색 - 둘다 모름.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 웨어 하우스 - 주제 지향성 , 데이터 통합 , 시계열성 , 비휘발성 DW!! 정형 데이터를 저장.&lt;/h2&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;Q 러닝 , SARSA 등의 알고리즘이 있다.&lt;/li&gt;
&lt;li&gt;순차적인 의사결정 문제에 주로 적용된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>자격증</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/307</guid>
      <comments>https://ye-seul0-0.tistory.com/307#entry307comment</comments>
      <pubDate>Thu, 2 Apr 2026 01:03:50 +0900</pubDate>
    </item>
    <item>
      <title>[NLP BootCamp - 02] NLP 입문</title>
      <link>https://ye-seul0-0.tistory.com/301</link>
      <description>&lt;h1&gt;NLP란? 자연어 처리의 정의&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 처리(Natural Language Processing, NLP)는 &lt;b&gt;컴퓨터가 인간의 언어를 이해&amp;middot;처리하도록 돕는 기술 분야&lt;/b&gt;이다. 즉, 사람이 일상적으로 사용하는 언어(텍스트나 음성)를 컴퓨터가 해석하여 의미를 이해하거나 생성할 수 있도록 한다. 일반적으로 NLP는 통계적 방법과 머신러닝&amp;middot;딥러닝 기법을 결합해 동작하며, 인공지능(AI)의 하위 분야로 분류된다. 예를 들어 IBM은 &amp;ldquo;NLP는 머신러닝을 활용해 컴퓨터가 인간의 언어를 이해하고 소통하도록 돕는 AI의 하위 분야&amp;rdquo;라고 정의하고 있으며, AWS 역시 &amp;ldquo;컴퓨터가 인간 언어를 해석&amp;middot;조작하고 이해할 수 있도록 하는 기술&amp;rdquo;이라 설명한다. NLP 기술은 검색 엔진의 질의 처리나 챗봇, 음성비서(예: Alexa, Siri) 등 다양한 응용에 필수적이다.&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;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;/b&gt; 같은 단어라도 문맥에 따라 의미가 달라진다. 예를 들어 &amp;ldquo;&lt;i&gt;배&lt;/i&gt;&amp;rdquo;는 &amp;ldquo;배가 고프다&amp;rdquo;일 때는 &amp;lsquo;배(위장)&amp;rsquo;를, &amp;ldquo;&lt;i&gt;배&lt;/i&gt;가 출항했다&amp;rdquo;일 때는 &amp;lsquo;배(선박)&amp;rsquo;를 뜻한다. 자연어에는 동음이의어&amp;middot;다의어&amp;middot;동형어 등이 많아, 단어의 정확한 의미 판단에는 주변 문맥이 중요하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불완전성:&lt;/b&gt; 일상 언어 표현은 문법이나 문장이 불완전해도 의사소통이 가능하다. 사람들은 때로 주어를 생략하거나 비문(非文) 형태로 말해도 서로 이해한다(예: &amp;ldquo;&lt;i&gt;대신 부탁했다&lt;/i&gt;&amp;rdquo;라는 문장만으로는 누가 무슨 부탁을 했는지 모호하다). 따라서 NLP 시스템은 이런 불완전한 표현에서도 의미를 유추해야 한다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다양성:&lt;/b&gt; 같은 의미라도 여러 방식으로 표현된다. 뉘앙스나 어순, 방언&amp;middot;은어 등으로 표현 방식이 매우 다양하다. 이미지 데이터의 작은 변화가 시각적 의미에 큰 영향을 주지 않는 반면, 자연어는 &lt;b&gt;단어 하나, 어순 하나가 바뀌면 뜻이 완전히 달라질 수 있어&lt;/b&gt; 정규화가 어렵다. 예를 들어 &amp;ldquo;조용히 해&amp;rdquo;와 &amp;ldquo;해 조용히&amp;rdquo;는 어순만 달라도 의미가 달라질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 NLP 태스크&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;개체명 인식(Named Entity Recognition, NER):&lt;/b&gt; 텍스트에서 사람 이름, 장소, 기관명 같은 의미 단위를 찾아 분류하는 작업이다. 예를 들어 문장에서 &amp;ldquo;&lt;i&gt;런던&lt;/i&gt;&amp;rdquo;을 장소로, &amp;ldquo;&lt;i&gt;마리아&lt;/i&gt;&amp;rdquo;를 사람 이름으로 식별한다. NER은 문서 요약, 정보 검색, 기계번역 등에서 중요한 역할을 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;품사 태깅(Pos Tagging):&lt;/b&gt; 문장 속 각 단어의 품사(명사, 동사, 형용사 등)를 문맥에 맞게 결정한다. 예컨대 &amp;ldquo;I can make a paper plane&amp;rdquo;에서 &amp;lsquo;make&amp;rsquo;는 동사, &amp;ldquo;What make of car&amp;rdquo;에서는 &amp;lsquo;make&amp;rsquo;가 명사(자동차 제조사)임을 구분한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단어 의미 명확화(WSD, Word Sense Disambiguation):&lt;/b&gt; 다의어(여러 뜻을 가진 단어)의 적절한 의미를 문맥에 따라 정한다. 예를 들어 &amp;ldquo;&lt;i&gt;make the grade&lt;/i&gt;&amp;rdquo;와 &amp;ldquo;&lt;i&gt;make a bet&lt;/i&gt;&amp;rdquo;에서 &amp;lsquo;make&amp;rsquo;의 의미가 각각 &amp;lsquo;성취하다&amp;rsquo;, &amp;lsquo;행하다&amp;rsquo;로 다르다는 것을 구별한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;질의응답(Question Answering, QA):&lt;/b&gt; 문서나 데이터베이스에서 사용자의 질문에 대한 답을 찾아내는 작업이다. 예를 들어, 어떤 문서를 주고 &amp;ldquo;뉴턴이 제창한 이론은?&amp;rdquo;이라는 질문이 들어오면, 문서에서 답변에 해당하는 구절을 추출해야 한다. 특히 추출형 QA에서는 질문에 해당하는 문서 내 텍스트 범위를 답으로 찾아낸다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기계번역과 요약:&lt;/b&gt; 한 언어의 문장을 다른 언어로 변환(기계번역)하거나, 긴 문서에서 핵심 정보를 추출해 짧게 요약하는 작업이다. IBM은 NLP 툴이 핵심 정보를 자동 분류&amp;middot;추출하여 콘텐츠를 요약하고, 한 언어에서 다른 언어로 번역하는 데 활용된다고 설명한다. 예를 들어 문서 처리 파이프라인에서 &amp;ldquo;하이라이트 자동 요약&amp;rdquo;이나 &amp;ldquo;영어-한국어 번역&amp;rdquo; 등이 여기에 속한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 &lt;b&gt;맞춤법 검사, 텍스트 정규화, 감성 분석, 구문 분석(파싱)&lt;/b&gt; 등 수많은 NLP 태스크가 연구되고 활용된다. 각각의 태스크는 자연어의 모호성과 다양성을 풀기 위해 문맥 이해, 통계 모델링, 딥러닝 등을 기반으로 한 알고리즘을 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜스포머(Transformer) 이후의 변화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2017년 트랜스포머 아키텍처의 등장은 NLP 연구와 응용을 근본적으로 바꾸었다. 그 전에는 &lt;b&gt;순환신경망(RNN) 기반 시퀀스-투-시퀀스 모델&lt;/b&gt;이 주로 기계번역 등에 사용되었다. 그러나 트랜스포머는 병렬 연산이 가능해 학습이 빠르고, 문맥을 멀리까지 한 번에 고려할 수 있어 BERT나 GPT 같은 대형 언어모델을 가능하게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 &lt;b&gt;모든 NLP 태스크를 생성 문제로 통합&lt;/b&gt;하려는 시도가 늘었다. 예를 들어 구글의 T5(Text-to-Text Transfer Transformer) 모델은 모든 NLP 태스크를 &lt;i&gt;&amp;ldquo;텍스트-투-텍스트&amp;rdquo;&lt;/i&gt; 문제로 재구성해 다룬다. 이전에는 감정분석 결과를 0/1로 출력하거나 NER에서 각 토큰에 클래스 레이블을 붙였지만, 이제는 &amp;ldquo;이 문장은 긍정입니다&amp;rdquo;처럼 모델이 자연어 형태로 답을 생성하도록 한다. 대표적인 모델인 GPT 시리즈는 &lt;b&gt;자기회귀 생성 모델&lt;/b&gt;로 설계되어, &amp;ldquo;다음 단어 예측&amp;rdquo; 문제를 통해 언어 모델링뿐 아니라 분류&amp;middot;생성 등 다양한 태스크를 수행할 수 있다. 실제로 IBM은 GPT-4 같은 사전학습 언어모델이 프롬프트에 따라 기사&amp;middot;보고서 등 사람과 유사한 텍스트를 생성할 수 있다고 보도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 모든 문제를 생성으로만 풀 수 있는 것은 아니다. 생성 모델의 출력은 확률적이어서 항상 정확성을 보장하지 않는다. 특히 &lt;b&gt;할루시네이션(hallucination)&lt;/b&gt;이라 불리는, 그럴듯하지만 사실과 다른 내용을 생성하는 현상이 여전히 큰 문제로 남아 있다. 최근 연구에서는 &amp;ldquo;대규모 언어모델의 환각 현상은 본질적 한계이며 완전히 제거할 수 없다&amp;rdquo;고 경고한다. 또한 출력 포맷을 명확히 제한하기 어렵고, 생성된 텍스트의 품질 평가 지표 마련도 쉽지 않다. 그럼에도 불구하고 트랜스포머 기반 생성모델은 NLP의 새로운 전기를 열었으며, 다양한 태스크를 단일한 모델로 다룰 수 있게 되었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NLP 태스크 이해의 중요성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 현대에는 거대 생성 모델이 많은 작업을 자동화해 주지만, 전통적 NLP 태스크의 이해는 여전히 중요하다. 예를 들어 &lt;b&gt;RAG(Retrieval-Augmented Generation)&lt;/b&gt; 방식의 QA 시스템은 사용자가 질문을 하면 곧바로 답을 생성하는 대신, 먼저 관련 문서를 검색(retrieval)한 후 유의미한 내용을 추출(extraction)하고 이를 바탕으로 답을 생성(generation)한다. IBM 설명에 따르면, RAG는 외부 지식 베이스와 연결하여 LLM이 보다 높은 품질의 응답을 제공할 수 있게 해 준다 실제로 RAG는 검색 과정을 통해 최신 정보와 도메인 특화 내용을 학습 없이 활용할 수 있으므로, 오류 가능성을 줄이고 보다 정확한 답변을 얻는 데 기여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 생성형 모델의 실패를 분석하거나 사용자 질의에 대한 설명 가능한 응답을 생성하려면, 전통적인 NLP 기법들(NER, 구문 분석, 정규화 등)에 대한 이해가 필요하다. 예를 들어, 생성 모델의 출력 결과가 의도와 어긋난다면 어떤 단계에서 문제가 발생했는지 파악하기 위해 &lt;b&gt;토큰 분류 모델&lt;/b&gt;이나 &lt;b&gt;문장 분석 모델&lt;/b&gt; 등을 활용해 내부 상태를 점검할 수 있다. 또한 모델이 특정 도메인 지식을 다루도록 하려면 전문 문서를 검색&amp;middot;추출하는 RAG 같은 파이프라인 설계가 유용하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모델 출력 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NLP 시스템의 전형적인 파이프라인은 &lt;b&gt;입력 X의 전처리 &amp;rarr; 벡터 표현 &amp;rarr; 모델 추론 &amp;rarr; 출력 Y의 디코딩&lt;/b&gt; 단계를 거친다. 먼저 입력 문장은 토크나이저를 이용해 단어 또는 하위어(subword) 단위로 분리(Tokenization)되고, 어간 추출이나 불용어 제거 등의 작업을 통해 정제된다. 이어서 각 토큰은 Word2Vec, GloVe 같은 임베딩 기법이나 최근 유행하는 문맥 임베딩을 통해 고정 길이의 벡터로 변환된다. 이렇게 만들어진 벡터 시퀀스가 모델(RNN이나 트랜스포머)을 거쳐 학습된 특징을 반영한 출력 벡터를 생성하면, 마지막으로 이 벡터를 사람이 이해할 수 있는 형태로 디코딩하는 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델의 &lt;b&gt;출력 설계&lt;/b&gt;는 풀고자 하는 태스크에 따라 달라진다. 크게 두 가지 유형인 &lt;b&gt;분류(classification)&lt;/b&gt;와 &lt;b&gt;회귀(regression)&lt;/b&gt;로 구분할 수 있다. 분류 문제의 경우 출력은 이산적인 클래스 라벨이 되며, 회귀 문제의 경우 연속적인 실수 값이 된다. 예를 들어 감성분석에서는 &amp;ldquo;긍정/부정&amp;rdquo;과 같은 이진 클래스로 예측할 수 있고, 품사 태깅에서는 어휘마다 품사 카테고리를 예측한다. 자연어 생성 태스크의 경우 Y를 문장 자체로 정의하며, 모델은 주어진 입력으로부터 연속된 단어 시퀀스를 생성한다. GPT-4 같은 거대 생성모델은 프롬프트에 따라 기사나 보고서 같은 텍스트를 생성할 수 있는데, 이때 각 단계마다 모델은 어휘 전체에 대한 확률 분포를 계산해 가장 높은 점수의 단어를 선택한다(순차적 토큰 예측).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 NLP 모델의 출력값은 문제 유형에 맞추어 분류값, 회귀값 혹은 생성된 텍스트 등 다양한 형태로 설계된다. &lt;b&gt;분류형 출력&lt;/b&gt;(이산 값)은 결과의 정확성과 제약 조건 적용이 비교적 쉽지만, &lt;b&gt;생성형 출력&lt;/b&gt;(연속된 텍스트)은 표현력이 풍부한 반면 제어가 까다롭고 때로는 불안정한 측면이 있다. 따라서 원하는 태스크에 적합한 출력 방식을 잘 설계하는 것이 NLP 시스템의 성능과 안정성을 높이는 데 중요하다.&lt;/p&gt;</description>
      <category>AI&amp;amp;MLOps</category>
      <category>NLP</category>
      <category>NLP부트캠프</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/301</guid>
      <comments>https://ye-seul0-0.tistory.com/301#entry301comment</comments>
      <pubDate>Mon, 23 Mar 2026 14:20:00 +0900</pubDate>
    </item>
    <item>
      <title>[NLP BootCamp - 01] Language Model의 발전: N-gram에서 RNN, 그리고 그 한계까지</title>
      <link>https://ye-seul0-0.tistory.com/297</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 처리에서 가장 기본적인 질문 중 하나는 이것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;이 문장이 얼마나 자연스러운가?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 문장을 보면 어색한지 자연스러운지 어느 정도 직관적으로 판단할 수 있다. 하지만 컴퓨터는 문장을 사람처럼 이해하지 못한다. 대신 지금까지 관찰한 데이터로부터, &amp;ldquo;이런 단어 다음에는 어떤 단어가 나올 가능성이 높은가&amp;rdquo;, 혹은 &amp;ldquo;이 문장 전체가 실제로 등장할 가능성이 얼마나 되는가&amp;rdquo;를 &lt;span&gt;&lt;b&gt;확률적으로 계산&lt;/b&gt;&lt;/span&gt;한다. 이러한 관점에서 등장한 것이 바로 &lt;span&gt;&lt;b&gt;언어 모델(Language Model)&lt;/b&gt;&lt;/span&gt; 이다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 언어 모델이 어떤 문제를 풀기 위해 등장했는지부터 시작해서, 전통적인 &lt;span&gt;&lt;b&gt;N-gram Language Model&lt;/b&gt;&lt;/span&gt;, 임베딩을 도입한 &lt;span&gt;&lt;b&gt;Neural Probabilistic Language Model(NPLM)&lt;/b&gt;&lt;/span&gt;, 그리고 긴 문맥을 다루기 위해 등장한 &lt;span&gt;&lt;b&gt;RNN과 LSTM&lt;/b&gt;&lt;/span&gt;까지의 흐름을 정리해보려고 한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 모델(Model)이란 무엇인가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인공지능 분야에 한정하지 않더라도 모델이라는 용어는 정말 자주 등장한다. 물리학에서는 운동 방정식, 경제학에서는 시장 모델, 기상학에서는 날씨 모델처럼, 모델은 일반적으로 &lt;span&gt;&lt;b&gt;현실 세계의 일부 현상이나 대상을 단순화해 표현한 수학적&amp;middot;계산적 구조&lt;/b&gt;&lt;/span&gt;를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 모델의 공통점은 세 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 현실의 어떤 패턴이나 현상을 설명하려는 목적이 있다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인공지능에서는 보통 이를 다음과 같이 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.18.40.png&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sBZdz/dJMcafFTtDt/Hy7rBKIRPTyk72v7Lda0v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sBZdz/dJMcafFTtDt/Hy7rBKIRPTyk72v7Lda0v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sBZdz/dJMcafFTtDt/Hy7rBKIRPTyk72v7Lda0v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsBZdz%2FdJMcafFTtDt%2FHy7rBKIRPTyk72v7Lda0v0%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;200&quot; height=&quot;90&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.18.40.png&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;는 입력, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;는 예측값, 세타&amp;nbsp;&lt;/span&gt;&lt;span&gt;는 모델의 파라미터다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉 모델 학습이란, 입력 &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;가 주어졌을 때 예측값 &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;가 실제 정답 &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;에 가까워지도록 파라미터 세타&lt;/span&gt;&lt;span&gt;를 조정하는 과정이라고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어 처리에서는 이런 모델 중에서도 특히 &lt;span&gt;&lt;b&gt;확률 모델&lt;/b&gt;&lt;/span&gt;의 관점이 중요하다. 언어는 정답/오답처럼 딱 잘라 떨어지기보다, 특정 표현이 &lt;span&gt;&lt;b&gt;얼마나 그럴듯한가&lt;/b&gt;&lt;/span&gt;로 판단하는 편이 훨씬 자연스럽기 때문이다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Language Model이란 무엇인가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어처럼 문법을 먼저 설계한 뒤 만들어진 체계와 달리, 자연어는 사람들이 먼저 쓰고 나중에 문법이 정리된 체계에 가깝다. 그래서 자연어에는 다음과 같은 경우가 흔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문법적으로는 맞지만 어색한 문장&lt;/li&gt;
&lt;li&gt;문법적으로는 다소 틀렸지만 의미 전달은 되는 문장&lt;/li&gt;
&lt;li&gt;문맥에 따라 자연스러움이 달라지는 문장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자연어를 다룰 때는 단순히 &amp;ldquo;맞다 / 틀리다&amp;rdquo;보다는, &amp;ldquo;이 문장이 실제로 등장할 가능성이 얼마나 높은가&quot; 를 묻는 편이 훨씬 현실적이다. 이때 등장하는 것이 언어 모델이며, 언어 모델은 문장 &lt;span&gt;W&lt;/span&gt;가 등장할 확률을 모델링한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;P(W)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 확률이 높다는 것은, 해당 문장이 데이터에서 더 자주 등장할 법하고 언어적으로 더 자연스럽다는 뜻으로 해석할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 두 문장을 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A: 나는 매일 아침 사과를 먹는다.&lt;/li&gt;
&lt;li&gt;B: 나는 매일 아침 능금을 먹는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문장 모두 문법적으로는 큰 문제가 없지만, 일반적인 현대 한국어 코퍼스에서는 보통 A가 더 자주 관측될 가능성이 크다. 그렇다면 언어 모델은 보통 &lt;span&gt;P(A)&lt;/span&gt;를 &lt;span&gt;P(B)&lt;/span&gt;보다 더 높게 부여하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 문장 확률과 Chain Rule&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 모델의 목표는 문장 전체의 확률 &lt;span&gt;P(W)&lt;/span&gt;를 계산하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문장을 &lt;span&gt;w_1, w_2, ... , w_n&lt;/span&gt;이라는 단어열로 두면, 문장 확률은 다음과 같이 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.12.26.png&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxUjuR/dJMb99Mprpo/0eFJfK9EkQbH7LCyMGki80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxUjuR/dJMb99Mprpo/0eFJfK9EkQbH7LCyMGki80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxUjuR/dJMb99Mprpo/0eFJfK9EkQbH7LCyMGki80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxUjuR%2FdJMb99Mprpo%2F0eFJfK9EkQbH7LCyMGki80%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;398&quot; height=&quot;104&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.12.26.png&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 긴 문장을 통째로 하나의 사건처럼 다루는 것은 매우 어렵다. 실제 자연어는 조합의 수가 너무 많고, 문장이 길어질수록 &lt;span&gt;&lt;b&gt;똑같은 문장이 다시 등장할 가능성은 급격히 낮아지기 때문&lt;/b&gt;&lt;/span&gt;이다. Jurafsky와 Martin도 전체 문장 확률은 chain rule로 분해해 다루는 것이 기본이라고 설명한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확률의 chain rule을 적용하면 문장 확률은 다음처럼 쪼갤 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.12.57.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2BXkg/dJMcab4x09S/abYkso0kH52rVH2Q5RLqXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2BXkg/dJMcab4x09S/abYkso0kH52rVH2Q5RLqXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2BXkg/dJMcab4x09S/abYkso0kH52rVH2Q5RLqXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2BXkg%2FdJMcab4x09S%2FabYkso0kH52rVH2Q5RLqXK%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;640&quot; height=&quot;140&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.12.57.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 문장 전체의 확률은 각 시점에서 &lt;span&gt;&lt;b&gt;이전 단어들이 주어졌을 때 다음 단어가 등장할 조건부 확률&lt;/b&gt;&lt;/span&gt;을 모두 곱한 것으로 볼 수 있다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식은 언어 모델의 핵심을 보여준다. 결국 언어 모델은 &amp;ldquo;다음 단어 예측 문제&amp;rdquo;를 반복하는 구조로 바꿔 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 확률적 언어 모델과 N-gram&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론적으로는 &lt;span&gt;w_k&lt;/span&gt;를 예측할 때 그 앞에 등장한 모든 단어를 전부 봐야 한다. 하지만 현실적으로는 그렇게 하기 어렵다. 앞 문맥이 길어질수록 가능한 조합 수가 폭발적으로 증가하고, 특정 긴 문맥이 코퍼스에서 정확히 같은 형태로 다시 등장할 가능성은 매우 낮아지기 때문이다. 이 문제를 줄이기 위해 고전적으로 사용된 근사가 바로 &lt;span&gt;&lt;b&gt;Markov Assumption&lt;/b&gt;&lt;/span&gt;이다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;다음 단어의 확률은 전체 과거가 아니라, &lt;span&gt;&lt;b&gt;직전 &lt;/b&gt;&lt;/span&gt;&lt;span&gt;N-1&lt;/span&gt;&lt;span&gt;&lt;b&gt;개의 단어만 보면 충분하다&lt;/b&gt;&lt;/span&gt;고 가정하자.&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같이 근사할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.13.24.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cENR5a/dJMcaiP6gch/H7NLYhO6u6FCJmm1Qg5Vz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cENR5a/dJMcaiP6gch/H7NLYhO6u6FCJmm1Qg5Vz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cENR5a/dJMcaiP6gch/H7NLYhO6u6FCJmm1Qg5Vz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcENR5a%2FdJMcaiP6gch%2FH7NLYhO6u6FCJmm1Qg5Vz1%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;672&quot; height=&quot;118&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.13.24.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이것이 바로 &lt;/span&gt;&lt;b&gt;N-gram Language Model&lt;/b&gt;&lt;span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 bigram은 직전 1개 단어만, trigram은 직전 2개 단어만, 4-gram은 직전 3개 단어만 본다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. N-gram은 실제로 어떻게 계산되는가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N-gram 모델은 본질적으로 &lt;span&gt;&lt;b&gt;빈도를 세는 모델&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 trigram 기준으로 다음 확률을 계산한다고 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.14.53.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4oKKc/dJMcaaq3gzm/4jNB8JeLMnZP2JRKTSFak1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4oKKc/dJMcaaq3gzm/4jNB8JeLMnZP2JRKTSFak1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4oKKc/dJMcaaq3gzm/4jNB8JeLMnZP2JRKTSFak1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4oKKc%2FdJMcaaq3gzm%2F4jNB8JeLMnZP2JRKTSFak1%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;1144&quot; height=&quot;212&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.14.53.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값은 최대우도추정(MLE) 기준으로 다음처럼 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 코퍼스에서 &amp;ldquo;나는 사과를&amp;rdquo; 다음에 &amp;ldquo;먹는다&amp;rdquo;가 얼마나 자주 이어졌는지를 세는 방식이다. 예를 들어 &amp;ldquo;나는 사과를&amp;rdquo;이 총 100번 등장했고, 그중 10번이 &amp;ldquo;먹는다&amp;rdquo;로 이어졌다면 위 확률은 0.1이 된다. 이런 빈도 기반 정규화가 전형적인 N-gram 확률 추정 방식이다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 접근은 직관적이고 단순하며, 실제로 오랫동안 매우 성공적으로 사용되었다. Bengio의 2003년 논문도 전통적인 n-gram 접근이 매우 성공적이었지만, &lt;span&gt;&lt;b&gt;차원의 저주(curse of dimensionality)&lt;/b&gt;&lt;/span&gt; 때문에 한계가 분명하다고 설명한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. N-gram의 장점과 한계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N-gram의 장점은 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현이 단순하다.&lt;/li&gt;
&lt;li&gt;해석이 쉽다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 한계도 매우 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6.1 희소성(Sparsity) 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;사과를 먹는다&amp;rdquo;는 자주 나왔지만, &amp;ldquo;포도를 먹는다&amp;rdquo;가 한 번도 등장하지 않았다고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N-gram은 단지 본 적이 없다는 이유로 두 번째 패턴에 매우 낮은 확률 혹은 0에 가까운 확률을 부여할 수 있다. 그러나 사람은 사과와 포도가 모두 &amp;ldquo;먹을 수 있는 과일&amp;rdquo;이라는 유사성을 이용해 두 문장 모두 자연스럽다고 판단한다. N-gram은 이런 &lt;span&gt;&lt;b&gt;단어 간 의미적 유사성&lt;/b&gt;&lt;/span&gt;을 반영하지 못한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6.2 긴 문맥을 보지 못함&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N-gram은 직전 &lt;span&gt;N-1&lt;/span&gt;개만 보기 때문에 문장이 길어지면 앞쪽 문맥 정보를 잃어버린다. 예를 들어 문장 초반에 중요한 힌트가 있었더라도, 현재 시점에서 그 힌트가 &lt;span&gt;N-1&lt;/span&gt;개 범위를 벗어나면 모델은 그것을 전혀 활용할 수 없다. 이것 역시 언어 이해에서 큰 한계다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. N-gram은 실무에서 어디에 활용되었나&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 언어 모델의 주류는 Transformer 기반이지만, N-gram은 오랫동안 다양한 실무 시스템의 핵심 구성요소였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예는 &lt;span&gt;&lt;b&gt;음성 인식&lt;/b&gt;&lt;/span&gt;이다. Jurafsky와 Martin은 N-gram이 음성 인식과 음성 합성, 철자 교정 등에서 중요한 구성요소라고 설명한다. 실제로 Mikolov의 RNN LM 논문도 비교 대상으로 state-of-the-art backoff n-gram language model을 두고, RNN LM이 음성 인식에서 perplexity와 word error rate를 개선함을 보고한다. 즉, RNN이 등장하기 전까지는 N-gram이 음성 인식에서 매우 강력한 기본기술이었다고 볼 수 있다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예는 &lt;span&gt;&lt;b&gt;자동완성&lt;/b&gt;&lt;/span&gt;과 &lt;span&gt;&lt;b&gt;검색어 추천&lt;/b&gt;&lt;/span&gt;이다. 오늘날 검색 시스템에서는 prefix matching, edge n-gram matching 같은 기법이 자동완성에 활용된다. OpenSearch 문서도 edge n-gram이 입력 중인 검색어의 앞부분을 잘라 빠른 부분 일치 검색을 지원한다고 설명한다. 엄밀히 말하면 이것은 고전적인 word-level language model과는 조금 다르지만, &amp;ldquo;n개의 연속된 문자 조각&amp;rdquo;을 이용해 실시간 추천과 부분 검색을 구현한다는 점에서 n-gram 계열 아이디어의 실용적 활용 예시다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 N-gram은 다음과 같은 곳에서 강점을 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;음성 인식의 언어 모델&lt;/li&gt;
&lt;li&gt;철자 교정과 오타 보정&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, N-gram은 단순한 이론 모델이 아니라 실제 산업 시스템에서 오랫동안 폭넓게 사용된 &lt;span&gt;&lt;b&gt;실전형 기본기 모델&lt;/b&gt;&lt;/span&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. NPLM: 임베딩을 도입한 신경망 언어 모델&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N-gram의 가장 큰 한계 중 하나는, 단어를 단지 &amp;ldquo;기호&amp;rdquo;처럼 다룬다는 점이다. &amp;ldquo;사과&amp;rdquo;와 &amp;ldquo;포도&amp;rdquo;가 비슷한 의미를 갖는다는 사실을 전혀 반영하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 Bengio 등이 제안한 것이 &lt;span&gt;&lt;b&gt;Neural Probabilistic Language Model(NPLM)&lt;/b&gt;&lt;/span&gt; 이다. 이 논문의 핵심은 두 가지를 동시에 학습하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;각 단어의 &lt;/span&gt;&lt;b&gt;distributed representation&lt;/b&gt;&lt;span&gt;, 즉 밀집 벡터 표현&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;그 벡터 표현을 기반으로 한 &lt;span&gt;&lt;b&gt;단어열의 확률 함수&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단어를 one-hot 인덱스가 아니라 &lt;span&gt;&lt;b&gt;의미가 담긴 벡터&lt;/b&gt;&lt;/span&gt;로 표현하고, 유사한 단어는 벡터 공간에서 서로 가깝게 놓이도록 학습한다. 그러면 한 번도 보지 못한 문장이라도, 그 문장을 구성하는 단어들이 이미 본 적 있는 단어들과 의미적으로 가깝다면 더 높은 확률을 줄 수 있다. Bengio 논문은 이를 &amp;ldquo;training sentence가 semantically neighboring sentences에 대해서도 일반화한다&amp;rdquo;고 설명한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. NPLM의 계산 흐름&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPLM은 구조적으로 보면 고정 길이 문맥을 입력으로 받는 &lt;span&gt;&lt;b&gt;feed-forward neural language model&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 4-gram 상황이라면, 직전 3개의 단어 &lt;span&gt;w_{t-3}, w_{t-2}, w_{t-1}&lt;/span&gt;를 보고 다음 단어 &lt;span&gt;w_t&lt;/span&gt;를 예측한다고 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.1 One-hot에서 임베딩으로&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단어는 처음에 one-hot vector로 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 계산에서는 임베딩 행렬 &lt;span&gt;C&lt;/span&gt;를 통해 각 단어를 저차원 밀집벡터로 바꾼다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.15.28.png&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QQMJs/dJMcacWF8dQ/LYTJN6X6JRL3hK1QGuukk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QQMJs/dJMcacWF8dQ/LYTJN6X6JRL3hK1QGuukk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QQMJs/dJMcacWF8dQ/LYTJN6X6JRL3hK1QGuukk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQQMJs%2FdJMcacWF8dQ%2FLYTJN6X6JRL3hK1QGuukk0%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;176&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.15.28.png&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 연산은 실제 코드 관점에서는 &amp;ldquo;one-hot과 행렬곱&amp;rdquo;이라기보다, &lt;span&gt;&lt;b&gt;단어 인덱스로 임베딩 행렬의 특정 행 또는 열을 lookup하는 과정&lt;/b&gt;&lt;/span&gt;으로 이해하는 편이 더 직관적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.2 Concatenation&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 직전 문맥에 해당하는 임베딩 벡터들을 이어 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.15.40.png&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ziR28/dJMcahqa29V/UU7dPzZc7GyBncDNlbjun0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ziR28/dJMcahqa29V/UU7dPzZc7GyBncDNlbjun0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ziR28/dJMcahqa29V/UU7dPzZc7GyBncDNlbjun0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FziR28%2FdJMcahqa29V%2FUU7dPzZc7GyBncDNlbjun0%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;314&quot; height=&quot;102&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.15.40.png&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.3 Hidden Layer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 벡터를 MLP에 통과시켜 문맥을 반영한 은닉 표현을 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.15.48.png&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3zuGE/dJMcagktBXz/LNjabBde4rFSKqiQuKgfpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3zuGE/dJMcagktBXz/LNjabBde4rFSKqiQuKgfpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3zuGE/dJMcagktBXz/LNjabBde4rFSKqiQuKgfpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3zuGE%2FdJMcagktBXz%2FLNjabBde4rFSKqiQuKgfpk%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;336&quot; height=&quot;108&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.15.48.png&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.4 Output Layer와 Softmax&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은닉 표현으로부터 어휘집 전체 크기 &lt;span&gt;V&lt;/span&gt;에 대한 점수를 만든 뒤,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.16.00.png&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yjGQq/dJMcagSk7Ci/kPdOXYkMok21JP9hBQY2d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yjGQq/dJMcagSk7Ci/kPdOXYkMok21JP9hBQY2d0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yjGQq/dJMcagSk7Ci/kPdOXYkMok21JP9hBQY2d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyjGQq%2FdJMcagSk7Ci%2FkPdOXYkMok21JP9hBQY2d0%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;232&quot; height=&quot;108&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.16.00.png&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 softmax를 적용해 다음 단어의 확률 분포를 얻는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.16.09.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUa9W8/dJMcagSk7Cs/RAGsUBzPGl83exYDge1bAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUa9W8/dJMcagSk7Cs/RAGsUBzPGl83exYDge1bAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUa9W8/dJMcagSk7Cs/RAGsUBzPGl83exYDge1bAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUa9W8%2FdJMcagSk7Cs%2FRAGsUBzPGl83exYDge1bAK%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;564&quot; height=&quot;162&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.16.09.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 N-gram이 단순 빈도 기반이었다면, NPLM은 &lt;span&gt;&lt;b&gt;단어 간 유사성을 반영하는 연속 벡터 공간&lt;/b&gt;&lt;/span&gt;을 도입해 희소성 문제를 완화했다는 점이다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10.&amp;nbsp; NPLM 한계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPLM은 분명 큰 진전이었다. 하지만 구조적으로는 여전히 &lt;span&gt;&lt;b&gt;고정 길이 문맥&lt;/b&gt;&lt;/span&gt;만 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 4-gram 기반 NPLM이라면 앞에 아무리 긴 문장이 있었어도 결국 직전 3개 단어만 입력으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 문장을 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;나는 어제 저녁에 고기를 먹었다. 그래서 오늘 저녁에는 __ 외에 다른 음식을 먹고 싶다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문장에서 빈칸에 들어갈 단어를 예측하려면 &amp;ldquo;어제 저녁&amp;rdquo;, &amp;ldquo;고기&amp;rdquo; 같은 앞 문맥이 중요하다. 하지만 고정 길이 NPLM은 현재 시점 바로 앞의 몇 단어만 보기 때문에 이런 장거리 의존성을 충분히 반영하기 어렵다. Mikolov의 RNN LM 논문도 Bengio의 feed-forward neural LM이 fixed-length context를 사용했다는 점을 언급하면서, recurrent connection이 이를 확장하는 방향임을 설명한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11. RNN: 문맥을 기억하는 언어 모델&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bej4jr/dJMcadH1CWL/WQvK4UaPcktekYmRKsA8P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bej4jr/dJMcadH1CWL/WQvK4UaPcktekYmRKsA8P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bej4jr/dJMcadH1CWL/WQvK4UaPcktekYmRKsA8P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbej4jr%2FdJMcadH1CWL%2FWQvK4UaPcktekYmRKsA8P1%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;1410&quot; height=&quot;722&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 한계를 극복하기 위해 등장한 것이 &lt;/span&gt;&lt;b&gt;RNN(Recurrent Neural Network)&lt;/b&gt;&lt;span&gt; 이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RNN의 핵심 아이디어는 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;이전 시점의 정보를 버리지 말고, 현재 시점 계산에 다시 넣자.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Olah의 설명처럼, RNN은 같은 네트워크 블록이 시퀀스의 각 시점에 반복적으로 적용되며, 이전 단계의 정보가 다음 단계로 전달된다. 즉 RNN은 sequence 데이터에 자연스럽게 맞는 구조다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RNN의 기본 수식은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.16.38.png&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eawW7n/dJMcaflAwRv/aggbo99lTlzxaNrnzXiS60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eawW7n/dJMcaflAwRv/aggbo99lTlzxaNrnzXiS60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eawW7n/dJMcaflAwRv/aggbo99lTlzxaNrnzXiS60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeawW7n%2FdJMcaflAwRv%2Faggbo99lTlzxaNrnzXiS60%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;464&quot; height=&quot;118&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.16.38.png&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;x_t&lt;/span&gt;&lt;span&gt;: 현재 시점의 입력 벡터&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;h_{t-1}&lt;/span&gt;: 이전 시점의 hidden state&lt;/li&gt;
&lt;li&gt;&lt;span&gt;h_t&lt;/span&gt;: 현재 시점의 hidden state&lt;/li&gt;
&lt;li&gt;&lt;span&gt;W_h, W_x&lt;/span&gt;&lt;span&gt;: 가중치&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;tanh&lt;/span&gt;&lt;span&gt;: 비선형 활성화 함수&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식의 의미는 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;현재 입력 &lt;/b&gt;&lt;/span&gt;&lt;span&gt;x_t&lt;/span&gt;&lt;span&gt;&lt;b&gt;와 이전까지의 문맥 요약 &lt;/b&gt;&lt;/span&gt;&lt;span&gt;h_{t-1}&lt;/span&gt;&lt;span&gt;&lt;b&gt;를 함께 사용해 새로운 문맥 요약 &lt;/b&gt;&lt;/span&gt;&lt;span&gt;h_t&lt;/span&gt;&lt;span&gt;&lt;b&gt;를 만든다&lt;/b&gt;&lt;/span&gt;&lt;span&gt;는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, NPLM처럼 입력 창(window)을 고정해 잘라보는 방식이 아니라, 시퀀스를 따라가면서 문맥 정보를 hidden state에 누적해 전달하려는 구조다. Mikolov의 RNN LM은 이런 recurrent 구조를 이용해 고정 길이 문맥 모델보다 더 넓은 문맥을 활용할 수 있었고, 음성 인식 실험에서 backoff n-gram 모델 대비 perplexity와 WER 개선을 보고했다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;12. RNN의 직관적 동작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RNN을 시점별로 생각해보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;시점 &lt;/span&gt;&lt;span&gt;t=0&lt;/span&gt;&lt;span&gt;에서 첫 번째 입력 &lt;/span&gt;&lt;span&gt;x_0&lt;/span&gt;&lt;span&gt;를 받아 &lt;/span&gt;&lt;span&gt;h_0&lt;/span&gt;&lt;span&gt;를 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;시점 &lt;/span&gt;&lt;span&gt;t=1&lt;/span&gt;&lt;span&gt;에서는 두 번째 입력 &lt;/span&gt;&lt;span&gt;x_1&lt;/span&gt;&lt;span&gt;과 이전 hidden state &lt;/span&gt;&lt;span&gt;h_0&lt;/span&gt;&lt;span&gt;를 함께 사용해 &lt;/span&gt;&lt;span&gt;h_1&lt;/span&gt;&lt;span&gt;을 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 과정을 반복하면 &lt;/span&gt;&lt;span&gt;h_t&lt;/span&gt;&lt;span&gt;는 이론적으로 &lt;/span&gt;&lt;span&gt;x_0&lt;/span&gt;&lt;span&gt;부터 &lt;/span&gt;&lt;span&gt;x_t&lt;/span&gt;&lt;span&gt;까지의 정보를 요약한 벡터가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조의 장점은 문장 길이가 달라져도 같은 가중치 &lt;span&gt;W_h, W_x&lt;/span&gt;를 반복 사용하므로, 가변 길이 시퀀스를 자연스럽게 처리할 수 있다는 점이다. 이것이 feed-forward NPLM과 RNN의 가장 큰 차이다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;13. RNN의 한계 1: Information Vanishing&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RNN은 과거 정보를 hidden state 하나에 계속 압축해서 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 hidden state의 크기는 고정되어 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 시퀀스가 길어질수록 더 많은 정보를 같은 크기의 벡터에 계속 눌러 담아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 문장 초반의 정보는 점차 희석되고, 뒤쪽 시점으로 갈수록 최근 정보가 상대적으로 더 큰 비중을 차지하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 흔히 &lt;span&gt;&lt;b&gt;information vanishing&lt;/b&gt;&lt;/span&gt;이라고 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 이전에라도, 표현 자체가 고정 크기 병목(bottleneck)을 가지기 때문에 아주 긴 문맥의 세부 정보를 온전히 보존하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, RNN은 &amp;ldquo;시퀀스 전체를 기억하고 싶다&amp;rdquo;는 의도로 설계되었지만, 실제로는 시간이 지날수록 초반 정보가 흐려지기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;14. RNN의 한계 2: Gradient Vanishing&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RNN의 더 큰 문제는 학습 과정에서 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역전파 시에는 시간축을 따라 기울기가 뒤로 전달되는데, 이 과정에서 작은 값들이 반복적으로 곱해지면 gradient가 점점 0에 가까워진다. 이것이 &lt;span&gt;&lt;b&gt;gradient vanishing&lt;/b&gt;&lt;/span&gt;이다. 반대로 큰 값이 반복되면 gradient exploding이 발생할 수 있다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으로 보자면, 입력항과 비선형성을 잠시 무시하고 recurrent weight만 단순화해 생각하면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.17.19.png&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csDJZl/dJMcacWF8nU/cHjTsS6xUmHO6DD43HRiAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csDJZl/dJMcacWF8nU/cHjTsS6xUmHO6DD43HRiAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csDJZl/dJMcacWF8nU/cHjTsS6xUmHO6DD43HRiAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsDJZl%2FdJMcacWF8nU%2FcHjTsS6xUmHO6DD43HRiAk%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;260&quot; height=&quot;96&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.17.19.png&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 비슷한 형태를 떠올릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 역전파에서는 &lt;span&gt;\tanh&lt;/span&gt;의 미분값까지 반복적으로 곱해진다. 그런데 &lt;span&gt;\tanh&lt;/span&gt;의 미분은 0과 1 사이 값이므로, 이 값이 여러 시점을 거쳐 계속 곱해지면 gradient는 쉽게 작아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 문장 초반 단어가 현재 예측에 중요하더라도, 그 정보는 학습 시점에 충분한 gradient를 받지 못해 잘 학습되지 않는다. 이 때문에 표준 RNN은 긴 문맥을 이론적으로는 다룰 수 있지만, 실제 학습에서는 장기 의존성을 잘 잡지 못하는 경우가 많았다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;15. LSTM: RNN의 한계를 완화한 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 등장한 대표적인 구조가 &lt;span&gt;&lt;b&gt;LSTM(Long Short-Term Memory)&lt;/b&gt;&lt;/span&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Olah의 설명처럼 LSTM은 기본 RNN에 비해 훨씬 정교한 구조를 가지며, 핵심은 &lt;span&gt;&lt;b&gt;cell state&lt;/b&gt;&lt;/span&gt;와 여러 gate를 통해 정보의 보존&amp;middot;삭제&amp;middot;추가를 제어하는 데 있다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LSTM의 cell state 업데이트는 대표적으로 다음과 같이 표현된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.17.04.png&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4JgYV/dJMcadH1CVB/oS9JqJFrNAP0zbIjLk9djK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4JgYV/dJMcadH1CVB/oS9JqJFrNAP0zbIjLk9djK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4JgYV/dJMcadH1CVB/oS9JqJFrNAP0zbIjLk9djK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4JgYV%2FdJMcadH1CVB%2FoS9JqJFrNAP0zbIjLk9djK%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;442&quot; height=&quot;148&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.17.04.png&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.17.33.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIgISS/dJMcacWF8oO/0m906zedyaZdDQ6QkZljI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIgISS/dJMcacWF8oO/0m906zedyaZdDQ6QkZljI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIgISS/dJMcacWF8oO/0m906zedyaZdDQ6QkZljI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIgISS%2FdJMcacWF8oO%2F0m906zedyaZdDQ6QkZljI1%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;434&quot; height=&quot;392&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.17.33.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식의 중요한 점은, 단순 RNN처럼 매 시점 완전히 새로 hidden state를 덮어쓰는 것이 아니라, &lt;span&gt;&lt;b&gt;기존 기억을 얼마나 남길지와 새 정보를 얼마나 넣을지를 gate로 조절한다&lt;/b&gt;&lt;/span&gt;는 것이다. 그 결과 gradient가 훨씬 안정적으로 흐를 수 있고, 표준 RNN보다 장기 의존성을 더 잘 학습할 수 있다. Olah도 실전에서 흥미로운 RNN 성능의 대부분이 사실상 LSTM에 의해 가능해졌다고 설명한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 LSTM도 완전한 해결책은 아니었다. 시퀀스를 여전히 순차적으로 처리해야 하므로 병렬화에 한계가 있고, 아주 긴 문맥을 다루는 데에는 여전히 구조적 부담이 남아 있었다. 이후 이러한 한계를 더 효과적으로 해결한 것이 &lt;span&gt;&lt;b&gt;Attention&lt;/b&gt;&lt;/span&gt;과 &lt;span&gt;&lt;b&gt;Transformer&lt;/b&gt;&lt;/span&gt;다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;16. 정리: 왜 N-gram에서 RNN으로 발전했는가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 흐름을 한 번에 정리하면 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;N-gram&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 문맥의 빈도를 세어 다음 단어 확률을 추정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적이고 강력했지만, 희소성 문제와 긴 문맥 처리의 한계가 있었다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NPLM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어를 임베딩 벡터로 바꾸어 단어 간 유사성을 반영했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;희소성 문제를 완화했지만, 여전히 고정 길이 문맥만 사용했다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RNN&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 hidden state를 현재 계산에 재사용하는 순환 구조를 도입해, 시퀀스 전체 문맥을 다루려 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 information vanishing과 gradient vanishing 문제가 발생했다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LSTM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cell state와 gate 구조를 통해 장기 의존성 문제를 완화했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 순차 처리 구조 자체의 한계는 남아 있었다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;결국 언어 모델의 발전사는 &lt;/span&gt;&lt;b&gt;더 긴 문맥을 더 안정적으로, 더 일반화 가능하게 다루려는 시도&lt;/b&gt;&lt;span&gt;의 역사라고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;17. 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 언어 모델은 문장 전체 확률을 계산하는 문제로 출발했지만, 실제로는 이를 각 시점의 다음 단어 예측 문제로 분해해 다뤄왔다. 그 과정에서 N-gram은 매우 단순하면서도 강력한 통계적 방법이었고, NPLM은 임베딩을 통해 단어 유사성을 반영했으며, RNN은 고정 길이 문맥의 한계를 넘기 위해 순환 구조를 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 RNN 역시 긴 시퀀스에서 정보를 안정적으로 전달하는 데 한계가 있었고, 이를 완화한 LSTM을 거쳐 결국 Attention과 Transformer로 흐름이 이어지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, &lt;/span&gt;&lt;b&gt;Language Model의 발전은 결국 &amp;ldquo;문맥을 얼마나 잘 기억하고 활용할 수 있는가&amp;rdquo;를 둘러싼 발전사&lt;/b&gt;&lt;span&gt;라고 정리할 수 있다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI&amp;amp;MLOps</category>
      <category>NLP개념</category>
      <category>NLP부트캠프</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/297</guid>
      <comments>https://ye-seul0-0.tistory.com/297#entry297comment</comments>
      <pubDate>Wed, 18 Mar 2026 12:19:51 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] ECS는 Running인데 계속 Unhealthy? ALB Health Check 실패의 진짜 원인 (보안그룹 아웃바운드 문제) (Product : SpeakNote)</title>
      <link>https://ye-seul0-0.tistory.com/256</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sMFGr/dJMb99L4R6T/v4kQbdmcuBk48zV30IEYK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sMFGr/dJMb99L4R6T/v4kQbdmcuBk48zV30IEYK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sMFGr/dJMb99L4R6T/v4kQbdmcuBk48zV30IEYK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsMFGr%2FdJMb99L4R6T%2Fv4kQbdmcuBk48zV30IEYK1%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;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Next.js 프론트를 ECS Fargate + ALB 구조로 배포하면서 이상한 현상을 겪었다.&lt;/p&gt;
&lt;p data-end=&quot;276&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너는 분명히 살아있는데 ECS는 계속 태스크를 죽이고 다시 올렸다.&lt;/p&gt;
&lt;p data-end=&quot;276&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;291&quot; data-start=&quot;283&quot; data-ke-size=&quot;size26&quot;&gt;증상&lt;/h2&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;293&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.02.51.png&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6HDHT/dJMcabb36MZ/Ue1UxInBrlw9dpD8fHNjgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6HDHT/dJMcabb36MZ/Ue1UxInBrlw9dpD8fHNjgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6HDHT/dJMcabb36MZ/Ue1UxInBrlw9dpD8fHNjgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6HDHT%2FdJMcabb36MZ%2FUe1UxInBrlw9dpD8fHNjgK%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;1441&quot; height=&quot;93&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.02.51.png&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.08.33.png&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VkFNo/dJMcaihXL8j/FWZUR6JZ2moqlR9UVJ4N10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VkFNo/dJMcaihXL8j/FWZUR6JZ2moqlR9UVJ4N10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VkFNo/dJMcaihXL8j/FWZUR6JZ2moqlR9UVJ4N10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVkFNo%2FdJMcaihXL8j%2FFWZUR6JZ2moqlR9UVJ4N10%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;1864&quot; height=&quot;60&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.08.33.png&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;ECS 서비스 이벤트 로그&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.03.17.png&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N7WaR/dJMcagLeU3y/3tJ2KyZDvdKkpdY75niypk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N7WaR/dJMcagLeU3y/3tJ2KyZDvdKkpdY75niypk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N7WaR/dJMcagLeU3y/3tJ2KyZDvdKkpdY75niypk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN7WaR%2FdJMcagLeU3y%2F3tJ2KyZDvdKkpdY75niypk%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;2268&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.03.17.png&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1771178645735&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task ... port 3000 is unhealthy in target-group ... reason: Health checks failed Amazon ECS replaced 1 tasks due to an unhealthy status.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;466&quot; data-start=&quot;456&quot; data-ke-size=&quot;size16&quot;&gt;그리고 무한 반복.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;543&quot; data-start=&quot;468&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;479&quot; data-start=&quot;468&quot;&gt;태스크 &amp;rarr; 실행됨&lt;/li&gt;
&lt;li data-end=&quot;506&quot; data-start=&quot;480&quot;&gt;Target group &amp;rarr; Unhealthy&lt;/li&gt;
&lt;li data-end=&quot;521&quot; data-start=&quot;507&quot;&gt;ECS &amp;rarr; 태스크 교체&lt;/li&gt;
&lt;li data-end=&quot;536&quot; data-start=&quot;522&quot;&gt;다시 Unhealthy&lt;/li&gt;
&lt;li data-end=&quot;543&quot; data-start=&quot;537&quot;&gt;또 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;545&quot; data-ke-size=&quot;size16&quot;&gt;무한 루프&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;545&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;579&quot; data-start=&quot;558&quot; data-ke-size=&quot;size26&quot;&gt;1차 의심: 컨테이너 문제?&lt;/h2&gt;
&lt;p data-end=&quot;605&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;ECS Exec으로 직접 들어가서 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.04.51.png&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsKF9/dJMcagYKCmF/OitbzXPpTINjSoqkbmkGKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsKF9/dJMcagYKCmF/OitbzXPpTINjSoqkbmkGKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsKF9/dJMcagYKCmF/OitbzXPpTINjSoqkbmkGKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsKF9%2FdJMcagYKCmF%2FOitbzXPpTINjSoqkbmkGKk%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;2416&quot; height=&quot;168&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.04.51.png&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;200&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;/ health /api/health &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;810&quot; data-start=&quot;803&quot; data-ke-size=&quot;size16&quot;&gt;전부 200&lt;/p&gt;
&lt;p data-end=&quot;830&quot; data-start=&quot;812&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너 내부에서는 완벽히 정상&lt;/p&gt;
&lt;hr data-end=&quot;835&quot; data-start=&quot;832&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;865&quot; data-start=&quot;837&quot; data-ke-size=&quot;size26&quot;&gt;2차 의심: HTTPS 리다이렉트 문제?&lt;/h2&gt;
&lt;p data-end=&quot;872&quot; data-start=&quot;867&quot; data-ke-size=&quot;size16&quot;&gt;ALB에서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;911&quot; data-start=&quot;874&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;890&quot; data-start=&quot;874&quot;&gt;80 &amp;rarr; 443 리다이렉트&lt;/li&gt;
&lt;li data-end=&quot;911&quot; data-start=&quot;891&quot;&gt;443 &amp;rarr; target group&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;916&quot; data-start=&quot;913&quot; data-ke-size=&quot;size16&quot;&gt;구성.&lt;/p&gt;
&lt;p data-end=&quot;974&quot; data-start=&quot;918&quot; data-ke-size=&quot;size16&quot;&gt;Next.js가 HTTPS 강제 리다이렉트 해서 Health check가 301 받는 건가 의심을 해봤는데&lt;/p&gt;
&lt;p data-end=&quot;991&quot; data-start=&quot;976&quot; data-ke-size=&quot;size16&quot;&gt;헤더 강제로 넣어서 테스트를 해도&lt;/p&gt;
&lt;p data-end=&quot;991&quot; data-start=&quot;976&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Host: dev.speaknote.site X-Forwarded-Proto: https&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1066&quot; data-start=&quot;1056&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 여전히 200.&lt;/p&gt;
&lt;p data-end=&quot;1080&quot; data-start=&quot;1068&quot; data-ke-size=&quot;size16&quot;&gt;리다이렉트 문제 아님.&lt;/p&gt;
&lt;hr data-end=&quot;1085&quot; data-start=&quot;1082&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1116&quot; data-start=&quot;1087&quot; data-ke-size=&quot;size26&quot;&gt;3차 의심: Target Group 설정?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1175&quot; data-start=&quot;1118&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1143&quot; data-start=&quot;1118&quot;&gt;Health check path &amp;rarr; /&lt;/li&gt;
&lt;li data-end=&quot;1157&quot; data-start=&quot;1144&quot;&gt;&amp;rarr; /health&lt;/li&gt;
&lt;li data-end=&quot;1175&quot; data-start=&quot;1158&quot;&gt;&amp;rarr; /api/health&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1184&quot; data-start=&quot;1177&quot; data-ke-size=&quot;size16&quot;&gt;다 바꿔봤다.&lt;/p&gt;
&lt;p data-end=&quot;1200&quot; data-start=&quot;1186&quot; data-ke-size=&quot;size16&quot;&gt;여전히 Unhealthy.&lt;/p&gt;
&lt;hr data-end=&quot;1205&quot; data-start=&quot;1202&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1234&quot; data-start=&quot;1207&quot; data-ke-size=&quot;size26&quot;&gt;진짜 원인: ALB 보안그룹 아웃바운드&lt;/h2&gt;
&lt;p data-end=&quot;1245&quot; data-start=&quot;1236&quot; data-ke-size=&quot;size16&quot;&gt;여기서 깨달았다.&lt;/p&gt;
&lt;h3 data-end=&quot;1262&quot; data-start=&quot;1247&quot; data-ke-size=&quot;size23&quot;&gt;ALB 보안그룹 상태&lt;/h3&gt;
&lt;p data-end=&quot;1272&quot; data-start=&quot;1264&quot; 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-end=&quot;1289&quot; data-start=&quot;1273&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1289&quot; data-start=&quot;1273&quot;&gt;443 허용 (내 IP만)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1300&quot; data-start=&quot;1291&quot; 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-end=&quot;1310&quot; data-start=&quot;1301&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1310&quot; data-start=&quot;1301&quot;&gt;443만 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1319&quot; data-start=&quot;1312&quot; data-ke-size=&quot;size16&quot;&gt;문제는 여기.&lt;/p&gt;
&lt;hr data-end=&quot;1324&quot; data-start=&quot;1321&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1352&quot; data-start=&quot;1326&quot; data-ke-size=&quot;size23&quot;&gt;ALB는 443으로만 통신하지 않는다&lt;/h3&gt;
&lt;p data-end=&quot;1379&quot; data-start=&quot;1354&quot; data-ke-size=&quot;size16&quot;&gt;ALB는 클라이언트와는 443으로 통신하지만,&lt;/p&gt;
&lt;p data-end=&quot;1391&quot; data-start=&quot;1381&quot; data-ke-size=&quot;size16&quot;&gt;타겟(ECS)으로는&lt;/p&gt;
&lt;blockquote data-end=&quot;1423&quot; data-start=&quot;1393&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1423&quot; data-start=&quot;1395&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타겟 포트(여기서는 3000)&lt;/b&gt; 로 접속한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1427&quot; data-start=&quot;1425&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;p data-end=&quot;1427&quot; data-start=&quot;1425&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;ALB&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;rarr;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;ECS :&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;TCP&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1478&quot; data-start=&quot;1459&quot; data-ke-size=&quot;size16&quot;&gt;그런데 ALB 보안그룹 아웃바운드가&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;TCP &lt;/span&gt;&lt;span&gt;&lt;span&gt;443&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;only&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1511&quot; data-start=&quot;1502&quot; data-ke-size=&quot;size16&quot;&gt;로 되어 있었다.&lt;/p&gt;
&lt;p data-end=&quot;1537&quot; data-start=&quot;1513&quot; data-ke-size=&quot;size16&quot;&gt;그럼 ALB는 타겟으로 연결 자체를 못한다.&lt;/p&gt;
&lt;p data-end=&quot;1572&quot; data-start=&quot;1539&quot; data-ke-size=&quot;size16&quot;&gt;Health check도 못하고&lt;br /&gt;Forward도 못한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;1616&quot; data-start=&quot;1613&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1628&quot; data-start=&quot;1618&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;해결 방법&lt;/h2&gt;
&lt;p data-end=&quot;1642&quot; data-start=&quot;1630&quot; data-ke-size=&quot;size16&quot;&gt;ALB 보안그룹 수정&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Outbound:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;TCP&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;rarr;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;ecs-service&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;보안그룹&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1693&quot; data-start=&quot;1691&quot; data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;All&lt;/span&gt;&lt;/span&gt;&lt;span&gt; traffic &amp;rarr; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0.0.0.0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span&gt;0 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(개발 환경이면 이게 편함)&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1752&quot; data-start=&quot;1745&quot; data-ke-size=&quot;size16&quot;&gt;수정하자마자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1801&quot; data-start=&quot;1754&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1778&quot; data-start=&quot;1754&quot;&gt;Target group &amp;rarr; Healthy&lt;/li&gt;
&lt;li data-end=&quot;1793&quot; data-start=&quot;1779&quot;&gt;ECS 교체 루프 종료&lt;/li&gt;
&lt;li data-end=&quot;1801&quot; data-start=&quot;1794&quot;&gt;정상 접속&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1805&quot; data-start=&quot;1803&quot; data-ke-size=&quot;size16&quot;&gt;끝.&lt;/p&gt;
&lt;hr data-end=&quot;1810&quot; data-start=&quot;1807&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1834&quot; data-start=&quot;1812&quot; data-ke-size=&quot;size26&quot;&gt;왜 내부에서는 200이었을까?&lt;/h2&gt;
&lt;p data-end=&quot;1846&quot; data-start=&quot;1836&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너 안에서는&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;/span&gt;&lt;span&gt; localhost:&lt;/span&gt;&lt;span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1880&quot; data-start=&quot;1877&quot; data-ke-size=&quot;size16&quot;&gt;정상.&lt;/p&gt;
&lt;p data-end=&quot;1891&quot; data-start=&quot;1882&quot; data-ke-size=&quot;size16&quot;&gt;하지만 ALB는&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;ALB &amp;rarr; ECS (&lt;/span&gt;&lt;span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1938&quot; data-start=&quot;1919&quot; data-ke-size=&quot;size16&quot;&gt;이 경로가 보안그룹에 막혀 있었음.&lt;/p&gt;
&lt;p data-end=&quot;1942&quot; data-start=&quot;1940&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;blockquote data-end=&quot;1968&quot; data-start=&quot;1944&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1968&quot; data-start=&quot;1946&quot; data-ke-size=&quot;size16&quot;&gt;앱은 정상&lt;br /&gt;네트워크가 막혀있던 것&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-end=&quot;1973&quot; data-start=&quot;1970&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1983&quot; data-start=&quot;1975&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-end=&quot;2042&quot; data-start=&quot;1985&quot; data-ke-size=&quot;size16&quot;&gt;ECS에서 Unhealthy 뜨면 무조건 앱 문제부터 의심하는데&lt;br /&gt;이번 케이스는 완전히 네트워크였다.&lt;/p&gt;
&lt;p data-end=&quot;2050&quot; data-start=&quot;2044&quot; data-ke-size=&quot;size16&quot;&gt;체크 순서&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2141&quot; data-start=&quot;2052&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2069&quot; data-start=&quot;2052&quot;&gt;컨테이너 내부 200 확인&lt;/li&gt;
&lt;li data-end=&quot;2093&quot; data-start=&quot;2070&quot;&gt;Target group path 확인&lt;/li&gt;
&lt;li data-end=&quot;2108&quot; data-start=&quot;2094&quot;&gt;리다이렉트 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;2141&quot; data-start=&quot;2109&quot;&gt;&lt;b&gt;ALB 보안그룹 아웃바운드 확인 &amp;larr; 이게 핵심&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-end=&quot;2146&quot; data-start=&quot;2143&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2160&quot; data-start=&quot;2148&quot; data-ke-size=&quot;size26&quot;&gt;오늘의 교훈&lt;/h2&gt;
&lt;blockquote data-end=&quot;2211&quot; data-start=&quot;2162&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;2211&quot; data-start=&quot;2164&quot; data-ke-size=&quot;size16&quot;&gt;ALB는 클라이언트와 443으로 통신하지만&lt;br /&gt;타겟과는 &amp;ldquo;타겟 포트&amp;rdquo;로 통신한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2240&quot; data-start=&quot;2213&quot; data-ke-size=&quot;size16&quot;&gt;이걸 안 열어두면&lt;br /&gt;헬스체크는 영원히 실패한다.&lt;/p&gt;
&lt;p data-end=&quot;2240&quot; data-start=&quot;2213&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2240&quot; data-start=&quot;2213&quot; data-ke-size=&quot;size16&quot;&gt;개 뻘~~~~짓을 했고,, 나는 또 새벽 3시에 잠들지만,,,, 한가지를 알아가서 행복하다..&lt;/p&gt;
&lt;p data-end=&quot;2240&quot; data-start=&quot;2213&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.07.09.png&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnNpj7/dJMcadgESuB/I7JIaKYa6KMmkrvXjYWOvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnNpj7/dJMcadgESuB/I7JIaKYa6KMmkrvXjYWOvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnNpj7/dJMcadgESuB/I7JIaKYa6KMmkrvXjYWOvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnNpj7%2FdJMcadgESuB%2FI7JIaKYa6KMmkrvXjYWOvK%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;396&quot; height=&quot;774&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.07.09.png&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.07.27.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BwdtP/dJMcaiCfovr/QQAypkeMgsFvkptc3EYje0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BwdtP/dJMcaiCfovr/QQAypkeMgsFvkptc3EYje0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BwdtP/dJMcaiCfovr/QQAypkeMgsFvkptc3EYje0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBwdtP%2FdJMcaiCfovr%2FQQAypkeMgsFvkptc3EYje0%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;1410&quot; height=&quot;50&quot; data-filename=&quot;스크린샷 2026-02-16 오전 3.07.27.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/AWS</category>
      <category>ALB Outbound Rule</category>
      <category>AWS</category>
      <category>ECS unhealthy</category>
      <category>보안그룹</category>
      <category>트러블슈팅</category>
      <category>프로젝트기록-speaknote</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/256</guid>
      <comments>https://ye-seul0-0.tistory.com/256#entry256comment</comments>
      <pubDate>Mon, 16 Feb 2026 03:12:44 +0900</pubDate>
    </item>
    <item>
      <title>[AWS - ECS &amp;amp; ALB]ECS + ALB+RDS 기반 서비스 아키텍처 정리</title>
      <link>https://ye-seul0-0.tistory.com/240</link>
      <description>&lt;pre id=&quot;code_1770915890304&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ Internet ]
     |
     v
[ Route53 ]
     |
     v
[ ALB ]  &amp;larr; (여기에 WAF를 &quot;나중에&quot; 붙일 예정)
     |
     v
[ ECS Fargate ]
     |
     v
[ Spring Boot App ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 특정 기능 구현 이야기가 아니라,&lt;br /&gt;&lt;b&gt;Spring Boot 기반 API 서버를 SaaS 구조로 설계하고 AWS 상에 배포하면서 고민했던 아키텍처 포인트&lt;/b&gt;를 정리해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 애플리케이션을 배포하는 것을 넘어, 멀티테넌시 구조, 무중단 배포 전략, 헬스체크 설계, 네트워크 경계 설정, 관찰성까지 고려한 &amp;ldquo;운영 가능한 구조&amp;rdquo;를 목표로 설계했다. (트래픽 분산처리는 하지 않음.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGol6/dJMcad1XN5J/JKzxRboWk6lQuZAz4pS600/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGol6/dJMcad1XN5J/JKzxRboWk6lQuZAz4pS600/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGol6/dJMcad1XN5J/JKzxRboWk6lQuZAz4pS600/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGol6%2FdJMcad1XN5J%2FJKzxRboWk6lQuZAz4pS600%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;1024&quot; height=&quot;1536&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EC2 vs ECS&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 EC2에 직접 배포할지, ECS를 사용할지 고민했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션은 어차피 Docker 컨테이너로 패키징할 예정이었고,&lt;br /&gt;운영 환경 또한 컨테이너 단위로 관리하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2를 선택한다면 다음을 직접 관리해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OS 패치 및 보안 업데이트&lt;/li&gt;
&lt;li&gt;Docker 런타임 관리&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;반면 ECS(Fargate)는 컨테이너 실행에 집중할 수 있는 환경을 제공한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Docker Image &amp;rarr; ECR &amp;rarr; ECS Service
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조로 배포하면, 인프라 레벨의 서버 관리 부담을 크게 줄일 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ECS를 선택한 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;컨테이너 중심 아키텍처와의 정합성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 Docker 기반으로 설계했기 때문에,&lt;br /&gt;컨테이너를 1급 실행 단위로 취급하는 ECS가 자연스러운 선택이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 위에서 Docker를 직접 관리하는 것보다,&lt;br /&gt;컨테이너 오케스트레이션 레이어를 활용하는 것이 구조적으로 명확하다고 판단했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;비용 관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 서비스는 트래픽이 일정하지 않고, 고정적으로 인스턴스를 상시 유지할 필요가 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fargate는&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2는 유연성이 높지만, 소규모 SaaS 서비스에서는 오히려 관리 오버헤드가 더 클 수 있다고 판단했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 및 라우팅 구성의 편의성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS + ALB 구조&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Internet
  &amp;darr;
ALB
  &amp;darr;
Target Group
  &amp;darr;
ECS Task
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;헬스체크 기반 트래픽 제어&lt;/li&gt;
&lt;li&gt;무중단 배포 (Rolling Update)&lt;/li&gt;
&lt;li&gt;네트워크 경계 분리 (ALB SG &amp;rarr; ECS SG)&lt;/li&gt;
&lt;li&gt;향후 스케일아웃 확장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 지원하기 때문에 EC2 단독 구성보다, 운영 관점에서 구조적 안정성을 확보하기 쉬웠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;ALB를 선택한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 서비스를 계속 운영 하되 , 비용적인 부분에서 최소화를 하고 싶었기 때문에&amp;nbsp;&lt;br /&gt;ip 접속 제한이 필수였다. 만약 ec2 일 경우에는 엔진엑스 설정을 통해 블랙 화이트 리스트를 설정하면 됐지만, ecs로 배포하기로 마음을 먹었기 때문에 ALB + WAF 의 경우를 생각해서 ALB를 선택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(추후 ip 제한을 달기 위해서 현재는 적용하지 않았음.)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;설정 순서&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;Step A. RDS(Postgres) 만들기 (DB 먼저)&lt;br /&gt;Step B. ECR에 Spring 이미지 푸시&lt;br /&gt;Step C. ECS Fargate + ALB(HTTPS)로 서비스 오픈&lt;br /&gt;Step D. Route53에서 fileext.yeseulkim.cloud &amp;rarr; ALB 연결&lt;br /&gt;Step E. (나중에) WAF로 IP 제한 ON&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 들어가기 전에 로컬에서 먼저 해둔 설정들 부터 정리해보자면&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;로컬에서의 기본 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가기 전, 기본적으로 도메인을 따로 구매해놔야 한다. (가비아에서 구매함! )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 모놀로식으로 구성했기 때문에, 여러개를 할 필요 없이 ECR 에 올릴 하나의 도커 이미지만 연결하면 됐었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 컴퓨터 로컬 환경에서 aws 로그인이 가능해야 한다. (왜냐면 이미지 빌드 한 뒤에 올리는 과정 때문... 여러개의 방법이 있었는데 처음은 키 발급이었음.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS 배포에 들어가기 전에, 단순히 콘솔을 여는 것보다 먼저 준비해야 할 것들이 있다.&lt;br /&gt;이 단계를 정리하지 않으면 이후 과정에서 권한 오류, 네트워크 오류, 이미지 푸시 실패 같은 문제가 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 계정 및 IAM&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;루트 계정으로 작업하지 않기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 환경에서는 루트 계정 대신 IAM User를 생성하여 작업해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필수 권한 예시&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ECR Full Access&lt;/li&gt;
&lt;li&gt;ECS Full Access&lt;/li&gt;
&lt;li&gt;Elastic Load Balancing&lt;/li&gt;
&lt;li&gt;IAM Role 관리 권한&lt;/li&gt;
&lt;li&gt;CloudWatch Logs 권한&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS CLI 설치 및 로그인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 Docker 이미지를 ECR에 업로드해야 하므로&lt;br /&gt;&lt;b&gt;AWS CLI 설정은 필수&lt;/b&gt;다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&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;AWS Access Key&lt;/li&gt;
&lt;li&gt;AWS Secret Key&lt;/li&gt;
&lt;li&gt;Default Region (ex: ap-northeast-2)&lt;/li&gt;
&lt;li&gt;Output format (json 권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 계정 ID가 나오면 설정 완료.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ECS Service Linked Role 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS가 내부적으로 ALB, Target Group 등을 연결하기 위해 필요한 역할이다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 있다면 에러가 나지만 무시해도 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker Desktop 실행 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECR 로그인 및 이미지 푸시 전 반드시 확인해야 한다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;Cannot connect to the Docker daemon
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 나오면 Docker Desktop이 실행되지 않은 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac(M1/M2)의 경우 반드시 실행 후 다시 시도해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;빌드 시 주의할 점!!!!!!!!&amp;nbsp;&lt;br /&gt;OS 에 따라 안돌아갈수도 있기 때문에,&amp;nbsp;&lt;br /&gt;
&lt;pre id=&quot;code_1770914943032&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker buildx build \
  --platform linux/amd64 \
  -t ${ECR_URI}:latest \
  --push \
  .​&lt;/code&gt;&lt;/pre&gt;
나는 위의 방식으로 빌드를 진행했음!&amp;nbsp;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ECR 로그인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 이미지를 AWS ECR에 업로드하기 위해 로그인한다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin &amp;lt;ACCOUNT_ID&amp;gt;.dkr.ecr.ap-northeast-2.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인이 성공하면&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Login Succeeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 출력된다.&lt;/p&gt;
&lt;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;Route53 사용할 거임&lt;/li&gt;
&lt;li&gt;ALB Alias 레코드 연결할 거&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;(도메인 등록은 aws 에서 프리티어 계정에는 지원을 안하는걸로 알고있기 때문에 참고하여 설정하시길!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중심으로 본거는 네트워크와 보안 쪽이다. (잘 올려도 여기서 연결이 잘 안되면 호스팅 자체가 안됨.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보안 &amp;amp; 네트워크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 우선 default VPC 를 선택했다. (단순 포폴용이기 때문에)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 보안그룹은 따로 나눠놔야된다. (안그러면 ECS 용과 RDS 가 섞여버림.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 설정해둔 VPC 에 총 3개의 보안그룹을 생성해둔다.&lt;/p&gt;
&lt;h3 data-end=&quot;486&quot; data-start=&quot;454&quot; data-ke-size=&quot;size23&quot;&gt;SG-ALB (예: sg-alb-(프로젝트명))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALB&amp;nbsp;public&amp;nbsp;inbound&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;546&quot; data-start=&quot;487&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;530&quot; data-start=&quot;487&quot;&gt;Inbound: &lt;b&gt;80&lt;/b&gt;, &lt;b&gt;443&lt;/b&gt; from 0.0.0.0/0&lt;/li&gt;
&lt;li data-end=&quot;546&quot; data-start=&quot;531&quot;&gt;Outbound: all&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;206&quot; data-start=&quot;187&quot; data-ke-size=&quot;size23&quot;&gt;Inbound Rules&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;308&quot; data-start=&quot;208&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;308&quot; data-start=&quot;266&quot;&gt;
&lt;tr data-end=&quot;308&quot; data-start=&quot;266&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;274&quot; data-start=&quot;266&quot;&gt;HTTPS&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;280&quot; data-start=&quot;274&quot;&gt;443&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;292&quot; data-start=&quot;280&quot;&gt;0.0.0.0/0&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;308&quot; data-start=&quot;292&quot;&gt;외부 사용자 접속 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;363&quot; data-start=&quot;310&quot; data-ke-size=&quot;size16&quot;&gt;※ HTTP(80)는 HTTPS 리다이렉트용이면 80 추가 가능&lt;br /&gt;※ 그 외 모든 포트는 차단&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-13 오전 4.59.33.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyRHYD/dJMcabXqp9h/cpV6aOKCFrur9bBrFZo1Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyRHYD/dJMcabXqp9h/cpV6aOKCFrur9bBrFZo1Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyRHYD/dJMcabXqp9h/cpV6aOKCFrur9bBrFZo1Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyRHYD%2FdJMcabXqp9h%2FcpV6aOKCFrur9bBrFZo1Yk%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;1078&quot; height=&quot;132&quot; data-filename=&quot;스크린샷 2026-02-13 오전 4.59.33.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;385&quot; data-start=&quot;365&quot; data-ke-size=&quot;size23&quot;&gt;Outbound Rules (ALB &amp;rarr; ECS 통신 허용)&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;466&quot; data-start=&quot;387&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;466&quot; data-start=&quot;433&quot;&gt;
&lt;tr data-end=&quot;466&quot; data-start=&quot;433&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;447&quot; data-start=&quot;433&quot;&gt;All traffic&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;453&quot; data-start=&quot;447&quot;&gt;All&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;466&quot; data-start=&quot;453&quot;&gt;0.0.0.0/0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-13 오전 4.59.44.png&quot; data-origin-width=&quot;1073&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMXa7a/dJMcaac97MI/bzWUlTNK9f7G1LyDzj1ekk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMXa7a/dJMcaac97MI/bzWUlTNK9f7G1LyDzj1ekk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMXa7a/dJMcaac97MI/bzWUlTNK9f7G1LyDzj1ekk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMXa7a%2FdJMcaac97MI%2FbzWUlTNK9f7G1LyDzj1ekk%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;1073&quot; height=&quot;159&quot; data-filename=&quot;스크린샷 2026-02-13 오전 4.59.44.png&quot; data-origin-width=&quot;1073&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;580&quot; data-start=&quot;548&quot; data-ke-size=&quot;size23&quot;&gt;SG-ECS (예: sg-ecs-(프로젝트명))&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;662&quot; data-start=&quot;581&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;616&quot; data-start=&quot;581&quot;&gt;Inbound: &lt;b&gt;8089&lt;/b&gt; from &lt;b&gt;SG-ALB&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;662&quot; data-start=&quot;617&quot;&gt;Outbound: all (RDS 붙이면 5432는 아웃바운드에서 자동 OK)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;580&quot; data-start=&quot;561&quot; data-ke-size=&quot;size23&quot;&gt;Inbound Rules (ALB &amp;rarr; ECS)&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;686&quot; data-start=&quot;582&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;686&quot; data-start=&quot;640&quot;&gt;
&lt;tr data-end=&quot;686&quot; data-start=&quot;640&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;646&quot; data-start=&quot;640&quot;&gt;TCP&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;653&quot; data-start=&quot;646&quot;&gt;8089&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;670&quot; data-start=&quot;653&quot;&gt;위에서 만든 보안 그룹&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;686&quot; data-start=&quot;670&quot;&gt;ALB에서만 접근 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;720&quot; data-start=&quot;688&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;0.0.0.0/0 금지&lt;br /&gt;&amp;nbsp;직접 외부 접근 차단&lt;/p&gt;
&lt;h3 data-end=&quot;742&quot; data-start=&quot;722&quot; data-ke-size=&quot;size23&quot;&gt;Outbound Rules (ECS &amp;rarr; RDS 통신 허용)&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;823&quot; data-start=&quot;744&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;823&quot; data-start=&quot;790&quot;&gt;
&lt;tr data-end=&quot;823&quot; data-start=&quot;790&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;804&quot; data-start=&quot;790&quot;&gt;All traffic&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;810&quot; data-start=&quot;804&quot;&gt;All&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;823&quot; data-start=&quot;810&quot;&gt;0.0.0.0/0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tPELv/dJMcab4bNSQ/a2KxkDmYAmuueteJI9vhJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tPELv/dJMcab4bNSQ/a2KxkDmYAmuueteJI9vhJ0/img.png&quot; data-alt=&quot;여기서 선택한 소스가 sg-alb 보안그룹임&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tPELv/dJMcab4bNSQ/a2KxkDmYAmuueteJI9vhJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtPELv%2FdJMcab4bNSQ%2Fa2KxkDmYAmuueteJI9vhJ0%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;1025&quot; height=&quot;187&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;여기서 선택한 소스가 sg-alb 보안그룹임&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;696&quot; data-start=&quot;664&quot; data-ke-size=&quot;size23&quot;&gt;SG-RDS (예: sg-rds-(프로젝트명))&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;756&quot; data-start=&quot;697&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;732&quot; data-start=&quot;697&quot;&gt;Inbound: &lt;b&gt;5432&lt;/b&gt; from &lt;b&gt;SG-ECS&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;756&quot; data-start=&quot;733&quot;&gt;Public access: &lt;b&gt;No&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;913&quot; data-start=&quot;894&quot; data-ke-size=&quot;size23&quot;&gt;Inbound Rules&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1021&quot; data-start=&quot;915&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1021&quot; data-start=&quot;973&quot;&gt;
&lt;tr data-end=&quot;1021&quot; data-start=&quot;973&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;986&quot; data-start=&quot;973&quot;&gt;PostgreSQL&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;993&quot; data-start=&quot;986&quot;&gt;5432&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1002&quot; data-start=&quot;993&quot;&gt;ECS SG&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1021&quot; data-start=&quot;1002&quot;&gt;ECS에서만 DB 접근 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1081&quot; data-start=&quot;1023&quot; data-ke-size=&quot;size16&quot;&gt;VPC 전체 허용 금지&lt;br /&gt;default SG 허용 금지&lt;br /&gt;0.0.0.0/0 절대 금지&lt;/p&gt;
&lt;h3 data-end=&quot;1103&quot; data-start=&quot;1083&quot; data-ke-size=&quot;size23&quot;&gt;Outbound Rules&lt;/h3&gt;
&lt;p data-end=&quot;1128&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;기본값 유지 (All traffic 허용)&lt;/p&gt;
&lt;p data-end=&quot;1128&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770926664680&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Internet
   &amp;darr; 443
ALB (Public Subnet)
   &amp;darr; 8089 (SG로 제한)
ECS Fargate (Private)
   &amp;darr; 5432 (SG로 제한)
RDS (Private)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1128&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ACM 인증서 발급 &amp;amp; Route53 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 보안그룹설정이 마쳤다면, 편하게 도메인 연결을 하기 위해서 Route 설정과 ACM 인증서를 먼저 발급해둔다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기서 만들어둔 인증서는 나중에 loadbalancer 에 붙음. 인증서 발급을 요청하면 도메인의 CNAME 이 나오는데 이걸 Route53에 붙여줘야 된다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로 해야될것은 route 53의 host zone 생성이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생성 시에 ns 레코드 4개의 네임서버가 나오는데,&amp;nbsp; 나는 DNS 관리를 가비아에 들어가서 매일 하기 귀찮을 듯 해 네임서버를 가비아에 등록해서, DNS 관리를 route 53에서 진행할 수 있게 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHD38v/dJMcaaqGWiu/mlqli8mXfecSGyfzsPzcDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHD38v/dJMcaaqGWiu/mlqli8mXfecSGyfzsPzcDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHD38v/dJMcaaqGWiu/mlqli8mXfecSGyfzsPzcDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHD38v%2FdJMcaaqGWiu%2Fmlqli8mXfecSGyfzsPzcDK%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;1118&quot; height=&quot;336&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 설정을 해두면 이제 route 53 에서 DNS 관리를 할 수 있음!&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두번째로 해야될 설정은 HTTPS 인증서 발급이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ACM (Certificate Manager) 에 접속 (설정 시 나는 ALB나 전체 서비스의 리전을 단일로 했음. 서울로 선택!)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인증서 요청할 때의 주의사항은 *.~~.~~ 는 빼야된다.. (비용 폭탄됨)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인증서 요청 후에는 CName 레코드가 나오는데 이제 그걸로 Route 53 의 도메인에 레코드로 등록해주면 되고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기다리다보면 인증서의 상태가 변경된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;ECS&amp;nbsp;+&amp;nbsp;ALB+RDS&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적인 세팅을 마쳤기 때문에 이제 전체 설정을 해줘야 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(보안그룹 선택과 네트워크는 위에서 설정해뒀던걸 잘 기억하며 선택하면 됨.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 설정한 aws cli 를 통해, ecr에 이미지를 배포해야 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 ECR 에 들어가서 실제로 존재하는지 확인을 한다. (배포과정에 대한 설명은 생략,, gpt가 잘 알려줌 ㅜ)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이미지가 무사히 올라가있다면 ecs 를 하기 전에 RDS 먼저 설정을 해보려 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(ecs 먼저하게 되면, 배포 중 rds 연결 에러가 뜨기 때문에... )&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RDS 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 일단 RDS 연결 먼저 해두려고 한다. (ECS 올릴 때 에러 피하기 위함임..미리 안해두면 재빌드가 되기 때문에 ㅜ)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;1414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qr9iw/dJMcaaYvUGC/tEfj57dkAQiP2aXW05Iaj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qr9iw/dJMcaaYvUGC/tEfj57dkAQiP2aXW05Iaj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qr9iw/dJMcaaYvUGC/tEfj57dkAQiP2aXW05Iaj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqr9iw%2FdJMcaaYvUGC%2FtEfj57dkAQiP2aXW05Iaj1%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;1818&quot; height=&quot;1414&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;1414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택은 자유지만, 나는 포폴용으로 올리는 예시기 때문에 비용이 최저로 나올 수 있게 올렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마스터 사용자 이름이나 암호는 Spring의 (백엔드서버) 프로포티에 입력해둔 값들로 적으면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 같은 경우는 default VPC 보다는 따로 프로젝트용 설정을 권장하는데 굳이 여러개의 프로젝트를 올릴게 아니라면, 기본을 사용해도 된다. (하지만 보안 그룹은 분리해야함.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼블릭 액세스 같은 경우는 인터넷에 직접 노출할지 말지를 결정하는 용도인데, 개발용에 외부 통신이 필요하다면 오픈해도 되지만, 나는 내부 통신으로만 열어뒀다. 만들고 나서 나오는 RDS 의 엔드포인트로 spring에서 접근하기 떄문에 저장해놔야 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;ECR에 Spring 이미지 푸시하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDS 를 마쳤다면, 이제 docker build 를 해야할 차례!&amp;nbsp;&lt;br /&gt;Spring boot 던 python이던 환경변수 파일 설정은 중요하다. 우선 Spring 기준으로 해둔 설정들만 공유해보면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env.prod 모드에는 RDS 정보값 입력, cors 다시한번 확인하기!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-13 오전 4.34.07.png&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G018t/dJMcaf6A285/V7dGHGKEVAxH6Vzu40yj5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G018t/dJMcaf6A285/V7dGHGKEVAxH6Vzu40yj5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G018t/dJMcaf6A285/V7dGHGKEVAxH6Vzu40yj5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG018t%2FdJMcaf6A285%2FV7dGHGKEVAxH6Vzu40yj5k%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;632&quot; height=&quot;325&quot; data-filename=&quot;스크린샷 2026-02-13 오전 4.34.07.png&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;ECS 로 클러스터 만들기&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;클러스터를 만들 때 주의할 점은 health check 부분,,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;태스크를 만들게 되면 loadbalancer 설정을 하게 되는데 이때 리스너 규칙에 설정하는 헬스체크의 시간대를 잘 지정해둬야 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;971&quot; data-start=&quot;930&quot; data-ke-size=&quot;size23&quot;&gt;Health Check Interval &amp;amp; Threshold&lt;/h3&gt;
&lt;p data-end=&quot;1044&quot; data-start=&quot;973&quot; data-ke-size=&quot;size16&quot;&gt;기본값으로 두면 배포 직후 바로 Unhealthy로 빠지는 경우가 있다.&lt;br /&gt;Spring Boot는 기동 시간이 있기 때문에,&lt;br /&gt;기본 Health Check 설정으로 두면 애플리케이션이 완전히 올라오기 전에 ALB가 Unhealthy로 판단해버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 다음과 같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 직후 태스크가 반복적으로 재시작됨&lt;/li&gt;
&lt;li&gt;Rolling Update가 제대로 동작하지 않음&lt;/li&gt;
&lt;li&gt;서비스가 순간적으로 0개 태스크 상태가 됨&lt;/li&gt;
&lt;li&gt;무중단 배포 실패&lt;/li&gt;
&lt;/ul&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;Interval: 30초&lt;/li&gt;
&lt;li&gt;Timeout: 5초&lt;/li&gt;
&lt;li&gt;Healthy threshold: 2&lt;/li&gt;
&lt;li&gt;Unhealthy threshold: 5&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헬스체크는 단순히 &amp;ldquo;살아있나?&amp;rdquo;를 확인하는 기능이 아니라,&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;장애 감지의 1차 필터&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;ECS + ALB 구조에서는&lt;br /&gt;헬스체크 설계가 곧 운영 안정성 설계라고 봐도 무방하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 아키텍처 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Internet&lt;br /&gt;&amp;darr; 443&lt;br /&gt;ALB (Public Subnet)&lt;br /&gt;&amp;darr; 8089 (SG 제한 + Health Check)&lt;br /&gt;ECS Fargate (Private)&lt;br /&gt;&amp;darr; 5432 (SG 제한)&lt;br /&gt;RDS (Private)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 직접 접근 차단&lt;/li&gt;
&lt;li&gt;내부 통신만 허용&lt;/li&gt;
&lt;li&gt;HTTPS 강제&lt;/li&gt;
&lt;li&gt;DB 비공개&lt;/li&gt;
&lt;li&gt;헬스체크 기반 트래픽 제어&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/AWS</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/240</guid>
      <comments>https://ye-seul0-0.tistory.com/240#entry240comment</comments>
      <pubDate>Tue, 10 Feb 2026 10:26:00 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] egress 비용 폭증 원인 분석 -&amp;gt; 아키텍처 재설계 (Product : SpeakNote)</title>
      <link>https://ye-seul0-0.tistory.com/204</link>
      <description>&lt;h2 data-end=&quot;224&quot; data-start=&quot;216&quot; data-ke-size=&quot;size26&quot;&gt;문제 배경&lt;/h2&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;226&quot; data-ke-size=&quot;size16&quot;&gt;SpeakNote를 GCP 환경에서 운영하던 중,&lt;br /&gt;GCP Billing 리포트에서 &lt;b&gt;Network egress 비용이 비정상적으로 급증&lt;/b&gt;한 것을 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;393&quot; data-start=&quot;318&quot; data-ke-size=&quot;size16&quot;&gt;트래픽이 갑자기 폭증한 것도 아니고,&lt;br /&gt;기능을 대규모로 추가한 시점도 아니었기 때문에 단순 사용량 증가로 보기에는 이상한 상황이었다.&lt;/p&gt;
&lt;p data-end=&quot;452&quot; data-start=&quot;395&quot; data-ke-size=&quot;size16&quot;&gt;조사를 진행하면서 egress 비용 증가의 원인은 &lt;b&gt;하나가 아니라 여러 개&lt;/b&gt;라는 것을 알게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;537&quot; data-start=&quot;454&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;488&quot; data-start=&quot;454&quot;&gt;Docker 이미지 빌드 시 캐시 없이 반복 빌드되던 문제&lt;/li&gt;
&lt;li data-end=&quot;506&quot; data-start=&quot;489&quot;&gt;외부 AI API 연동 구조&lt;/li&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;507&quot;&gt;그리고 &lt;b&gt;GCP 네트워크 아키텍처 자체의 문제&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;625&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는&lt;br /&gt;&lt;b&gt;아직 해결 중인 빌드/코드 레벨 이슈는 제외하고&lt;/b&gt;, &lt;b&gt;GCP 아키텍처 재배포로 실제 비용을 줄인 과정&lt;/b&gt;만 정리한다.&lt;/p&gt;
&lt;p data-end=&quot;625&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;625&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;672&quot; data-start=&quot;632&quot; data-ke-size=&quot;size26&quot;&gt;초기 상황: &amp;ldquo;같은 서버끼리 통신하는데 왜 egress가 나가지?&amp;rdquo;&lt;/h2&gt;
&lt;p data-end=&quot;706&quot; data-start=&quot;674&quot; data-ke-size=&quot;size16&quot;&gt;SpeakNote는 다음과 같은 구성으로 배포되어 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;838&quot; data-start=&quot;708&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;727&quot; data-start=&quot;708&quot;&gt;GCP Load Balancer&lt;/li&gt;
&lt;li data-end=&quot;740&quot; data-start=&quot;728&quot;&gt;단일 VM 인스턴스&lt;/li&gt;
&lt;li data-end=&quot;838&quot; data-start=&quot;741&quot;&gt;VM 내부에서 Docker Compose로
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;838&quot; data-start=&quot;771&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;783&quot; data-start=&quot;771&quot;&gt;Frontend&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;786&quot;&gt;Spring Boot&lt;/li&gt;
&lt;li data-end=&quot;815&quot; data-start=&quot;804&quot;&gt;FastAPI&lt;/li&gt;
&lt;li data-end=&quot;838&quot; data-start=&quot;818&quot;&gt;Redis / DB 컨테이너 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;884&quot; data-start=&quot;840&quot; data-ke-size=&quot;size16&quot;&gt;겉보기에는 단순한 구조였지만,&lt;br /&gt;문제는 &lt;b&gt;네트워크 트래픽 경로&lt;/b&gt;에 있었다.&lt;/p&gt;
&lt;p data-end=&quot;956&quot; data-start=&quot;886&quot; data-ke-size=&quot;size16&quot;&gt;서비스 간 통신이 모두 &lt;b&gt;Load Balancer를 기준으로 구성&lt;/b&gt;되어 있었고,&lt;br /&gt;이로 인해 다음과 같은 일이 발생했다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1017&quot; data-start=&quot;958&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1017&quot; data-start=&quot;960&quot; data-ke-size=&quot;size16&quot;&gt;같은 VM 내부 컨테이너 간 통신임에도&lt;br /&gt;트래픽이 외부 &amp;rarr; LB &amp;rarr; 다시 VM 으로 돌아오는 구조&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1087&quot; data-start=&quot;1019&quot; data-ke-size=&quot;size16&quot;&gt;즉, 논리적으로는 내부 통신이지만&lt;br /&gt;&lt;b&gt;GCP 입장에서는 egress + ingress 트래픽으로 계산&lt;/b&gt;되는 구조였다.&lt;/p&gt;
&lt;p data-end=&quot;1087&quot; data-start=&quot;1019&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1128&quot; data-start=&quot;1094&quot; data-ke-size=&quot;size26&quot;&gt;원인 정리: egress는 &amp;ldquo;트래픽 양&amp;rdquo; 문제?&lt;/h2&gt;
&lt;p data-end=&quot;1158&quot; data-start=&quot;1130&quot; data-ke-size=&quot;size16&quot;&gt;이슈를 분석하면서 가장 크게 느낀 점은 이것이었다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1228&quot; data-start=&quot;1160&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1228&quot; data-start=&quot;1162&quot; data-ke-size=&quot;size16&quot;&gt;egress 비용은&lt;br /&gt;&amp;ldquo;데이터를 많이 보내서&amp;rdquo; 생기는 게 아니라&lt;br /&gt;&lt;b&gt;&amp;ldquo;어디로 보내느냐&amp;rdquo;에 따라 발생한다&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1238&quot; data-start=&quot;1230&quot; data-ke-size=&quot;size16&quot;&gt;기존 구조에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1280&quot; data-start=&quot;1240&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1251&quot; data-start=&quot;1240&quot;&gt;내부 서비스 호출&lt;/li&gt;
&lt;li data-end=&quot;1258&quot; data-start=&quot;1252&quot;&gt;헬스체크&lt;/li&gt;
&lt;li data-end=&quot;1280&quot; data-start=&quot;1259&quot;&gt;WebSocket 연결 유지 트래픽&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1352&quot; data-start=&quot;1282&quot; data-ke-size=&quot;size16&quot;&gt;같은 것들조차&lt;br /&gt;외부 네트워크 경로를 타고 다시 들어오는 구조였고,&lt;br /&gt;이 트래픽들이 &lt;b&gt;모두 비용으로 누적&lt;/b&gt;되고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;1352&quot; data-start=&quot;1282&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1387&quot; data-start=&quot;1359&quot; data-ke-size=&quot;size26&quot;&gt;해결 전략: 아키텍처 구조 수정&lt;/h2&gt;
&lt;p data-end=&quot;1419&quot; data-start=&quot;1389&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 코드 최적화만으로는 해결할 수 없다고 판단했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1477&quot; data-start=&quot;1421&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1443&quot; data-start=&quot;1421&quot;&gt;요청 수를 줄여도 근본 원인은 그대로&lt;/li&gt;
&lt;li data-end=&quot;1477&quot; data-start=&quot;1444&quot;&gt;API 호출을 줄여도 내부 통신 egress는 계속 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1506&quot; data-start=&quot;1479&quot; data-ke-size=&quot;size16&quot;&gt;그래서 선택한 방법은 &lt;b&gt;아키텍처 재배포&lt;/b&gt;였다.&lt;/p&gt;
&lt;h3 data-end=&quot;1523&quot; data-start=&quot;1508&quot; data-ke-size=&quot;size23&quot;&gt;핵심 전략은 단순했다&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1686&quot; data-start=&quot;1525&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1563&quot; data-start=&quot;1525&quot;&gt;&lt;b&gt;내부 통신은 무조건 internal/private 경로로&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1602&quot; data-start=&quot;1564&quot;&gt;외부 노출이 필요한 엔드포인트만 Load Balancer에 연결&lt;/li&gt;
&lt;li data-end=&quot;1686&quot; data-start=&quot;1603&quot;&gt;VM 내부 서비스 간 호출은
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1686&quot; data-start=&quot;1627&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1640&quot; data-start=&quot;1627&quot;&gt;localhost&lt;/li&gt;
&lt;li data-end=&quot;1662&quot; data-start=&quot;1644&quot;&gt;docker network&lt;/li&gt;
&lt;li data-end=&quot;1686&quot; data-start=&quot;1666&quot;&gt;private IP 기준으로 통일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1690&quot; data-start=&quot;1688&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;blockquote data-end=&quot;1734&quot; data-start=&quot;1692&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1734&quot; data-start=&quot;1694&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;외부 공개용 트래픽&amp;rdquo;과&lt;br /&gt;&amp;ldquo;내부 서비스 간 트래픽&amp;rdquo;을 명확히 분리&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 전환점: Global Load Balancer &amp;rarr; Regional Load Balancer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;egress 비용을 본격적으로 분석하면서,&lt;br /&gt;가장 먼저 의심하게 된 지점은 &lt;b&gt;Load Balancer의 스코프&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 SpeakNote는 &lt;b&gt;Global HTTP(S) Load Balancer&lt;/b&gt;를 사용하고 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 구성의 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Global Load Balancer는 이름 그대로 &lt;b&gt;전 세계 엣지(Edge)를 기준&lt;/b&gt;으로 동작한다.&lt;br /&gt;이 자체는 글로벌 서비스를 운영할 때는 매우 강력한 장점이지만,&lt;br /&gt;현재 SpeakNote의 상황에는 &lt;b&gt;과한 선택&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구조에서는 다음과 같은 문제가 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트래픽이 &lt;b&gt;글로벌 엣지 &amp;rarr; 리전 &amp;rarr; VM&lt;/b&gt; 경로를 탑&lt;/li&gt;
&lt;li&gt;같은 리전에 있는 서비스 간 통신임에도&lt;br /&gt;&lt;b&gt;cross-region / global routing&lt;/b&gt;으로 인식될 여지&lt;/li&gt;
&lt;li&gt;결과적으로 내부 통신 성격의 요청까지&lt;br /&gt;&lt;b&gt;egress 비용으로 잡히는 구조&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;글로벌 확장을 고려한 인프라&amp;rdquo;가&lt;br /&gt;&amp;ldquo;단일 리전 서비스&amp;rdquo;에는 오히려 비용 리스크가 된 상황&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변경 결정: Global LB를 버리고 Regional LB로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote의 실제 사용 시나리오는 다음과 같았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 리전(GCP 한 리전)에서만 서비스 운영&lt;/li&gt;
&lt;li&gt;글로벌 트래픽 분산, Anycast 라우팅 필요 없음&lt;/li&gt;
&lt;li&gt;지연(latency)보다 &lt;b&gt;비용 안정성&lt;/b&gt;이 더 중요한 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조 변화 한눈에 보기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Before (Global LB 기반)&lt;/h3&gt;
&lt;p data-end=&quot;675&quot; data-start=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;895&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nlWtn/dJMcaivngOx/svXUvovkMyBAa2LxhRDUu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nlWtn/dJMcaivngOx/svXUvovkMyBAa2LxhRDUu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nlWtn/dJMcaivngOx/svXUvovkMyBAa2LxhRDUu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnlWtn%2FdJMcaivngOx%2FsvXUvovkMyBAa2LxhRDUu1%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;1636&quot; height=&quot;895&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;895&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단을 보면 Asia / America / Europe 등&lt;br /&gt;&lt;b&gt;글로벌 엣지(Edge) 위치에서 트래픽이 유입&lt;/b&gt;되고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;530&quot; data-end=&quot;620&quot;&gt;
&lt;li data-start=&quot;530&quot; data-end=&quot;555&quot;&gt;실제 사용자는 대부분 Asia 리전에 있음&lt;/li&gt;
&lt;li data-start=&quot;556&quot; data-end=&quot;620&quot;&gt;그럼에도 Global LB 특성상
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;579&quot; data-end=&quot;620&quot;&gt;
&lt;li data-start=&quot;579&quot; data-end=&quot;597&quot;&gt;글로벌 엣지를 기준으로 라우팅&lt;/li&gt;
&lt;li data-start=&quot;600&quot; data-end=&quot;620&quot;&gt;리전 간 개념이 개입될 여지 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;622&quot; data-end=&quot;675&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;br /&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;Client &amp;rarr; Global Edge&lt;/li&gt;
&lt;li&gt;Edge &amp;rarr; Region &amp;rarr; VM&lt;/li&gt;
&lt;li&gt;내부 통신도 LB 경유&lt;/li&gt;
&lt;li&gt;egress 발생 지점이 불명확&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;After (Regional LB 기반)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client &amp;rarr; Regional LB &amp;rarr; VM&lt;/li&gt;
&lt;li&gt;내부 서비스 간 통신은 private 경로&lt;/li&gt;
&lt;li&gt;외부/내부 트래픽 역할 분리&lt;/li&gt;
&lt;li&gt;egress 발생 지점 명확&lt;/li&gt;
&lt;/ul&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;Global LB는 &lt;b&gt;항상 좋은 선택은 아니다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;서비스 규모&amp;middot;운영 단계에 맞는 인프라 선택이 중요&lt;/li&gt;
&lt;li&gt;GCP egress 비용은&lt;br /&gt;트래픽 양보다 &lt;b&gt;라우팅 구조의 문제&lt;/b&gt;인 경우가 많다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote에서는&lt;br /&gt;&lt;b&gt;Global &amp;rarr; Regional LB 전환&lt;/b&gt;이&lt;br /&gt;egress 비용 문제를 해결할 가장 큰 분기점..이었음 좋겠다...&lt;/p&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1912&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/GCP</category>
      <category>Egress</category>
      <category>gloabalLB</category>
      <category>regionalLB</category>
      <category>트러블슈팅</category>
      <category>프로젝트기록-speaknote</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/204</guid>
      <comments>https://ye-seul0-0.tistory.com/204#entry204comment</comments>
      <pubDate>Sat, 31 Jan 2026 04:59:35 +0900</pubDate>
    </item>
    <item>
      <title>[GCP - egress] GCP 요금 폭증 &amp;ndash; Egress 트래픽 추적기(Product : SpeakNote)</title>
      <link>https://ye-seul0-0.tistory.com/203</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote 프로젝트의 고도화 기간 중, 기존에는 로컬로만 테스트를 돌렸었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 운영 환경에서의 테스트도 중요할 거 같아 배포를 미리 해두기로 마음을 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 1월 24일경 시작을 했는데 크레딧의 반절을 써버림.. (뭔가 단단히 잘못됨..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번도 이랬던 적이 없어서 우선 인스턴스를 중지 시킴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(고정 ip 와 디스크로 몇천원 결제되는게 더 나을 듯해서...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.54.03.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ReI6/dJMcagdhP17/iKakrNKk9CLXTqGbS3izw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ReI6/dJMcagdhP17/iKakrNKk9CLXTqGbS3izw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ReI6/dJMcagdhP17/iKakrNKk9CLXTqGbS3izw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ReI6%2FdJMcagdhP17%2FiKakrNKk9CLXTqGbS3izw1%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;746&quot; height=&quot;128&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.54.03.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 요금이 폭등하게 된 원인부터 찾으려 함.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.57.39.png&quot; data-origin-width=&quot;2396&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRlpB1/dJMcafSY6xb/tw7dXB7BOMWUguAOVqCk4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRlpB1/dJMcafSY6xb/tw7dXB7BOMWUguAOVqCk4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRlpB1/dJMcafSY6xb/tw7dXB7BOMWUguAOVqCk4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRlpB1%2FdJMcafSY6xb%2Ftw7dXB7BOMWUguAOVqCk4K%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;2396&quot; height=&quot;786&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.57.39.png&quot; data-origin-width=&quot;2396&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google cloud 의 billing -&amp;gt; 보고서를 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오잉??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹화 기준을 날짜 &amp;gt; SKU 로 변경 (1월 29일날 인스턴스 중지를 시켰습니다. 나온 974는 아마도 고정 ip와 디스크 값이겠죵?)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGfPY6/dJMcaf6vHYn/MW9Vs02OCHmTVFm7fO3Ygk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGfPY6/dJMcaf6vHYn/MW9Vs02OCHmTVFm7fO3Ygk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;994&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.01.00.png&quot; data-widthpercent=&quot;22.53&quot; style=&quot;width: 22.2668%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGfPY6/dJMcaf6vHYn/MW9Vs02OCHmTVFm7fO3Ygk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGfPY6%2FdJMcaf6vHYn%2FMW9Vs02OCHmTVFm7fO3Ygk%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;1830&quot; height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.59.00.png&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6mIYC/dJMcabC3iqN/3rsvK7eWprfUblVDIkgAq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6mIYC/dJMcabC3iqN/3rsvK7eWprfUblVDIkgAq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6mIYC/dJMcabC3iqN/3rsvK7eWprfUblVDIkgAq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6mIYC%2FdJMcabC3iqN%2F3rsvK7eWprfUblVDIkgAq0%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;1740&quot; height=&quot;340&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.59.00.png&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.58.13.png&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tSsv3/dJMb996iQ9n/47vZ1eGsTN4eHbOVivtKM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tSsv3/dJMb996iQ9n/47vZ1eGsTN4eHbOVivtKM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tSsv3/dJMb996iQ9n/47vZ1eGsTN4eHbOVivtKM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtSsv3%2FdJMb996iQ9n%2F47vZ1eGsTN4eHbOVivtKM1%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;1760&quot; height=&quot;278&quot; data-filename=&quot;스크린샷 2026-01-30 오후 9.58.13.png&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔지 정확히 확인하기 위해&amp;nbsp; csv 다운로드를 클릭합니다. (27일만 많이 나온게 아니라 28일도 많이 나온것으로 확인이 됨.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.03.21.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed6tXA/dJMcagK8okF/O1miGD1ugWKlfo0mAVok51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed6tXA/dJMcagK8okF/O1miGD1ugWKlfo0mAVok51/img.png&quot; data-alt=&quot;1월 28일자 원인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed6tXA/dJMcagK8okF/O1miGD1ugWKlfo0mAVok51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fed6tXA%2FdJMcagK8okF%2FO1miGD1ugWKlfo0mAVok51%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;846&quot; height=&quot;144&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.03.21.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1월 28일자 원인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.03.48.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1tG2h/dJMcadANvQt/35u2EDQKZ34CMCzdD1cWOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1tG2h/dJMcadANvQt/35u2EDQKZ34CMCzdD1cWOK/img.png&quot; data-alt=&quot;1월 27일자 원인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1tG2h/dJMcadANvQt/35u2EDQKZ34CMCzdD1cWOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1tG2h%2FdJMcadANvQt%2F35u2EDQKZ34CMCzdD1cWOK%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;914&quot; height=&quot;243&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.03.48.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1월 27일자 원인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vm의 스펙에 따른 가격은 대충 예상한대로 나온거라 큰 의미는 없지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비용의 대부분 = Compute Engine Network Egress&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 문제인 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metrics Explorer 에 들어가서 리소스 선택을 VM instance 에 두고, Egress bytes 를 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.12.57.png&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDwKie/dJMcadnhUpv/BqLbtmT5pbd4m5PxCy9w1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDwKie/dJMcadnhUpv/BqLbtmT5pbd4m5PxCy9w1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDwKie/dJMcadnhUpv/BqLbtmT5pbd4m5PxCy9w1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDwKie%2FdJMcadnhUpv%2FBqLbtmT5pbd4m5PxCy9w1K%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;1956&quot; height=&quot;1046&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.12.57.png&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.12.03.png&quot; data-origin-width=&quot;2494&quot; data-origin-height=&quot;1288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgUocH/dJMcahiYDNx/VjzWiwF9KVpNBRXGUMqBqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgUocH/dJMcahiYDNx/VjzWiwF9KVpNBRXGUMqBqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgUocH/dJMcahiYDNx/VjzWiwF9KVpNBRXGUMqBqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgUocH%2FdJMcahiYDNx%2FVjzWiwF9KVpNBRXGUMqBqK%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;2494&quot; height=&quot;1288&quot; data-filename=&quot;스크린샷 2026-01-30 오후 10.12.03.png&quot; data-origin-width=&quot;2494&quot; data-origin-height=&quot;1288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 구성한 FastAPI 쪽 서버 (Upstage API 를 사용하고 있음) 의 문제일 거 같은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 가능성 있는게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Docker build (노 캐시)&amp;nbsp;&lt;br /&gt;2. Upstage Parser , 임베딩 api 연결&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 부분이 문제일 거 같음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 수정은 다음 파트에서 다루겠음.&lt;/p&gt;</description>
      <category>DevOps/Monitoring</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/203</guid>
      <comments>https://ye-seul0-0.tistory.com/203#entry203comment</comments>
      <pubDate>Fri, 30 Jan 2026 23:01:25 +0900</pubDate>
    </item>
    <item>
      <title>[GCP - ssh] Compute Engine SSH 접속 실패 원인과 해결 &amp;ndash; 콘솔 SSH로 직접 고친 방법 (Product : SpeakNote)</title>
      <link>https://ye-seul0-0.tistory.com/192</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSH 키 등록했는데 로컬에서 접속이 안 됐던 문제 (해결까지 한 번에 정리)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compute Engine 인스턴스를 생성하면서&lt;br /&gt;초기 설정 화면에서 &lt;b&gt;SSH 공개키를 분명히 등록&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인스턴스 생성 이후, 로컬 터미널에서 다음과 같이 접속을 시도했을 때&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ssh user@&amp;lt;외부 IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 접속이 정상적으로 이루어지지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;VM 생성 단계에서 SSH 키를 넣었음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GCP 콘솔에서는 인스턴스가 정상적으로 떠 있음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그런데 로컬 터미널에서는 SSH 접속 실패&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 상황이었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;당시 문제 상황 정리&lt;/h3&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;VM에는 외부 IP가 정상적으로 할당되어 있었고&lt;/li&gt;
&lt;li&gt;방화벽에서도 22번 포트는 열려 있는 상태였으며&lt;/li&gt;
&lt;li&gt;SSH 키 자체도 로컬에 정상적으로 존재했습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 네트워크나 포트 문제라기보다는 &lt;b&gt;SSH 키 적용 방식 문제&lt;/b&gt;로 판단했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인으로 판단한 부분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP에서는 SSH 키를 등록하는 위치가 여러 군데 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VM 생성 시 입력하는 SSH 키&lt;/li&gt;
&lt;li&gt;프로젝트 메타데이터(Project-wide SSH keys)&lt;/li&gt;
&lt;li&gt;인스턴스 메타데이터(Instance-level SSH keys)&lt;/li&gt;
&lt;/ul&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;생성 단계에서 입력한 SSH 키가 실제 인스턴스에 제대로 반영되지 않았거나&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;프로젝트/인스턴스 메타데이터 적용 우선순위 문제로&lt;/li&gt;
&lt;li&gt;로컬 SSH 접속 시 인증이 실패한 것으로 보였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는&lt;br /&gt;&amp;ldquo;왜 안 되는지 끝까지 파고들기&amp;rdquo;보다&lt;br /&gt;&lt;b&gt;가장 확실한 방식으로 키를 다시 적용하는 것이 빠르다&lt;/b&gt;고 판단했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;GCP 콘솔에서 제공하는 브라우저 기반 SSH 터미널로 먼저 접속한 뒤&lt;/b&gt;,&lt;br /&gt;&lt;b&gt;인스턴스 내부에서 SSH 키를 직접 갱신하는 방식&lt;/b&gt;으로 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행한 순서는 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;GCP 콘솔 &amp;rarr; Compute Engine &amp;rarr; 해당 인스턴스 &amp;rarr; [SSH] 버튼 클릭&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;브라우저 기반 SSH 터미널로 인스턴스에 접속&lt;/li&gt;
&lt;li&gt;접속 후, 인스턴스 내부에서 아래와 같은 방식으로&lt;br /&gt;&lt;b&gt;로컬에서 사용 중인 공개키를 authorized_keys에 직접 추가&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;mkdir -p ~/.ssh
chmod 700 ~/.ssh

nano ~/.ssh/authorized_keys
&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;로컬에서 사용 중인 공개키(~/.ssh/id_ed25519.pub 등)의 내용을&lt;br /&gt;authorized_keys 파일에 그대로 붙여넣기&lt;/li&gt;
&lt;li&gt;저장 후 권한 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;chmod 600 ~/.ssh/authorized_keys
&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;브라우저 SSH 세션 종료 후, 로컬 터미널에서 다시 SSH 접속 시도&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ssh user@&amp;lt;외부 IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 거친 이후,&lt;br /&gt;&lt;b&gt;로컬 터미널에서 SSH 접속이 정&lt;b&gt;상적으로 이루어졌습니다.&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wSQku/dJMcafMeXgR/x2nKkmfpSKYcnQsW2uOHJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wSQku/dJMcafMeXgR/x2nKkmfpSKYcnQsW2uOHJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wSQku/dJMcafMeXgR/x2nKkmfpSKYcnQsW2uOHJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwSQku%2FdJMcafMeXgR%2Fx2nKkmfpSKYcnQsW2uOHJ1%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;1456&quot; height=&quot;186&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;VM 생성 단계에서 등록한 SSH 키가&lt;br /&gt;메타데이터/계정 생성 과정에서 정상 반영되지 않은 상태였고&lt;/li&gt;
&lt;li&gt;인스턴스 내부의 authorized_keys 파일에는&lt;br /&gt;실제로 유효한 키가 존재하지 않았던 상황이었습니다&lt;/li&gt;
&lt;/ul&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;GCP 메타데이터를 우회&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;인스턴스 내부 SSH 인증 파일을 직접 수정하여&lt;/li&gt;
&lt;li&gt;SSH 인증 경로를 명확하게 맞춘 것이 핵심이었습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;SSH 접속이 안 될 때&lt;br /&gt;&amp;ldquo;키를 어디에 넣었는지&amp;rdquo;보다&lt;br /&gt;&lt;b&gt;~/.ssh/authorized_keys에 실제로 키가 들어가 있는지&lt;/b&gt;가 더 중요합니다.&lt;/li&gt;
&lt;li&gt;브라우저 SSH는 되는데 로컬 SSH가 안 되면&lt;br /&gt;거의 대부분 이 파일 문제로 귀결됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;Compute Engine 생성 시 SSH 키를 넣었음에도&lt;/li&gt;
&lt;li&gt;로컬 SSH 접속이 되지 않는 경우가 발생할 수 있음&lt;/li&gt;
&lt;li&gt;이때는 복잡하게 원인을 모두 추적하기보다는
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인스턴스 수정 &amp;rarr; SSH 키 직접 등록&lt;/b&gt;&lt;br /&gt;방식이 가장 확실한 해결책이었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 접속 문제 없이 서버 설정, 포트별 서비스 실행, Load Balancer 설정을&lt;br /&gt;정상적으로 진행할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/GCP</category>
      <category>ssh키안됨</category>
      <category>트러블슈팅</category>
      <category>프로젝트기록-speaknote</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/192</guid>
      <comments>https://ye-seul0-0.tistory.com/192#entry192comment</comments>
      <pubDate>Wed, 28 Jan 2026 18:50:13 +0900</pubDate>
    </item>
    <item>
      <title>[GCP - global LB &amp;amp; compute Engine] GCP 서버 구성 기록 (default VPC + 전역 로드밸런서 기반)(Product : SpeakNote)</title>
      <link>https://ye-seul0-0.tistory.com/191</link>
      <description>&lt;h3 data-end=&quot;276&quot; data-start=&quot;231&quot; data-ke-size=&quot;size23&quot;&gt;Compute Engine 생성부터 전역 Load Balancer 연결까지&lt;/h3&gt;
&lt;p data-end=&quot;415&quot; data-start=&quot;278&quot; data-ke-size=&quot;size16&quot;&gt;본 글은 SpeakNote 서비스를 GCP 환경에 배포하면서&lt;br /&gt;&lt;b&gt;Compute Engine 생성 &amp;rarr; SSH 접속 &amp;rarr; 서비스 실행 &amp;rarr; Load Balancer 선택 및 설정 &amp;rarr; 도메인 연결&lt;/b&gt;&lt;br /&gt;까지의 과정을 &lt;b&gt;순서대로 기록&lt;/b&gt;한 글입니다.&lt;/p&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;417&quot; data-ke-size=&quot;size16&quot;&gt;설계 배경보다는 &lt;b&gt;실제로 어떤 설정을 했는지&lt;/b&gt;를 중심으로 정리합니다.&lt;/p&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;417&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;417&quot; data-ke-size=&quot;size16&quot;&gt;나중에 운영 서버는 AWS 에서 진행할 거지만,, (Upstage 와 제휴업체인걸로 알고있어서!)&lt;/p&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;417&quot; data-ke-size=&quot;size16&quot;&gt;AWS 는 현재 start up 신청 대기 중이기 때문에, 개발 단계는 GCP 로 진행을 하려 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;417&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;457&quot; data-start=&quot;417&quot; data-ke-size=&quot;size26&quot;&gt;GCP 계정 설정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-01 오후 11.52.31.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/omhLe/dJMcaivnbQK/TgnYopr4GhwGjBMbPW6Kqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/omhLe/dJMcaivnbQK/TgnYopr4GhwGjBMbPW6Kqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/omhLe/dJMcaivnbQK/TgnYopr4GhwGjBMbPW6Kqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FomhLe%2FdJMcaivnbQK%2FTgnYopr4GhwGjBMbPW6Kqk%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;1138&quot; height=&quot;876&quot; data-filename=&quot;스크린샷 2026-02-01 오후 11.52.31.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 가입하면 , First Project 라고 뜨는걸로 알고있는데, 우선 저는 프로젝트별 관리를 편안히 하기 위해 새프로젝트를 만들었습니다.&lt;/p&gt;
&lt;h2 data-end=&quot;493&quot; data-start=&quot;464&quot; data-ke-size=&quot;size26&quot;&gt;Compute Engine 인스턴스 생성&lt;/h2&gt;
&lt;p data-end=&quot;544&quot; data-start=&quot;495&quot; data-ke-size=&quot;size16&quot;&gt;먼저 GCP 콘솔에서 &lt;b&gt;Compute Engine 인스턴스(VM)&lt;/b&gt; 를 생성했습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;555&quot; data-start=&quot;546&quot; data-ke-size=&quot;size23&quot;&gt;기본 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QfymX/dJMcadt3sli/96W2cCeae06WNaTBsQDCek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QfymX/dJMcadt3sli/96W2cCeae06WNaTBsQDCek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QfymX/dJMcadt3sli/96W2cCeae06WNaTBsQDCek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQfymX%2FdJMcadt3sli%2F96W2cCeae06WNaTBsQDCek%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;2132&quot; height=&quot;1298&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;712&quot; data-start=&quot;556&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;586&quot; data-start=&quot;556&quot;&gt;&lt;b&gt;리전&lt;/b&gt;: asia-northeast3 (서울)&lt;/li&gt;
&lt;li data-end=&quot;630&quot; data-start=&quot;587&quot;&gt;&lt;b&gt;머신 유형&lt;/b&gt;: e2 계열 (CPU/메모리는 서비스 규모에 맞게 선택)&lt;/li&gt;
&lt;li data-end=&quot;690&quot; data-start=&quot;631&quot;&gt;&lt;b&gt;부팅 디스크&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;690&quot; data-start=&quot;646&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;668&quot; data-start=&quot;646&quot;&gt;OS: Ubuntu 22.04 LTS&lt;/li&gt;
&lt;li data-end=&quot;690&quot; data-start=&quot;671&quot;&gt;디스크 유형: 표준 영구 디스크&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;712&quot; data-start=&quot;691&quot;&gt;&lt;b&gt;외부 IP&lt;/b&gt;: 고정 IP 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;SpeakNote는 실시간 처리 요소(WebSocket, AI API 호출 등)가 포함되어 있어&lt;br /&gt;프리티어보다는 &lt;b&gt;안정적인 리소스 확보&lt;/b&gt;를 우선했습니다.&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;여기서 주의할점은&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;1. 네트워크 설정 &lt;br /&gt;&lt;br /&gt;만약에 네트워크를 처음부터 본인이 만들거라면,, default 가 아니라,&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;vpc 네트워크에서 한개 만들어준다음 설정하세요. (나중에 가서 수정 안됨.)&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;2. 부팅 디스크 설정&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;만약에 Spring 서버를 돌려야 돼서 , java 실행 명령어가 필요하면 Ubuntu 로 하시는게 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;3. 외부 IP 고정 할당&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;Lb 를 다실거면, 굳이 할 필요는 없습니다! (나중에 lb 설정 시 고정 ip 설정하는게 있음.) 하지만 , VM 자체에 접속해서 테스트를 자주해야된다! 하시면 설정하시는게 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;(저는 개발용이라 설정해놨습니다. 왜냐면 mysql , redis를 제 로컬에서 확인하기 좀더 수월히 하기 위해서, 그리고&amp;nbsp; 홉스카치를 통해 직접 요청해보려고요..)&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;4. 머신 유형 설정&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2080&quot; data-start=&quot;2052&quot; data-ke-size=&quot;size16&quot;&gt;머신 유형은 단순히 &amp;ldquo;CPU/메모리&amp;rdquo;만 보지 말고,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2130&quot; data-start=&quot;2081&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2096&quot; data-start=&quot;2081&quot;&gt;서버에 설치할 구성 요소&lt;/li&gt;
&lt;li data-end=&quot;2113&quot; data-start=&quot;2097&quot;&gt;Docker 컨테이너 개수&lt;/li&gt;
&lt;li data-end=&quot;2130&quot; data-start=&quot;2114&quot;&gt;로그, 캐시, 이미지 용량&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2170&quot; data-start=&quot;2132&quot; data-ke-size=&quot;size16&quot;&gt;까지 &lt;b&gt;전체 리소스 사용량을 고려해서 선택&lt;/b&gt;하시는 것이 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;2175&quot; data-start=&quot;2172&quot; data-ke-size=&quot;size16&quot;&gt;특히&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2241&quot; data-start=&quot;2176&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2214&quot; data-start=&quot;2176&quot;&gt;Spring + AI 서버 + 프론트엔드를 한 VM에서 돌릴 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리는 생각보다 빨리 부족해질 수 있습니다&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;846&quot; data-start=&quot;809&quot; data-ke-size=&quot;size26&quot;&gt;Compute Engine 접속을 위한 SSH 키 설정&lt;/h2&gt;
&lt;p data-end=&quot;887&quot; data-start=&quot;848&quot; data-ke-size=&quot;size16&quot;&gt;VM 생성 시 기본 설정 화면에서 &lt;b&gt;SSH 공개키를 등록&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;942&quot; data-start=&quot;889&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;904&quot; data-start=&quot;889&quot;&gt;로컬에서 SSH 키 생성&lt;/li&gt;
&lt;li data-end=&quot;942&quot; data-start=&quot;905&quot;&gt;생성한 &lt;b&gt;공개키를 VM 생성 화면의 SSH 키 항목에 등록&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;944&quot; data-ke-size=&quot;size16&quot;&gt;이론적으로는 이 설정만으로&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;ssh user@&amp;lt;외부 IP&amp;gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1007&quot; data-start=&quot;990&quot; data-ke-size=&quot;size16&quot;&gt;형태의 접속이 가능해야 합니다.&lt;/p&gt;
&lt;p data-end=&quot;1007&quot; data-start=&quot;990&quot; data-ke-size=&quot;size16&quot;&gt;(지금까지 이렇게 잘 해왔었는데... 이번엔 안되더라구요..? 왜인지 이유는 찾지 못했음.)&lt;/p&gt;
&lt;p data-end=&quot;1007&quot; data-start=&quot;990&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1026&quot; data-start=&quot;1009&quot; data-ke-size=&quot;size23&quot;&gt;실제로 겪었던 문제&lt;/h3&gt;
&lt;p data-end=&quot;1038&quot; data-start=&quot;1027&quot; data-ke-size=&quot;size16&quot;&gt;하지만 제 경우에는,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1109&quot; data-start=&quot;1039&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1066&quot; data-start=&quot;1039&quot;&gt;VM 생성 시 SSH 키를 분명히 등록했음에도&lt;/li&gt;
&lt;li data-end=&quot;1109&quot; data-start=&quot;1067&quot;&gt;로컬 터미널에서 SSH 접속이 정상적으로 되지 않는 문제가 발생했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1123&quot; data-start=&quot;1111&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이 단계에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1179&quot; data-start=&quot;1124&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1179&quot; data-start=&quot;1124&quot;&gt;&lt;b&gt;GCP 콘솔의 &amp;ldquo;인스턴스 수정 &amp;rarr; SSH 키 직접 수정&amp;rdquo; 방식&lt;/b&gt;으로 우회해서 해결했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1284&quot; data-start=&quot;1181&quot; data-ke-size=&quot;size16&quot;&gt;이 SSH 이슈와 해결 과정은 &lt;b&gt;별도의 게시물로 정리할 예정&lt;/b&gt;이라,&lt;br /&gt;본 글에서는 &amp;ldquo;이런 이슈가 있었고, 콘솔에서 SSH 키를 수정하는 방식으로 해결했다&amp;rdquo;는 기록만 남깁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E0YB5/dJMcagRUhe5/eMgvSyC1sAjEsN1QkHNBx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E0YB5/dJMcagRUhe5/eMgvSyC1sAjEsN1QkHNBx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E0YB5/dJMcagRUhe5/eMgvSyC1sAjEsN1QkHNBx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE0YB5%2FdJMcagRUhe5%2FeMgvSyC1sAjEsN1QkHNBx1%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;608&quot; height=&quot;119&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-01 오후 11.59.31.png&quot; data-origin-width=&quot;2020&quot; data-origin-height=&quot;1504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbh9I6/dJMcagdiBEg/8zN4xcFTyimTOeZsRkjUWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbh9I6/dJMcagdiBEg/8zN4xcFTyimTOeZsRkjUWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbh9I6/dJMcagdiBEg/8zN4xcFTyimTOeZsRkjUWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbh9I6%2FdJMcagdiBEg%2F8zN4xcFTyimTOeZsRkjUWK%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;2020&quot; height=&quot;1504&quot; data-filename=&quot;스크린샷 2026-02-01 오후 11.59.31.png&quot; data-origin-width=&quot;2020&quot; data-origin-height=&quot;1504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무사히 접속 성공...!!! VS 에 설정하는 방법도 다른 게시물에서 다루겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 접속이 가능해진 이후에는, VM 내부에서 각 서비스들을 포트별로 실행했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1451&quot; data-start=&quot;1372&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1397&quot; data-start=&quot;1372&quot;&gt;프론트엔드 (Next.js): 3000&lt;/li&gt;
&lt;li data-end=&quot;1425&quot; data-start=&quot;1398&quot;&gt;백엔드 (Spring Boot): 8080&lt;/li&gt;
&lt;li data-end=&quot;1451&quot; data-start=&quot;1426&quot;&gt;AI 서버 (FastAPI): 8000&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점!&amp;nbsp;&lt;br /&gt;&lt;br /&gt;1. Docker 올리기 전&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;도커 파일에 꼭꼭,, 캐시 활성화를 해두세요!!!(안그럼 요금 폭탄 맞음. 배포 할 때 마다)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. health api 세개를 각 서버별 만들어두세요 (next -&amp;gt; /health , fast-&amp;gt; /fastapi/health , spring -&amp;gt; /api/heath 로 해둠)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. cors 설정은 미리 바꿔놓던 .env 로 빼두세요.. 안그럼 다시 re build 해야 합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1474&quot; data-start=&quot;1453&quot; data-ke-size=&quot;size16&quot;&gt;이 단계에서는 서비스 실행 자체보다는,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1532&quot; data-start=&quot;1475&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1505&quot; data-start=&quot;1475&quot;&gt;&lt;b&gt;각 포트에서 정상적으로 LISTEN 상태인지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1532&quot; data-start=&quot;1506&quot;&gt;&lt;b&gt;컨테이너/프로세스가 정상 기동되었는지&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1552&quot; data-start=&quot;1534&quot; data-ke-size=&quot;size16&quot;&gt;를 확인하는 것이 목적이었습니다. (나중에 health check를 LB에서 진행을 하기 때문에 미리미리 올려놨습니다! )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-02 오전 12.01.18.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oxqeV/dJMcaaRHKT9/QsP1unXctkKzlcLteiHfA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oxqeV/dJMcaaRHKT9/QsP1unXctkKzlcLteiHfA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oxqeV/dJMcaaRHKT9/QsP1unXctkKzlcLteiHfA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoxqeV%2FdJMcaaRHKT9%2FQsP1unXctkKzlcLteiHfA0%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;1302&quot; height=&quot;294&quot; data-filename=&quot;스크린샷 2026-02-02 오전 12.01.18.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1671&quot; data-start=&quot;1644&quot; data-ke-size=&quot;size26&quot;&gt;localhost:3000 접속 확인&lt;/h2&gt;
&lt;p data-end=&quot;1704&quot; data-start=&quot;1673&quot; data-ke-size=&quot;size16&quot;&gt;서비스 실행 후, SSH 터널링 또는 VM 내부에서 직접&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;http:&lt;/span&gt;&lt;span&gt;&lt;span&gt;//localhost:3000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1777&quot; data-start=&quot;1737&quot; data-ke-size=&quot;size16&quot;&gt;으로 접속하여 &lt;b&gt;프론트엔드가 정상적으로 뜨는지&lt;/b&gt;를 먼저 확인했습니다.&lt;/p&gt;
&lt;p data-end=&quot;1791&quot; data-start=&quot;1779&quot; data-ke-size=&quot;size16&quot;&gt;이 단계까지 완료되면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1848&quot; data-start=&quot;1792&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1812&quot; data-start=&quot;1792&quot;&gt;서버 내부에서 서비스는 정상 기동&lt;/li&gt;
&lt;li data-end=&quot;1848&quot; data-start=&quot;1813&quot;&gt;문제는 &amp;ldquo;외부에서 어떻게 접근할 것인가&amp;rdquo;로 넘어가게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1887&quot; data-start=&quot;1855&quot; data-ke-size=&quot;size26&quot;&gt;Nginx vs Load Balancer 고민&lt;/h2&gt;
&lt;p data-end=&quot;1916&quot; data-start=&quot;1889&quot; data-ke-size=&quot;size16&quot;&gt;다음 단계에서 고민했던 선택지는 두 가지였습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1997&quot; data-start=&quot;1918&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1948&quot; data-start=&quot;1918&quot;&gt;VM 내부에 &lt;b&gt;Nginx 리버스 프록시 구성&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1997&quot; data-start=&quot;1949&quot;&gt;GCP &lt;b&gt;전역(External) HTTP(S) Load Balancer&lt;/b&gt; 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-end=&quot;2011&quot; data-start=&quot;1999&quot; data-ke-size=&quot;size23&quot;&gt;당시 상황 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2100&quot; data-start=&quot;2012&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2037&quot; data-start=&quot;2012&quot;&gt;WebSocket 기능은 아직 고도화 단계&lt;/li&gt;
&lt;li data-end=&quot;2100&quot; data-start=&quot;2038&quot;&gt;현재는
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2100&quot; data-start=&quot;2046&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2054&quot; data-start=&quot;2046&quot;&gt;파일 업로드&lt;/li&gt;
&lt;li data-end=&quot;2068&quot; data-start=&quot;2057&quot;&gt;문서 임베딩 처리&lt;/li&gt;
&lt;li data-end=&quot;2100&quot; data-start=&quot;2071&quot;&gt;Google 로그인 연동&lt;br /&gt;까지 확인 중인 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2144&quot; data-start=&quot;2102&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 Load Balancer를 선택한 이유&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2240&quot; data-start=&quot;2146&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2172&quot; data-start=&quot;2146&quot;&gt;HTTPS / SSL 인증서 관리 부담 감소&lt;/li&gt;
&lt;li data-end=&quot;2195&quot; data-start=&quot;2173&quot;&gt;단일 도메인에서 경로 기반 분기 필요&lt;/li&gt;
&lt;li data-end=&quot;2216&quot; data-start=&quot;2196&quot;&gt;추후 WebSocket 확장 고려&lt;/li&gt;
&lt;li data-end=&quot;2240&quot; data-start=&quot;2217&quot;&gt;GCP에서 제공하는 관리형 인프라 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2317&quot; data-start=&quot;2242&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 &lt;b&gt;Nginx는 사용하지 않고&lt;/b&gt;, &lt;b&gt;전역 HTTP(S) Load Balancer&lt;/b&gt;를 사용하는 방향으로 결정했습니다.&lt;/p&gt;
&lt;p data-end=&quot;2317&quot; data-start=&quot;2242&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2358&quot; data-start=&quot;2324&quot; data-ke-size=&quot;size26&quot;&gt;전역 HTTP(S) Load Balancer 설정&lt;/h2&gt;
&lt;p data-end=&quot;2424&quot; data-start=&quot;2360&quot; data-ke-size=&quot;size16&quot;&gt;Load Balancer는 &lt;b&gt;External HTTP(S) Load Balancer (전역)&lt;/b&gt; 로 생성했습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;2435&quot; data-start=&quot;2426&quot; data-ke-size=&quot;size23&quot;&gt;주요 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 설정 시에는 고정 ip 만들기 하시면 됩니다! (해당 lb IP 를 가지고 이제 도메인 구매한 곳에 가서 설정하면 됨.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2640&quot; data-start=&quot;2436&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2468&quot; data-start=&quot;2436&quot;&gt;&lt;b&gt;Backend&lt;/b&gt;: Compute Engine VM&lt;/li&gt;
&lt;li data-end=&quot;2564&quot; data-start=&quot;2469&quot;&gt;&lt;b&gt;Backend Service&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2564&quot; data-start=&quot;2493&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2564&quot; data-start=&quot;2493&quot;&gt;포트별 서비스 연결
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2564&quot; data-start=&quot;2510&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2527&quot; data-start=&quot;2510&quot;&gt;3000 (Frontend)&lt;/li&gt;
&lt;li data-end=&quot;2548&quot; data-start=&quot;2532&quot;&gt;8080 (Backend)&lt;/li&gt;
&lt;li data-end=&quot;2564&quot; data-start=&quot;2553&quot;&gt;8000 (AI)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;1490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cka34/dJMcag5rOON/3j7HkiKUk7tmhMlBOnSk01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cka34/dJMcag5rOON/3j7HkiKUk7tmhMlBOnSk01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cka34/dJMcag5rOON/3j7HkiKUk7tmhMlBOnSk01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCka34%2FdJMcag5rOON%2F3j7HkiKUk7tmhMlBOnSk01%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;1116&quot; height=&quot;1490&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;1490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-02-02 오전 12.07.20.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UsYxu/dJMcag5rOOW/tTKDYmtYnkwIzFERFMzWc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UsYxu/dJMcag5rOOW/tTKDYmtYnkwIzFERFMzWc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UsYxu/dJMcag5rOOW/tTKDYmtYnkwIzFERFMzWc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUsYxu%2FdJMcag5rOOW%2FtTKDYmtYnkwIzFERFMzWc1%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;1388&quot; height=&quot;834&quot; data-filename=&quot;스크린샷 2026-02-02 오전 12.07.20.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2640&quot; data-start=&quot;2436&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2640&quot; data-start=&quot;2565&quot;&gt;&lt;b&gt;URL Map&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2640&quot; data-start=&quot;2581&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2597&quot; data-start=&quot;2581&quot;&gt;/ &amp;rarr; Frontend&lt;/li&gt;
&lt;li data-end=&quot;2620&quot; data-start=&quot;2600&quot;&gt;/api/* &amp;rarr; Backend&lt;/li&gt;
&lt;li data-end=&quot;2640&quot; data-start=&quot;2623&quot;&gt;/fastapi/* &amp;rarr; AI 서버&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2654&quot; data-start=&quot;2642&quot; data-ke-size=&quot;size23&quot;&gt;헬스 체크 설정&lt;/h3&gt;
&lt;p data-end=&quot;2687&quot; data-start=&quot;2655&quot; 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-end=&quot;2736&quot; data-start=&quot;2689&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2717&quot; data-start=&quot;2689&quot;&gt;/health 또는 /api/health&lt;/li&gt;
&lt;li data-end=&quot;2736&quot; data-start=&quot;2718&quot;&gt;해당 포트로 접근 가능해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2787&quot; data-start=&quot;2738&quot; data-ke-size=&quot;size16&quot;&gt;헬스 체크 설정 후에는 반드시 &lt;b&gt;방화벽에서 헬스 체크 트래픽을 허용&lt;/b&gt;해야 합니다. 안그럼 아예 안됩니다.&lt;/p&gt;
&lt;p data-end=&quot;2787&quot; data-start=&quot;2738&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2830&quot; data-start=&quot;2794&quot; data-ke-size=&quot;size26&quot;&gt;Load Balancer 사용 시 추가한 방화벽 설정&lt;/h2&gt;
&lt;p data-end=&quot;2900&quot; data-start=&quot;2832&quot; data-ke-size=&quot;size16&quot;&gt;Load Balancer를 사용하는 순간,&lt;br /&gt;&amp;ldquo;VM이 살아있다&amp;rdquo;와 &amp;ldquo;LB가 접근 가능하다&amp;rdquo;는 완전히 다른 문제가 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;2934&quot; data-start=&quot;2902&quot; data-ke-size=&quot;size16&quot;&gt;그래서 default VPC에 다음 규칙들을 추가했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3113&quot; data-start=&quot;2936&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3019&quot; data-start=&quot;2936&quot;&gt;&lt;b&gt;헬스 체크용 Ingress 허용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3019&quot; data-start=&quot;2962&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2992&quot; data-start=&quot;2962&quot;&gt;Load Balancer 헬스 체크 소스 대역 허용&lt;/li&gt;
&lt;li data-end=&quot;3019&quot; data-start=&quot;2995&quot;&gt;포트: 3000 / 8080 / 8000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3113&quot; data-start=&quot;3020&quot;&gt;&lt;b&gt;LB &amp;rarr; VM 내부 전달 트래픽 허용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3113&quot; data-start=&quot;3049&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3062&quot; data-start=&quot;3049&quot;&gt;내부 IP 대역 허용&lt;/li&gt;
&lt;li data-end=&quot;3113&quot; data-start=&quot;3065&quot;&gt;이 설정이 없으면 connection timeout 문제가 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3135&quot; data-start=&quot;3115&quot; data-ke-size=&quot;size16&quot;&gt;이 단계에서 방화벽 설정이 누락되면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3198&quot; data-start=&quot;3136&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3163&quot; data-start=&quot;3136&quot;&gt;Backend가 Unhealthy로 표시되거나&lt;/li&gt;
&lt;li data-end=&quot;3198&quot; data-start=&quot;3164&quot;&gt;upstream connect error가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;3235&quot; data-start=&quot;3205&quot; data-ke-size=&quot;size26&quot;&gt;가비아 도메인 연결 및 SSL 인증서 설정&lt;/h2&gt;
&lt;p data-end=&quot;3266&quot; data-start=&quot;3237&quot; data-ke-size=&quot;size16&quot;&gt;도메인은 &lt;b&gt;가비아(Gabia)&lt;/b&gt; 를 사용했습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3281&quot; data-start=&quot;3268&quot; data-ke-size=&quot;size23&quot;&gt;도메인 설정 단계&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;3360&quot; data-start=&quot;3282&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3301&quot; data-start=&quot;3282&quot;&gt;가비아 DNS 설정 화면 접속&lt;/li&gt;
&lt;li data-end=&quot;3346&quot; data-start=&quot;3302&quot;&gt;&lt;b&gt;A 레코드&lt;/b&gt; 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3346&quot; data-start=&quot;3321&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3346&quot; data-start=&quot;3321&quot;&gt;값: Load Balancer의 외부 IP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3360&quot; data-start=&quot;3347&quot;&gt;TTL 기본값 유지&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;3385&quot; data-start=&quot;3362&quot; data-ke-size=&quot;size16&quot;&gt;이후 GCP Load Balancer에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3435&quot; data-start=&quot;3386&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3417&quot; data-start=&quot;3386&quot;&gt;&lt;b&gt;Google Managed SSL 인증서&lt;/b&gt; 생성&lt;/li&gt;
&lt;li data-end=&quot;3435&quot; data-start=&quot;3418&quot;&gt;해당 도메인을 인증서에 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3505&quot; data-start=&quot;3437&quot; data-ke-size=&quot;size16&quot;&gt;SSL 인증서는 &lt;b&gt;Load Balancer에서 종료&lt;/b&gt;되며,&lt;br /&gt;VM 내부에서는 HTTPS 설정을 따로 하지 않았습니다.&lt;/p&gt;
&lt;p data-end=&quot;3505&quot; data-start=&quot;3437&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;3538&quot; data-start=&quot;3512&quot; data-ke-size=&quot;size26&quot;&gt;선택) IP 제한을 걸고 싶다면?&lt;/h2&gt;
&lt;p data-end=&quot;3583&quot; data-start=&quot;3540&quot; data-ke-size=&quot;size16&quot;&gt;만약 특정 IP만 접근 가능하도록 제한하고 싶다면,&lt;br /&gt;방법은 두 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;3686&quot; data-start=&quot;3585&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3629&quot; data-start=&quot;3585&quot;&gt;&lt;b&gt;방화벽 규칙으로 Ingress 제한&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3629&quot; data-start=&quot;3615&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3629&quot; data-start=&quot;3615&quot;&gt;특정 IP 대역만 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3686&quot; data-start=&quot;3630&quot;&gt;&lt;b&gt;Cloud Armor 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3686&quot; data-start=&quot;3655&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3668&quot; data-start=&quot;3655&quot;&gt;IP 기반 접근 제어&lt;/li&gt;
&lt;li data-end=&quot;3686&quot; data-start=&quot;3672&quot;&gt;추후 WAF 확장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;3758&quot; data-start=&quot;3688&quot; data-ke-size=&quot;size16&quot;&gt;현재 단계에서는 개발/검증 목적이므로 IP 제한은 최소한으로만 적용했고, 운영 단계에서 추가 적용을 고려하고 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;3758&quot; data-start=&quot;3688&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3758&quot; data-start=&quot;3688&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 SpeakNote의 GCP 서버 환경을&lt;br /&gt;&lt;b&gt;아주 처음 단계부터 Load Balancer 연결까지&lt;/b&gt; 순서대로 정리했습니다.&lt;/p&gt;
&lt;p data-end=&quot;3863&quot; data-start=&quot;3855&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3930&quot; data-start=&quot;3864&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3883&quot; data-start=&quot;3864&quot;&gt;SSH 키 접속 문제 상세 분석 &lt;a href=&quot;https://ye-seul0-0.tistory.com/192&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ye-seul0-0.tistory.com/192&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1769960668960&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[GCP - ssh] Compute Engine SSH 접속 실패 원인과 해결 &amp;ndash; 콘솔 SSH로 직접 고친 방법 (Product : SpeakNote)&quot; data-og-description=&quot;SSH 키 등록했는데 로컬에서 접속이 안 됐던 문제 (해결까지 한 번에 정리)Compute Engine 인스턴스를 생성하면서초기 설정 화면에서 SSH 공개키를 분명히 등록했습니다.하지만 인스턴스 생성 이후, &quot; data-og-host=&quot;ye-seul0-0.tistory.com&quot; data-og-source-url=&quot;https://ye-seul0-0.tistory.com/192&quot; data-og-url=&quot;https://ye-seul0-0.tistory.com/192&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sTsP6/dJMb8WeuC8o/KH7fwdA09hVxt7BrdQl4C0/img.png?width=800&amp;amp;height=102&amp;amp;face=0_0_800_102,https://scrap.kakaocdn.net/dn/bYSsWb/dJMb8TB4AzA/uvP2OxihBS7D8nAsUZ8bKK/img.png?width=800&amp;amp;height=102&amp;amp;face=0_0_800_102&quot;&gt;&lt;a href=&quot;https://ye-seul0-0.tistory.com/192&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ye-seul0-0.tistory.com/192&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sTsP6/dJMb8WeuC8o/KH7fwdA09hVxt7BrdQl4C0/img.png?width=800&amp;amp;height=102&amp;amp;face=0_0_800_102,https://scrap.kakaocdn.net/dn/bYSsWb/dJMb8TB4AzA/uvP2OxihBS7D8nAsUZ8bKK/img.png?width=800&amp;amp;height=102&amp;amp;face=0_0_800_102');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[GCP - ssh] Compute Engine SSH 접속 실패 원인과 해결 &amp;ndash; 콘솔 SSH로 직접 고친 방법 (Product : SpeakNote)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SSH 키 등록했는데 로컬에서 접속이 안 됐던 문제 (해결까지 한 번에 정리)Compute Engine 인스턴스를 생성하면서초기 설정 화면에서 SSH 공개키를 분명히 등록했습니다.하지만 인스턴스 생성 이후,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ye-seul0-0.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;3864&quot; data-end=&quot;3930&quot;&gt;
&lt;li data-start=&quot;3884&quot; data-end=&quot;3911&quot;&gt;Load Balancer + 방화벽 트러블슈팅&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 다뤄보겠습니당.&lt;/p&gt;</description>
      <category>DevOps/GCP</category>
      <category>cloud armor</category>
      <category>GCP</category>
      <category>global lb 설정</category>
      <category>upstream connect error</category>
      <category>프로젝트기록-speaknote</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/191</guid>
      <comments>https://ye-seul0-0.tistory.com/191#entry191comment</comments>
      <pubDate>Wed, 28 Jan 2026 02:35:28 +0900</pubDate>
    </item>
    <item>
      <title>[Architecture] 병목 현상을 줄이기 위한 실시간 아키텍처 설계(Project:SpeakNote)</title>
      <link>https://ye-seul0-0.tistory.com/187</link>
      <description>&lt;h1&gt;병목 현상을 줄이기 위한 실시간 아키텍처 설계&lt;/h1&gt;
&lt;h1&gt;&amp;mdash; SpeakNote의 설계 선택과 그 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ye-seul0-0.tistory.com/185&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ye-seul0-0.tistory.com/185&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769618968883&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[CS | 실시간 시스템] 병목이 발생하는 이유&quot; data-og-description=&quot;실시간 시스템에서 병목이 발생하는 이유실시간 시스템을 처음 설계할 때 가장 많이 착각하는 부분 중 하나는&amp;ldquo;성능을 충분히 높이면 병목은 없앨 수 있다&amp;rdquo;는 생각입니다.저 역시 SpeakNote 프로&quot; data-og-host=&quot;ye-seul0-0.tistory.com&quot; data-og-source-url=&quot;https://ye-seul0-0.tistory.com/185&quot; data-og-url=&quot;https://ye-seul0-0.tistory.com/185&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwbPi3/dJMb86nSeoX/MIZBajIj4dg50OTpaxwnXk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/90Id1/dJMb9kTXF8V/CgkUH1rZv7H7YgT3TYKDNK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://ye-seul0-0.tistory.com/185&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ye-seul0-0.tistory.com/185&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwbPi3/dJMb86nSeoX/MIZBajIj4dg50OTpaxwnXk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/90Id1/dJMb9kTXF8V/CgkUH1rZv7H7YgT3TYKDNK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[CS | 실시간 시스템] 병목이 발생하는 이유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;실시간 시스템에서 병목이 발생하는 이유실시간 시스템을 처음 설계할 때 가장 많이 착각하는 부분 중 하나는&amp;ldquo;성능을 충분히 높이면 병목은 없앨 수 있다&amp;rdquo;는 생각입니다.저 역시 SpeakNote 프로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ye-seul0-0.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 실시간 시스템에서 병목 현상이 왜 필연적으로 발생하는지, 그리고 병목이 단순한 성능 문제가 아니라 &lt;b&gt;구조적인 문제&lt;/b&gt;라는 점을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 그 연장선에서, &lt;b&gt;SpeakNote에서는 병목을 줄이고, 병목이 전체 시스템으로 확산되는 것을 막기 위해 어떤 아키텍처를 선택했는지&lt;/b&gt;를 실제 설계를 기준으로 설명하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote는 실시간 음성 입력 &amp;rarr; STT &amp;rarr; AI 요약 &amp;rarr; 주석 렌더링이라는 여러 단계가 연속적으로 연결된 시스템입니다.&lt;br /&gt;이 구조에서 병목은 특정 코드의 문제가 아니라,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;입력 속도와 처리 속도가 다른 여러 단계가 동시에 연결되면서 자연스럽게 발생합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이 시스템의 핵심 설계 목표는 다음과 같았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;병목이 발생해도 전체 흐름이 무너지지 않을 것&lt;/li&gt;
&lt;li&gt;한 사용자의 지연이 다른 사용자에게 전파되지 않을 것&lt;/li&gt;
&lt;li&gt;실시간성을 &amp;ldquo;즉시 반영&amp;rdquo;이 아니라 &amp;ldquo;체감 가능한 지연 범위&amp;rdquo;로 정의할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병목을 없애는 것이 아니라,&lt;br /&gt;병목이 발생하더라도 시스템이 무너지지 않도록 만든다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SpeakNote 전체 아키텍처 개요&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KaXXe/dJMcahXyO1z/8vRS0kI6UQhq1b9bSR0QgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KaXXe/dJMcahXyO1z/8vRS0kI6UQhq1b9bSR0QgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KaXXe/dJMcahXyO1z/8vRS0kI6UQhq1b9bSR0QgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKaXXe%2FdJMcahXyO1z%2F8vRS0kI6UQhq1b9bSR0QgK%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;1634&quot; height=&quot;660&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Java Backend &amp;ndash; Python Backend &amp;ndash; Google STT Streaming 구조)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote는 크게 세 계층으로 나뉩니다.&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;클라이언트 (Browser)&lt;/b&gt;&lt;br /&gt;실시간 오디오 캡처 및 주석 렌더링&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애플리케이션 계층 (Spring Boot)&lt;/b&gt;&lt;br /&gt;WebSocket 기반 실시간 처리 + REST 기반 비실시간 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AI 처리 계층 (Python / FastAPI)&lt;/b&gt;&lt;br /&gt;문서 파싱, 요약, 주석 생성 등 고비용 연산&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 병목 관리의 핵심은 &lt;b&gt;Java Backend 내부의 세션 단위 실시간 처리 구조&lt;/b&gt;에 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. WebSocket 입력 단계: 병목은 &amp;lsquo;속도&amp;rsquo;가 아니라 &amp;lsquo;유입 제어 불가&amp;rsquo;에서 시작된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 음성 입력은 WebSocket을 통해 Java 서버로 전달됩니다.&lt;br /&gt;WebSocket은 연결 유지형 프로토콜이기 때문에, 서버가 클라이언트의 입력 속도를 직접 제어하기 어렵습니다.&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 다음 단계(STT 전송)를 처리하지 못해도&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;&amp;ldquo;즉시 처리&amp;rdquo; 대신 &amp;ldquo;즉시 큐잉&amp;rdquo;&lt;/b&gt; 구조를 선택했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 세션 단위 Inbound Queue: 병목을 분리하는 첫 번째 장치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 WebSocket 연결은 서버 내부에서 &lt;b&gt;독립된 SessionState&lt;/b&gt;로 관리됩니다.&lt;br /&gt;그리고 각 세션마다 다음 구성 요소를 가지게 구성했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InboundAudioQueue&lt;/li&gt;
&lt;li&gt;STT Text Buffer&lt;/li&gt;
&lt;li&gt;OutboundQueue&lt;/li&gt;
&lt;li&gt;세션 전용 워커 스레드&lt;/li&gt;
&lt;li&gt;세션 전용 Google STT gRPC 스트림&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-29 오전 1.33.26.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KGtYX/dJMb99ZwP5A/F6npIoAnmU0wLSIv38Bi60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KGtYX/dJMb99ZwP5A/F6npIoAnmU0wLSIv38Bi60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KGtYX/dJMb99ZwP5A/F6npIoAnmU0wLSIv38Bi60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKGtYX%2FdJMb99ZwP5A%2FF6npIoAnmU0wLSIv38Bi60%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;602&quot; height=&quot;454&quot; data-filename=&quot;스크린샷 2026-01-29 오전 1.33.26.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket 핸들러는 오디오 청크를 수신하자마자 &lt;b&gt;바로 InboundAudioQueue에 넣고 반환합니다.&lt;/b&gt;&lt;br /&gt;이렇게 함으로써 WebSocket I/O 스레드는 음성 처리 로직에 의해 블로킹되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조의 핵심은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력 폭주가 발생해도 &lt;b&gt;큐까지만 영향&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;STT 처리 지연이 발생해도 &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;p data-ke-size=&quot;size16&quot;&gt;즉, 병목을 &amp;ldquo;없앤 것&amp;rdquo;이 아니라 &lt;b&gt;영향 범위를 세션 단위로 가둔 것!!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;277&quot; data-start=&quot;230&quot; data-ke-size=&quot;size26&quot;&gt;3. Google STT Streaming: 병목이 발생해도 흐름이 끊기지 않게&lt;/h2&gt;
&lt;p data-end=&quot;599&quot; data-start=&quot;279&quot; data-ke-size=&quot;size16&quot;&gt;WebSocket 단계에서 입력 폭주(backpressure 부재)가 병목의 출발점이라면, 그 다음 단계인 Google STT 스트리밍은 &amp;ldquo;지연이 발생했을 때 시스템 전체로 전파되는가&amp;rdquo;를 결정하는 구간이다. 특히 STT는 네트워크 I/O와 외부 API 응답 지연에 크게 영향을 받는 구간이므로, 단일 스트림 또는 공유 스트림 구조로 설계할 경우 특정 사용자의 STT 지연이 곧바로 전체 서버의 지연으로 확산될 위험이 큽니다. SpeakNote는 이 문제를 피하기 위해 &lt;b&gt;세션 단위로 STT 스트림을 완전히 분리&lt;/b&gt;하고, STT 처리는 세션 전용 워커가 전담하도록 구성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq3KNL/dJMcacBWx64/1Vy7XnsJ0hjEvIiLl6FMv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq3KNL/dJMcacBWx64/1Vy7XnsJ0hjEvIiLl6FMv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq3KNL/dJMcacBWx64/1Vy7XnsJ0hjEvIiLl6FMv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq3KNL%2FdJMcacBWx64%2F1Vy7XnsJ0hjEvIiLl6FMv0%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;904&quot; height=&quot;472&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;631&quot; data-start=&quot;601&quot; data-ke-size=&quot;size23&quot;&gt;3.1 세션별 STT gRPC 스트림 분리 설계&lt;/h3&gt;
&lt;p data-end=&quot;995&quot; data-start=&quot;633&quot; data-ke-size=&quot;size16&quot;&gt;SpeakNote에서 각 WebSocket 세션(Session A/B/N)은 &lt;b&gt;독립적인 Google STT gRPC 스트림&lt;/b&gt;을 가집니다. 이는 &amp;ldquo;단일 클라이언트 인스턴스 또는 단일 스트림을 여러 세션이 공유&amp;rdquo;하는 방식에서 자주 발생하는 충돌/경합 문제를 회피하기 위한 선택입니다. 스트리밍 STT는 기본적으로 한 스트림 내에서 오디오 프레임의 순서와 타이밍이 중요하며, 여러 세션의 오디오가 섞이거나 동시 요청이 엉키는 순간 인식 품질 저하뿐 아니라 스트림 정지, 응답 꼬임과 같은 장애로 이어질 수 있는데 따라서 SpeakNote는 &lt;b&gt;세션당 하나의 스트림&lt;/b&gt;을 열어 &amp;ldquo;세션 내 순서 보장&amp;rdquo;과 &amp;ldquo;세션 간 격리&amp;rdquo;를 동시에 만족시키는 방향을 택했습니다.&lt;/p&gt;
&lt;p data-end=&quot;1197&quot; data-start=&quot;997&quot; data-ke-size=&quot;size16&quot;&gt;이 구조의 핵심은 단순히 스트림을 분리하는 것에서 끝나지 않는데, STT 스트림으로 오디오를 전송하는 역할은 WebSocket 핸들러가 아니라 세션 전용 워커(Session Worker)가 담당합니다. 즉, WebSocket I/O 스레드가 STT 호출을 직접 수행하지 않고, 입력 큐에 적재한 뒤 빠르게 반환함으로써 서버의 연결 유지 성능을 확보하는 것.&lt;/p&gt;
&lt;h3 data-end=&quot;1226&quot; data-start=&quot;1199&quot; data-ke-size=&quot;size23&quot;&gt;3.2 세션 전용 워커의 역할과 처리 흐름&lt;/h3&gt;
&lt;p data-end=&quot;1276&quot; data-start=&quot;1228&quot; data-ke-size=&quot;size16&quot;&gt;세션 전용 워커는 해당 세션의 STT 처리에서 다음 역할만 수행하도록 책임을 제한했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1802&quot; data-start=&quot;1278&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1442&quot; data-start=&quot;1278&quot;&gt;&lt;b&gt;InboundAudioQueue에서 오디오 청크를 소비(consume)&lt;/b&gt;&lt;br /&gt;WebSocket으로 수신된 오디오 청크는 즉시 InboundAudioQueue에 적재되고, 워커는 이 큐를 FIFO로 소비한다. 이때 큐는 순서를 보장하며, 세션 내 오디오 흐름이 뒤섞이지 않도록 한다.&lt;/li&gt;
&lt;li data-end=&quot;1607&quot; data-start=&quot;1444&quot;&gt;&lt;b&gt;해당 세션의 STT gRPC 스트림으로 순서대로 전송&lt;/b&gt;&lt;br /&gt;워커는 큐에서 꺼낸 청크를 해당 세션의 gRPC 요청 스트림에 순차적으로 write한다. &amp;ldquo;오디오 입력 순서가 그대로 STT 스트림 전송 순서&amp;rdquo;가 되기 때문에, 스트리밍 인식 모델이 기대하는 입력 흐름을 깨뜨리지 않는다.&lt;/li&gt;
&lt;li data-end=&quot;1802&quot; data-start=&quot;1609&quot;&gt;&lt;b&gt;STT 응답은 비동기 콜백으로 수신하고 TextBuffer에 누적&lt;/b&gt;&lt;br /&gt;STT의 partial/final transcript 응답은 비동기 observer 콜백으로 수신된다. 응답이 도착할 때마다 세션의 TextBuffer를 갱신하고, final 결과가 확정되면 해당 문장을 누적하여 이후 요약/주석 생성 트리거의 입력으로 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2015&quot; data-start=&quot;1804&quot; data-ke-size=&quot;size16&quot;&gt;이 설계는 &amp;ldquo;STT 지연이 발생하더라도 WebSocket 서버가 멈추지 않는 구조&amp;rdquo;를 만들기 위한 의도적인 분리인데, WebSocket I/O 스레드는 오디오를 받는 역할만 최소 비용으로 수행하고, 실제 STT 전송과 응답 처리는 세션 워커 및 비동기 콜백으로 분산되어 결과적으로 STT가 느려지는 순간에도 WebSocket 서버의 주요 스레드가 외부 호출로 블로킹되지 않습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;2052&quot; data-start=&quot;2017&quot; data-ke-size=&quot;size23&quot;&gt;3.3 STT 지연이 전체 시스템으로 전파되지 않는 이유&lt;/h3&gt;
&lt;p data-end=&quot;2161&quot; data-start=&quot;2054&quot; data-ke-size=&quot;size16&quot;&gt;이 구조가 병목을 &amp;ldquo;해결&amp;rdquo;한다기보다는, 병목이 생겼을 때 &lt;b&gt;영향 범위를 세션 내부로 격리&lt;/b&gt;한다는 점이 중요한데, SpeakNote는 STT 지연이 발생해도 다음 조건을 만족하도록 설계했습니다!&lt;/p&gt;
&lt;p data-end=&quot;2161&quot; data-start=&quot;2054&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;STT 응답이 늦어져도 입력 큐는 유지됨.&lt;/b&gt;&lt;br /&gt;STT 호출이 느려지면 워커의 소비 속도가 감소하고 InboundAudioQueue에 청크가 쌓이기 시작하지만 그러나 이 누적은 세션 단위 큐에서 발생하며, WebSocket 서버 전체가 멈추는 형태가 아니라 &amp;ldquo;해당 세션의 처리 지연&amp;rdquo;으로 국한됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2622&quot; data-start=&quot;2163&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2483&quot; data-start=&quot;2340&quot;&gt;&lt;b&gt;지연은 세션 내부에서 흡수된다(세션 격리)&lt;/b&gt;&lt;br /&gt;세션 A의 STT가 느려졌다고 해서 세션 B의 STT 전송이나 응답 처리에 간섭하지 않는다. 스트림, 큐, 워커가 모두 세션 전용이기 때문에 STT 지연은 공유 자원 경합으로 확대되지 않는다.&lt;/li&gt;
&lt;li data-end=&quot;2622&quot; data-start=&quot;2485&quot;&gt;&lt;b&gt;WebSocket 서버는 계속 연결을 유지한다&lt;/b&gt;&lt;br /&gt;WebSocket 핸들러는 오디오를 큐에 넣고 빠르게 반환한다. 즉, STT 응답이 늦어도 서버의 연결 유지 능력은 유지되며, 다른 세션의 오디오 수신/전송이 블로킹되지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2840&quot; data-start=&quot;2624&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 SpeakNote에서 Google STT Streaming 구간은 &amp;ldquo;지연이 발생할 수밖에 없는 외부 I/O&amp;rdquo;라는 현실을 인정하고, 그 지연이 &lt;b&gt;서버 전체 다운/정지로 번지지 않도록 설계한 단계&lt;/b&gt;라고 정리할 수 있습니다. 병목이 완전히 사라지는 것은 아니지만, 지연이 발생해도 서비스가 끊기지 않고, 문제 범위를 특정 세션으로 제한하며, 전체 파이프라인을 지속 가능하게 만듭니다.&lt;/p&gt;
&lt;p data-end=&quot;2840&quot; data-start=&quot;2624&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ru5By/dJMcahwt6bQ/KTcc5YTKMFTCDgP9IcgOW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ru5By/dJMcahwt6bQ/KTcc5YTKMFTCDgP9IcgOW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ru5By/dJMcahwt6bQ/KTcc5YTKMFTCDgP9IcgOW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRu5By%2FdJMcahwt6bQ%2FKTcc5YTKMFTCDgP9IcgOW0%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;904&quot; height=&quot;472&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. AI 요약 단계: 병목을 &amp;lsquo;비동기 분리&amp;rsquo;로 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 요약은 전체 파이프라인 중 &lt;b&gt;가장 느린 단계입니다.&lt;/b&gt;&lt;br /&gt;따라서 이 단계는 절대 실시간 흐름에 직접 연결되지 않도록 설계함&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;STT 텍스트 누적 &amp;rarr; 조건 충족 시 요약 요청&lt;/li&gt;
&lt;li&gt;요약 요청은 &lt;b&gt;비동기 HTTP&lt;/b&gt;로 Python 서버에 전달&lt;/li&gt;
&lt;li&gt;응답은 즉시 WebSocket으로 보내지 않고 &lt;b&gt;OutboundQueue에 적재&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;요약이 느려져도 STT&amp;middot;입력 처리는 계속 진행&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 Python 계층에서의 병목 조건 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 계층(Java)으로부터 Python 서버는 다음 두 가지 형태의 입력을 지속적으로 수신합니다.&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;STT 텍스트 조각(msg)&lt;/b&gt;: 수 초 간격으로 꾸준히 도착하는 짧은 문자열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문서 경로(doc_path)&lt;/b&gt;: 세션당 한 번 업로드되는 비교적 무거운 전처리 대상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 병목이 발생하는 조건은 명확한데,&lt;br /&gt;AI 주석 생성은 단일 요청 처리 시간이 입력 간격보다 길어 예를 들어, 음성 인식 결과가 3초마다 들어오지만, AI 주석 생성에는 10초 이상이 걸릴 수 있다면, 단순한 동기 처리나 즉시 비동기 호출 방식은 필연적으로 요청 누적과 지연 폭증으로 이어집니다. 이 문제를 해결하지 않으면 &amp;ldquo;처리 자체는 되지만, 시간이 지날수록 응답이 끝없이 밀리는 구조&amp;rdquo;가 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 Task Manager 기반 큐잉: 요청을 즉시 처리하지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote의 Python 계층은 들어오는 모든 요청을 즉시 LLM 호출로 넘기지 않고 대신, &lt;b&gt;Task Manager를 중심으로 한 작업 큐 기반 구조&lt;/b&gt;를 도입했습니다. 이 구조의 핵심은 &amp;ldquo;요청을 바로 처리하지 않고, 처리 가능한 단위로 묶어 관리한다&amp;rdquo;는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task Manager는 다음과 같은 책임을 가집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/opVMU/dJMcabJPc12/eu6k6KLIFm0ZWomuTElmfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/opVMU/dJMcabJPc12/eu6k6KLIFm0ZWomuTElmfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/opVMU/dJMcabJPc12/eu6k6KLIFm0ZWomuTElmfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FopVMU%2FdJMcabJPc12%2Feu6k6KLIFm0ZWomuTElmfK%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;938&quot; height=&quot;582&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&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;Chat Task / Context Task&lt;/b&gt;로 분리&lt;/li&gt;
&lt;li&gt;요청을 Max_process_num 단위의 묶음(batch)으로 구성&lt;/li&gt;
&lt;li&gt;일정 시간(patient time) 동안 요청이 충분히 모이지 않더라도 강제로 flush&lt;/li&gt;
&lt;li&gt;큐에 들어간 작업만 백그라운드에서 비동기 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 단순한 비동기 호출과 다른데, 단순히 asyncio.create_task()를 요청마다 호출하면, 요청 폭주 시 태스크 수가 무제한으로 늘어나 오히려 시스템이 불안정해지지만 반면 Task Manager는 &lt;b&gt;병렬 처리의 상한을 명시적으로 제한&lt;/b&gt;하고, 작업 생성 시점을 제어함으로써 병목이 &amp;ldquo;지연&amp;rdquo;으로만 나타나고 &amp;ldquo;장애&amp;rdquo;로 확대되지 않도록 하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1v2sw/dJMcadAMB42/JWb95pMJ6XMOdpouOmUc90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1v2sw/dJMcadAMB42/JWb95pMJ6XMOdpouOmUc90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1v2sw/dJMcadAMB42/JWb95pMJ6XMOdpouOmUc90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1v2sw%2FdJMcadAMB42%2FJWb95pMJ6XMOdpouOmUc90%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;930&quot; height=&quot;504&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 세션 단위 격리와 순서 보장&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKFubA/dJMcafSYhSn/WHJk5O09k5wzknMdo9mciK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKFubA/dJMcafSYhSn/WHJk5O09k5wzknMdo9mciK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKFubA/dJMcafSYhSn/WHJk5O09k5wzknMdo9mciK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKFubA%2FdJMcafSYhSn%2FWHJk5O09k5wzknMdo9mciK%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;922&quot; height=&quot;430&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python 계층에서 중요한 또 하나의 설계 원칙은 &lt;b&gt;세션 단위 순서 보장입니다&lt;/b&gt;. SpeakNote는 단일 사용자의 발화 흐름이 의미적으로 연결되어 있다는 점을 전제로 해야하기 때문에 따라서 동일 세션에서 발생한 STT 텍스트들은 순서를 유지한 채 처리되어야 하며, 동시에 다른 세션의 요청과는 병렬로 처리될 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 Chat Task는 내부적으로 세션 ID를 기준으로 관리되며, 같은 세션의 요청은 논리적으로 연결된 작업 흐름을 유지합니다. 반면 서로 다른 세션의 Chat Task들은 Task Manager에 의해 병렬로 실행될 수 있습니다. 이 구조 덕분에 특정 사용자의 요청이 느리게 처리되더라도, 다른 사용자의 요청이 그 영향을 받지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.4 문서 전처리 병렬화: 무거운 작업을 한 번에 끝내지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 전처리는 AI 계층에서 가장 무거운 작업 중 하나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote는 문서 내 텍스트뿐 아니라 도표, 차트, 수식과 같은 시각 정보를 LLM을 통해 자연어로 변환하는 과정을 포함하는데 이 과정은 필연적으로 긴 지연을 발생시키며, 단일 스레드로 처리할 경우 사용자 체감 대기 시간이 급격히 늘어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 완화하기 위해 문서 전처리는 내부적으로 &lt;b&gt;카테고리 단위 비동기 병렬 처리&lt;/b&gt;로 설계되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNMS25/dJMcacIIsKB/KxgbFvmyUzIe2lvPKRFZy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNMS25/dJMcacIIsKB/KxgbFvmyUzIe2lvPKRFZy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNMS25/dJMcacIIsKB/KxgbFvmyUzIe2lvPKRFZy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNMS25%2FdJMcacIIsKB%2FKxgbFvmyUzIe2lvPKRFZy0%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;836&quot; height=&quot;482&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에서 figure, chart, equation과 같은 시각 정보들을 먼저 수집한 뒤, 각 카테고리에 대해 비동기 함수로 전처리를 수행, 모든 카테고리에 대한 전처리가 완료되면 결과를 하나의 Context 객체로 통합합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AwKcZ/dJMcadng12g/Jxu9ZiNXg8V42hkWhAQ0s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AwKcZ/dJMcadng12g/Jxu9ZiNXg8V42hkWhAQ0s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AwKcZ/dJMcadng12g/Jxu9ZiNXg8V42hkWhAQ0s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAwKcZ%2FdJMcadng12g%2FJxu9ZiNXg8V42hkWhAQ0s0%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;946&quot; height=&quot;430&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 문서 전처리 자체를 빠르게 만드는 것이 목적이 아니며 핵심은 &amp;ldquo;문서 전처리로 인한 지연이 전체 시스템을 막지 않도록 분리&amp;rdquo;하는 데 있습니다. 전처리는 Context Task로 분리되어 백그라운드에서 처리되며, 채팅 기반 주석 생성(Chat Task)과는 독립적으로 관리합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.5 느린 AI 연산을 실시간 흐름에 맞추는 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeakNote의 Python 계층은 단일 요청 기준으로 보면 결코 빠르지 않습니다. 오히려 의도적으로 &amp;ldquo;느릴 수밖에 없는 구조&amp;rdquo;를 인정하고, 그 느림이 사용자 경험에 직접적으로 누적되지 않도록 설계했습니다. 첫 응답은 반드시 지연이 발생하지만, 이후에는 Task Manager와 비동기 병렬 구조를 통해 &lt;b&gt;요청 간 간격보다 빠른 속도로 응답을 지속적으로 반환&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 계층에서의 병목은 제거의 대상이 아니라 &lt;b&gt;관리의 대상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;병목을 숨기지 않고, 병목이 발생해도 시스템이 안정적으로 동작하도록 구조적으로 흡수하기 때문에&amp;nbsp; 그 결과 SpeakNote는 다중 사용자 환경에서도, 단일 사용자 환경에서도 &amp;ldquo;응답이 밀리다 멈추는 시스템&amp;rdquo;이 아니라 &amp;ldquo;느리더라도 끊기지 않는 시스템&amp;rdquo;으로 동작할 수 있습니다.&lt;/p&gt;</description>
      <category>Architecture</category>
      <category>backgroundqueue</category>
      <category>grpc</category>
      <category>OutboundQueue</category>
      <category>SpeakNote</category>
      <category>병렬처리</category>
      <category>아키텍처설계</category>
      <category>프로젝트기록-speaknote</category>
      <author>yeseul-kim01</author>
      <guid isPermaLink="true">https://ye-seul0-0.tistory.com/187</guid>
      <comments>https://ye-seul0-0.tistory.com/187#entry187comment</comments>
      <pubDate>Fri, 23 Jan 2026 21:06:12 +0900</pubDate>
    </item>
  </channel>
</rss>