<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>frontend-diary-log 님의 블로그</title>
    <link>https://frontend-diary-log.tistory.com/</link>
    <description>열심히 배우고 성장하는 프론트엔드 개발자가 되겠습니다!</description>
    <language>ko</language>
    <pubDate>Fri, 15 May 2026 22:52:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>frontend-diary-log</managingEditor>
    <image>
      <title>frontend-diary-log 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8364796/attach/70d1da8140044a338aca7bbb9dd301b4</url>
      <link>https://frontend-diary-log.tistory.com</link>
    </image>
    <item>
      <title>[Aura_AI] [8편] 서버가 반복적으로 터진 이유와 전체 복구 과정</title>
      <link>https://frontend-diary-log.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;[Aura_AI] [8편] 서버가 반복적으로 터진 이유와 전체 복구 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(EC2 재생성, DB 스키마 정합성 확보, GPT 오류 수정, CloudWatch 자동 복구 적용)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ 문제 상황: 기능은 있는데, 서버가 버티지 못했다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 이후 일정 기간이 지나면서 이상 현상이 반복적으로 발생했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ /api/proxy/users/me &amp;rarr; 500 에러&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 로그인 후 마이페이지 로딩 실패&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ GPT 응답 생성 시 &amp;ldquo;응답을 생성하지 못했습니다.&amp;rdquo; 반복&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ SSH 접속 간헐적 타임아웃&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ EC2 상태 검사 2/3&lt;/h3&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;고쳐놓으면 잠깐 정상 작동&lt;br /&gt;며칠 뒤 다시 서버가 불안정해짐&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 코드 문제가 아니라,&lt;br /&gt;&lt;b&gt;인프라 레벨 + DB + GPT 호출 문제가 동시에 얽힌 복합 장애&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 원인 분석&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. EC2 인스턴스 상태 불안정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드는 Amazon EC2 에서 운영 중이었습니다.&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;Status Check 2/3&lt;/li&gt;
&lt;li&gt;Instance Status Check 실패&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;SSH 연결 불안정&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 인스턴스 자체가 이미 불안정한 상태였음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재부팅으로는 근본 해결이 되지 않았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. DB 연결 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 로그에서 반복적으로 확인된 오류:&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;ECONNREFUSED
Can't connect to MySQL server
&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;MySQL 미실행&lt;/li&gt;
&lt;li&gt;bind-address 설정 문제&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;DB 레이어도 불완전한 상태&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-3. DB 스키마 불일치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 오류도 확인되었습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Unknown column 'bio' in 'field list'
&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;코드에서는 bio, location, style_preference, profile_image 컬럼 사용&lt;/li&gt;
&lt;li&gt;실제 DB에는 해당 컬럼이 존재하지 않음&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;&amp;rarr; /users/me 호출 시 500 에러 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 단순 서버 다운이 아니라&lt;br /&gt;&lt;b&gt;코드와 DB 구조가 맞지 않는 상태&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-4. GPT 호출 파라미터 충돌&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 문제는 GPT 호출 부분이었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;max_tokens 파라미터가 새 모델 정책과 충돌&lt;/li&gt;
&lt;li&gt;일부 요청에서 400 에러 발생&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버, DB, GPT 호출 모두 동시에 불안정&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 해결 전략: 부분 수리가 아닌 전체 재정비&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;h1&gt;3-1. EC2 인스턴스 재생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 인스턴스는 폐기했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 신규 t3.small 생성&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Elastic IP 재연결 (IP 고정)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 Stop/Start 시 IP가 변경되는 구조였습니다.&lt;br /&gt;이번에는 Elastic IP를 적용해 재발 방지했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-2. SSH 및 Git 환경 복구&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Host key mismatch 해결&lt;/h3&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;~/.ssh/known_hosts 수정
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ GitHub SSH 키 재등록&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ git clone 정상 완료&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-3. 백엔드 재설치 및 서비스화&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Python 가상환경 구성&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ systemd 서비스 등록&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;sudo nano /etc/systemd/system/aura-api.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 후:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable aura-api
sudo systemctl start aura-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버 재부팅 시 자동 실행&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-4. MySQL 완전 재정비&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ MySQL 설치&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;sudo apt install mysql-server
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ DB 및 계정 생성&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE DATABASE aura_ai;
CREATE USER 'aura_user'@'%' IDENTIFIED BY '비밀번호';
GRANT ALL PRIVILEGES ON aura_ai.* TO 'aura_user'@'%';
FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&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;✔ bind-address 수정&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bind-address = 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 방화벽 오픈&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sudo ufw allow 3306/tcp
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ EC2 보안 그룹에서도 3306 허용&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-5. DB 스키마 정합성 확보&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누락된 컬럼 추가:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ALTER TABLE users ADD COLUMN bio TEXT NULL;
ALTER TABLE users ADD COLUMN location VARCHAR(255) NULL;
ALTER TABLE users ADD COLUMN style_preference VARCHAR(255) NULL;
ALTER TABLE users ADD COLUMN profile_image VARCHAR(255) NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 /users/me 정상 동작 확인&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ 프론트/프록시 연결 재정비&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트는 Vercel 에 배포되어 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 환경변수 수정&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;BACKEND_API_BASE_URL=http://&amp;lt;Elastic-IP&amp;gt;:8000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ /api/proxy/* 연결 로그 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 실제 연결 주소 디버깅 가능하도록 개선&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ GPT 오류 수정&lt;/h1&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;max_tokens &amp;rarr; max_completion_tokens&lt;/li&gt;
&lt;li&gt;정책에 맞는 temperature 값 적용&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;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 동일 모델 재시도&lt;br /&gt;2️⃣ 재시도 실패 시 fallback 모델 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 단일 API 실패가 전체 실패로 이어지지 않도록 설계&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ CloudWatch 자동 복구 적용&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;며칠 뒤 다시 터지는 문제를 줄이기 위해&lt;br /&gt;모니터링 + 자동 복구 구조를 추가했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-1. CloudWatch Agent 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon CloudWatch Agent 설치 후:&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표 수집&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-2. 상태 검사 경보 생성&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-3. 자동 복구 설정&lt;/h2&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;rarr; 시스템 상태 검사 실패 시 자동 복구 수행&lt;/p&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;서버가 터져도 자동으로 다시 올라오는 구조 확보&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7️⃣ 최종 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 인스턴스 안정화&lt;br /&gt;✔ DB 연결 정상&lt;br /&gt;✔ /users/me 정상 반환&lt;br /&gt;✔ GPT 응답 정상&lt;br /&gt;✔ CloudWatch 자동 복구 설정 완료&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8️⃣ 이번 경험에서 배운 점&lt;/h1&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;DB 구조 불일치&lt;/li&gt;
&lt;li&gt;외부 API 정책 변경&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;하지만 이를 통해:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 인스턴스 레벨 복구 경험&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ DB 스키마 정합성 관리 중요성 체감&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 외부 API 의존성 관리 필요성 학습&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 자동 복구 기반 운영 구조 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코디챗봇 프로젝트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/55</guid>
      <comments>https://frontend-diary-log.tistory.com/55#entry55comment</comments>
      <pubDate>Fri, 27 Feb 2026 15:13:47 +0900</pubDate>
    </item>
    <item>
      <title>[Aura_AI] [7편] GPT 호출 안정화 및 토큰 사용량 제한 설계</title>
      <link>https://frontend-diary-log.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;[Aura_AI] [7편] GPT 호출 안정화 및 토큰 사용량 제한 설계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(운영 환경에서 비용 폭주를 막기 위한 구조 개선)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ 왜 이 작업을 하게 되었는가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 실제 배포 후 운영하면서 단순 기능 구현 단계에서는 보이지 않던 문제가 드러났습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ GPT 호출 비용 예측이 어렵다&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 사용자가 반복 요청을 보내면 호출이 무한히 누적될 수 있다&lt;/h3&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;호출이 제한되지 않으면 과금이 통제되지 않는다&amp;rdquo;는 점이었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능은 정상 동작했지만,&lt;br /&gt;&lt;b&gt;운영 가능한 구조는 아니었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 단순 기능 수정이 아니라&lt;br /&gt;&lt;b&gt;비용 통제 + 응답 안정성 개선 설계 작업&lt;/b&gt;을 진행했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 설계 목표&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업의 목표는 명확했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 사용자 단위 일일 토큰 사용량 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 무한 호출 방지&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 과도한 프롬프트 사전 차단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 비정상 입력 방어&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 응답 실패 시 자동 복구 로직 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 빈 응답 대응&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 모델 정책 변경에 유연한 구조 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 파라미터 충돌 방지&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 사용자별 일일 토큰 제한 설계&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. 왜 사용자 단위인가?&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;/li&gt;
&lt;li&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;3-2. 사용량 저장 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user_daily_usage 테이블 생성&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;def init_usage_db():
    # user_daily_usage 테이블 생성
&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;get_daily_usage(user_id) &amp;rarr; 오늘 사용량 조회&lt;/li&gt;
&lt;li&gt;add_daily_usage(user_id, tokens_in, tokens_out) &amp;rarr; 사용량 누적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-3. 환경변수 기반 제한&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 중 유연하게 조정할 수 있도록 하드코딩하지 않았습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;max_prompt_tokens = int(os.getenv(&quot;MAX_REQUEST_PROMPT_TOKENS&quot;, &quot;500&quot;))
max_response_tokens = int(os.getenv(&quot;MAX_RESPONSE_TOKENS&quot;, &quot;200&quot;))
daily_limit = int(os.getenv(&quot;MAX_DAILY_TOKENS_PER_USER&quot;, &quot;2000&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 변수:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;MAX_REQUEST_PROMPT_TOKENS=500
MAX_RESPONSE_TOKENS=200
MAX_DAILY_TOKENS_PER_USER=2000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 코드 수정 없이 운영 정책 조정 가능&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-4. 프롬프트 길이 사전 차단&lt;/h2&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;if prompt_tokens &amp;gt; max_prompt_tokens:
    return '{&quot;message&quot;:&quot;요청이 너무 깁니다. 입력을 줄여 주세요.&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 호출 이전에 차단하여&lt;br /&gt;불필요한 비용 발생 방지&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-5. 일일 사용량 초과 차단&lt;/h2&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;if projected &amp;gt; daily_limit:
    return '{&quot;message&quot;:&quot;오늘 사용 가능한 토큰을 초과했습니다. 내일 다시 시도해 주세요.&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 무한 호출 구조 제거&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ GPT 응답 안정화 설계&lt;/h1&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;모델 정책 변경으로 400 오류 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-1. 파라미터 정책 변경 대응&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;max_tokens &amp;rarr; max_completion_tokens로 변경&lt;/li&gt;
&lt;li&gt;모델 정책에 맞지 않는 temperature 값 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 고정된 API가 아니라, 정책이 변할 수 있는 외부 의존성입니다.&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;4-2. 빈 응답 대응 로직&lt;/h2&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;content = (response.choices[0].message.content or &quot;&quot;).strip()

if not content:
    # 동일 모델 재시도

if not content:
    # gpt-4o-mini fallback
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;처리 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 기본 모델: gpt-5-nano&lt;br /&gt;2️⃣ 응답 비어있으면 재시도&lt;br /&gt;3️⃣ 재시도 실패 시 gpt-4o-mini fallback&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 단일 실패가 전체 실패로 이어지지 않도록 개선&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ 설계적으로 의미 있었던 부분&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업은 단순 &amp;ldquo;기능 추가&amp;rdquo;가 아니었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 비용 통제 구조 설계&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 실패를 전제로 한 복구 설계&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 외부 API 정책 변화 대응&lt;/h3&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;b&gt;운영 환경을 고려하는 개발자로 사고가 확장된 작업&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ 적용 결과&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 무한 호출 구조 제거&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 과금 폭주 위험 감소&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 빈 응답 문제 완화&lt;/h3&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7️⃣ 정리&lt;/h1&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;p data-ke-size=&quot;size16&quot;&gt;GPT를 사용하는 서비스에서 중요한 것은&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;비용 통제&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는&lt;br /&gt;&lt;b&gt;실제 서버가 반복적으로 불안정해졌던 원인과, 인스턴스를 재생성하고 자동 복구까지 구축한 과정&lt;/b&gt;을 정리하겠습니다.&lt;/p&gt;</description>
      <category>코디챗봇 프로젝트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/54</guid>
      <comments>https://frontend-diary-log.tistory.com/54#entry54comment</comments>
      <pubDate>Fri, 27 Feb 2026 15:12:42 +0900</pubDate>
    </item>
    <item>
      <title>[22편] 서버 반복적 오류 해결</title>
      <link>https://frontend-diary-log.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;[Aura AI] 서버가 반복적으로 터진 이유와 전체 복구 과정 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(EC2 재생성, DB 재정비, GPT 안정화, CloudWatch 자동 복구 적용)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ 장애 증상 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 발생한 증상은 단순 500 에러였습니다.&lt;br /&gt;하지만 시간이 지날수록 서버 전체가 불안정해지는 문제가 반복되었습니다.&lt;/p&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;/api/proxy/users/me &amp;rarr; 500 에러&lt;/li&gt;
&lt;li&gt;로그인 후 마이페이지 정보 로드 실패&lt;/li&gt;
&lt;li&gt;GPT 응답 생성 시&lt;br /&gt;&amp;rarr; &quot;응답을 생성하지 못했습니다.&quot; 반복&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;EC2 상태 검사 2/3 통과&lt;/li&gt;
&lt;li&gt;SSH 연결 간헐적 타임아웃&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트 &amp;rarr; 백엔드 &amp;rarr; DB &amp;rarr; GPT 호출까지&lt;br /&gt;전체 스택이 동시에 불안정한 상태였습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 원인 분석 (복합 장애)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 장애는 단일 원인이 아니라, &lt;b&gt;여러 문제가 동시에 얽혀 있던 상황&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-1. EC2 인스턴스 상태 검사 2/3 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드는 Amazon EC2 위에서 운영 중이었습니다.&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;Status Check: 2/3&lt;/li&gt;
&lt;li&gt;Instance Status Check 실패&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;SSH 접속 불안정&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. DB 연결 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 로그에서 다음 오류가 반복 확인되었습니다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;ECONNREFUSED
Can't connect to MySQL server
&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;MySQL이 정상적으로 실행되지 않음&lt;/li&gt;
&lt;li&gt;bind-address 문제&lt;/li&gt;
&lt;li&gt;포트 차단 가능성&lt;/li&gt;
&lt;li&gt;DB 초기화 미완료&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-3. DB 스키마 불일치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그에서 다음 오류가 발견되었습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Unknown column 'bio' in 'field list'
&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;코드에서는 bio, location, style_preference, profile_image 컬럼 사용&lt;/li&gt;
&lt;li&gt;실제 DB에는 해당 컬럼이 존재하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 /users/me 호출 시 500 에러 발생&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-4. GPT 호출 파라미터 오류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT 호출 시 다음 문제가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;max_tokens 파라미터가 새 모델 정책과 호환되지 않음&lt;/li&gt;
&lt;li&gt;temperature 값 정책 불일치&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;400 에러 발생&lt;/li&gt;
&lt;li&gt;응답 생성 실패 메시지 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 해결 과정 (실제 작업 순서)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-1. 인스턴스 재생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 EC2는 재사용하지 않기로 결정했습니다.&lt;/p&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;t3.small 생성&lt;/li&gt;
&lt;li&gt;기존 인스턴스는 폐기&lt;/li&gt;
&lt;li&gt;Elastic IP 재연결하여 IP 고정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; IP 변경으로 인한 재발 방지&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-2. SSH 및 Git 환경 복구&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Host key mismatch 해결&lt;/h3&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;~/.ssh/known_hosts 수정
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ GitHub SSH 키 등록&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;/li&gt;
&lt;li&gt;git clone 정상 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-3. 백엔드 재설치 및 서비스 등록&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Python 가상환경 생성&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ systemd 서비스 등록&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;sudo nano /etc/systemd/system/aura-api.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록 후:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable aura-api
sudo systemctl start aura-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버 재부팅 시 자동 실행&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-4. MySQL 설치 및 초기화&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ MySQL 설치&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;sudo apt install mysql-server
sudo systemctl start mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ DB 및 계정 생성&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE DATABASE aura_ai;
CREATE USER 'aura_user'@'%' IDENTIFIED BY '비밀번호';
GRANT ALL PRIVILEGES ON aura_ai.* TO 'aura_user'@'%';
FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-5. MySQL 외부 연결 허용&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ bind-address 수정&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bind-address = 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 방화벽 오픈&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sudo ufw allow 3306/tcp
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ EC2 보안 그룹에서도 3306 허용&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3-6. DB 스키마 불일치 해결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부족한 컬럼을 직접 추가했습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ALTER TABLE users ADD COLUMN bio TEXT NULL;
ALTER TABLE users ADD COLUMN location VARCHAR(255) NULL;
ALTER TABLE users ADD COLUMN style_preference VARCHAR(255) NULL;
ALTER TABLE users ADD COLUMN profile_image VARCHAR(255) NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 /users/me 500 에러 해결 완료&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ 프론트 / 프록시 연결 안정화&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트는 Vercel 에 배포되어 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 환경변수 수정&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;BACKEND_API_BASE_URL=http://54.116.93.58:8000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ /api/proxy/* 연결 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 연결 URL 로그 출력 추가&lt;/li&gt;
&lt;li&gt;프록시 디버깅 가능하도록 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ GPT 응답 오류 해결&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5-1. 파라미터 호환성 수정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;max_tokens &amp;rarr; max_completion_tokens&lt;/li&gt;
&lt;li&gt;temperature 정책 범위에 맞게 조정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5-2. 빈 응답 안정화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직 개선:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;응답이 빈 문자열이면 재시도&lt;/li&gt;
&lt;li&gt;재시도 후에도 실패 시 gpt-4o-mini fallback&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 단일 실패가 전체 실패로 이어지지 않도록 개선&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ 토큰 사용량 제한 기능 추가&lt;/h1&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;사용량 추적 DB 테이블 생성&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;h1&gt;7️⃣ CloudWatch 기반 자동 복구 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 며칠 후 다시 터질 가능성을 줄이기 위해&lt;br /&gt;모니터링 + 자동 복구를 적용했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7-1. CloudWatch Agent 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon CloudWatch Agent 설치&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표 수집 설정&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7-2. 경보 생성&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;인스턴스 경보는 콘솔 오류로 1차 실패 (추후 재시도 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7-3. 자동 복구 활성화&lt;/h2&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;rarr; 시스템 상태 검사 실패 시 자동 복구 수행&lt;/p&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;서버가 터져도 자동으로 다시 살아나는 구조 구축&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8️⃣ 최종 결과&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 인스턴스 안정화&lt;br /&gt;✔ DB 연결 정상화&lt;br /&gt;✔ /users/me 정상 반환&lt;br /&gt;✔ GPT 응답 정상 동작&lt;br /&gt;✔ CloudWatch 자동 복구 준비 완료&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9️⃣ 핵심 요약&lt;/h1&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;EC2 인스턴스 불안정&lt;/li&gt;
&lt;li&gt;DB 스키마 불일치&lt;/li&gt;
&lt;li&gt;GPT 호출 파라미터 오류&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;저는:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인스턴스를 재생성하고&lt;/li&gt;
&lt;li&gt;DB와 코드 스키마를 정확히 맞추고&lt;/li&gt;
&lt;li&gt;GPT 호출을 안정화하고&lt;/li&gt;
&lt;li&gt;CloudWatch 자동 복구까지 적용하여&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 복구가 아닌 &lt;b&gt;재발 방지 구조까지 구축&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>챗봇 공부 노트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/53</guid>
      <comments>https://frontend-diary-log.tistory.com/53#entry53comment</comments>
      <pubDate>Fri, 27 Feb 2026 15:11:18 +0900</pubDate>
    </item>
    <item>
      <title>[21편] 토큰 사용량 제한</title>
      <link>https://frontend-diary-log.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;[Aura AI] 토큰 사용량 제한 / 응답 안정화 작업 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(gpt-5-nano, 일일 제한, 재시도 및 fallback 적용)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ 문제 배경&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 GPT 모델 호출 과정에서 다음과 같은 문제가 반복적으로 발생했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 요청에서 응답이 비어오는 현상&lt;/li&gt;
&lt;li&gt;과도한 프롬프트 길이로 인한 비용 증가 위험&lt;/li&gt;
&lt;li&gt;무제한 호출 가능 구조로 인한 과금 폭주 가능성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 환경에서 가장 위험한 요소는 &lt;b&gt;예측 불가능한 비용 증가와 API 실패 누적&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;/li&gt;
&lt;li&gt;요청 길이 사전 차단&lt;/li&gt;
&lt;li&gt;응답 비어올 경우 재시도 및 fallback 적용&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 적용 목표&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 1. 사용자별 일일 토큰 제한 적용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과금 리스크 감소&lt;/li&gt;
&lt;li&gt;무한 호출 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 2. 프롬프트 길이 사전 차단&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비정상적으로 긴 입력 방어&lt;/li&gt;
&lt;li&gt;서버 리소스 보호&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 3. 응답 안정성 강화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답이 비어올 경우 자동 재시도&lt;/li&gt;
&lt;li&gt;재시도 실패 시 fallback 모델 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 변경 파일 및 주요 구현 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. usage_service.py (신규)&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;/p&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;user_daily_usage 테이블 생성&lt;/li&gt;
&lt;li&gt;get_daily_usage(user_id) &amp;rarr; 오늘 사용량 조회&lt;/li&gt;
&lt;li&gt;add_daily_usage(user_id, tokens_in, tokens_out) &amp;rarr; 사용량 누적&lt;/li&gt;
&lt;li&gt;서버 시작 시 테이블 자동 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;def init_usage_db():
    # user_daily_usage 테이블 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 단위로 일별 토큰 사용량을 DB에 저장하여 누적 관리하도록 설계했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-2. gpt_service.py&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT 호출 + 토큰 제한 적용 + fallback 처리&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;pre class=&quot;lisp&quot;&gt;&lt;code&gt;max_prompt_tokens = int(os.getenv(&quot;MAX_REQUEST_PROMPT_TOKENS&quot;, &quot;500&quot;))
max_response_tokens = int(os.getenv(&quot;MAX_RESPONSE_TOKENS&quot;, &quot;200&quot;))
daily_limit = int(os.getenv(&quot;MAX_DAILY_TOKENS_PER_USER&quot;, &quot;2000&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 환경에서 코드 수정 없이 조정 가능하도록 &lt;b&gt;환경변수 기반 제어 구조&lt;/b&gt;로 설계했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  프롬프트 길이 초과 시 즉시 차단&lt;/h3&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;if prompt_tokens &amp;gt; max_prompt_tokens:
    return '{&quot;message&quot;:&quot;요청이 너무 깁니다. 입력을 줄여 주세요.&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 불필요한 모델 호출 자체를 방지&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;pre class=&quot;scilab&quot;&gt;&lt;code&gt;if projected &amp;gt; daily_limit:
    return '{&quot;message&quot;:&quot;오늘 사용 가능한 토큰을 초과했습니다. 내일 다시 시도해 주세요.&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 비용 통제 장치&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;pre class=&quot;makefile&quot;&gt;&lt;code&gt;content = (response.choices[0].message.content or &quot;&quot;).strip()

if not content:
    logging.warning(&quot;GPT empty content; retrying without response_format&quot;)
    # gpt-5-nano 재시도

if not content:
    logging.warning(&quot;GPT empty content after retry; falling back to gpt-4o-mini&quot;)
    # gpt-4o-mini fallback
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;처리 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기본 모델: gpt-5-nano&lt;/li&gt;
&lt;li&gt;응답이 비어있으면 &amp;rarr; 동일 모델 재시도&lt;/li&gt;
&lt;li&gt;재시도 실패 &amp;rarr; gpt-4o-mini fallback&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;3-3. chats.py&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 시작 시 토큰 사용량 DB 초기화&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;from services.usage_service import init_usage_db

init_usage_db()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버 기동 시 자동으로 사용량 테이블 생성&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-4. summary_service.py&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약 모델을 통일했습니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;model=&quot;gpt-5-nano&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 모델 일관성 확보 및 비용 관리&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-5. token_utils.py&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 모델 상수화&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DEFAULT_MODEL = &quot;gpt-5-nano&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 모델 변경 시 유지보수 용이&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ 환경변수 설정 예시&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env 또는 Vercel 환경 변수에 다음 값 설정&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;MAX_REQUEST_PROMPT_TOKENS=500
MAX_RESPONSE_TOKENS=200
MAX_DAILY_TOKENS_PER_USER=2000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 상황에 따라 유연하게 조정 가능하도록 구성했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ 적용 결과&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 무한 호출 및 과금 폭주 위험 감소&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 과도한 프롬프트 사전 차단&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 응답 비어오는 현상 완화&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ fallback 모델 적용으로 안정성 향상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, &amp;ldquo;응답이 가끔 비어오는 문제&amp;rdquo;를&lt;br /&gt;재시도 + 모델 대체 구조로 완화한 것이 가장 체감 효과가 컸습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ 기술적으로 의미 있었던 부분&lt;/h1&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;실패를 전제로 한 복구 구조 설계&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현이 아니라 &amp;ldquo;운영 안정성 설계&amp;rdquo;에 초점을 둔 작업이었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  정리&lt;/h1&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;GPT 응답 안정성을 높이며&lt;/li&gt;
&lt;li&gt;운영 환경에서 과금 리스크를 줄일 수 있는 구조를 구축했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영 환경에서 발생할 수 있는&lt;br /&gt;&lt;b&gt;비용 폭주, 무한 호출, 응답 실패&lt;/b&gt;를 방지하는 안정화 작업이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>챗봇 공부 노트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/52</guid>
      <comments>https://frontend-diary-log.tistory.com/52#entry52comment</comments>
      <pubDate>Fri, 27 Feb 2026 15:07:16 +0900</pubDate>
    </item>
    <item>
      <title>[Aura_Ai] [7편] 서버오류 해결</title>
      <link>https://frontend-diary-log.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  운영 중 서비스 장애 대응 경험 정리 (EC2 IP 변경 이슈)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영 중이던 프로젝트에서 &lt;b&gt;로그인 및 주요 API가 전체적으로 실패하는 장애&lt;/b&gt;를 경험했습니다.&lt;br /&gt;이 글은 단순 트러블슈팅 기록이 아니라, &lt;b&gt;문제 분석 &amp;rarr; 원인 추적 &amp;rarr; 재발 방지 설계까지 수행한 과정&lt;/b&gt;을 정리한 글입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  장애 개요&lt;/h1&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;로그인 500 에러 발생&lt;/li&gt;
&lt;li&gt;채팅/마이페이지 API 전체 실패&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;사용자 데이터 조회 불가&lt;/li&gt;
&lt;li&gt;실질적으로 서비스 사용 불가능 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  1. 1차 분석 &amp;mdash; 애플리케이션 레벨 문제인가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 확인 결과:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[auth][error] CallbackRouteError
[cause]: connect ETIMEDOUT
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 인증 과정에서 DB 연결 타임아웃이 발생하고 있었습니다.&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;Connect Timeout Error (attempted address: 43.200.169.171:8000)
&lt;/code&gt;&lt;/pre&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;코드 에러가 아니라 &lt;b&gt;네트워크 연결 타임아웃&lt;/b&gt;이라는 점이었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에서 애플리케이션 레벨이 아닌 &lt;b&gt;인프라 레벨 문제 가능성&lt;/b&gt;을 의심했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  2. 원인 추적 &amp;mdash; 인프라 계층 점검&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. EC2 상태 점검&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 서버는 Amazon EC2 위에서 실행 중이었습니다.&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;Status Check: &lt;b&gt;2/3 통과&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Instance Status Check 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 OS 내부 문제 또는 네트워크 레벨 이상을 의미합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. Stop/Start 이후 퍼블릭 IP 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 위해 EC2를 재시작(Stop &amp;rarr; Start)했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 퍼블릭 IP가 변경되었습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;기존: 43.200.169.171
변경: 새로운 퍼블릭 IP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프론트엔드는 Vercel 에 배포되어 있었고,&lt;br /&gt;환경변수에는 여전히 &lt;b&gt;이전 IP가 설정된 상태&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  3. 근본 원인&lt;/h1&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;EC2 인스턴스 상태 이상 발생&lt;/li&gt;
&lt;li&gt;Stop/Start 수행&lt;/li&gt;
&lt;li&gt;퍼블릭 IP 변경&lt;/li&gt;
&lt;li&gt;환경변수 갱신 누락&lt;/li&gt;
&lt;li&gt;Vercel &amp;rarr; 백엔드 연결 실패&lt;/li&gt;
&lt;li&gt;전체 API 500 에러&lt;/li&gt;
&lt;/ol&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;코드가 아닌 &lt;b&gt;고정되지 않은 인프라 IP에 직접 의존한 구조적 문제&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  4. 해결 과정&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-1. 인스턴스 정상화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2 상태 3/3 통과 확인&lt;/li&gt;
&lt;li&gt;SSH 접속 정상 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-2. DB 외부 접속 점검&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bind-address = 0.0.0.0&lt;/li&gt;
&lt;li&gt;3306 포트 리스닝 확인&lt;/li&gt;
&lt;li&gt;사용자 권한 재설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-3. 환경변수 동기화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드 .env 수정&lt;/li&gt;
&lt;li&gt;Vercel 환경변수 수정&lt;/li&gt;
&lt;li&gt;재배포 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 로그인 및 주요 API가 정상 동작하는 것을 확인했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  5. 단순 복구에서 끝내지 않고, 구조 개선&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 장애를 단순히 &quot;IP 수정&quot;으로 끝내지 않았습니다.&lt;br /&gt;재발 방지를 위한 구조 개선을 진행했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 5-1. Elastic IP 적용 (고정 IP 도입)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic IP 를 EC2에 연결했습니다.&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;Stop/Start 이후에도 IP 유지&lt;/li&gt;
&lt;li&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;✅ 5-2. 환경변수 관리 체계화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vercel&lt;/li&gt;
&lt;li&gt;EC2&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  이 경험을 통해 얻은 것&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 애플리케이션 문제가 아닐 때, 어디를 봐야 하는지 배웠습니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 &amp;ldquo;connect timeout&amp;rdquo; 한 줄에서&lt;br /&gt;코드가 아닌 네트워크/인프라 문제로 사고를 확장할 수 있었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 클라우드 환경에서 IP는 고정이 아니라는 점을 체감했습니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic IP를 사용하지 않으면&lt;br /&gt;Stop/Start만으로도 서비스가 깨질 수 있다는 것을 경험했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 장애 복구보다 중요한 것은 재발 방지 설계라는 것을 배웠습니다.&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;구조 개선&lt;/li&gt;
&lt;li&gt;고정 IP 도입&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  정리&lt;/h1&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;EC2 상태 이상&lt;br /&gt;&amp;rarr; IP 변경&lt;br /&gt;&amp;rarr; 환경변수 불일치&lt;br /&gt;&amp;rarr; 프록시 타임아웃&lt;br /&gt;&amp;rarr; 전체 API 500 에러&lt;/p&gt;
&lt;/blockquote&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;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 저는 이 경험을 통해&lt;br /&gt;&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;</description>
      <category>코디챗봇 프로젝트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/51</guid>
      <comments>https://frontend-diary-log.tistory.com/51#entry51comment</comments>
      <pubDate>Wed, 25 Feb 2026 10:13:02 +0900</pubDate>
    </item>
    <item>
      <title>[20편] 배포 후 오류 수정</title>
      <link>https://frontend-diary-log.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  배포된 사이트 로그인/채팅 전체 장애 발생 원인 및 해결 과정 상세 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 운영 중이던 서비스에서 갑자기 &lt;b&gt;로그인, 채팅, 마이페이지 기능이 모두 동작하지 않는 장애&lt;/b&gt;가 발생했습니다.&lt;br /&gt;코드 수정이나 배포 변경이 없던 시점이었기 때문에, 처음에는 원인 파악이 쉽지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말씀드리면, 이번 장애는 코드 문제가 아니라 &lt;b&gt;인프라 계층에서 발생한 복합적인 문제&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EC2 인스턴스 상태 이상 &amp;rarr; Stop/Start 과정에서 퍼블릭 IP 변경 &amp;rarr; 환경변수 불일치 &amp;rarr; Vercel 프록시 타임아웃 발생&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 실제 발생 흐름과 해결 과정을 단계별로 상세히 정리하겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ 발생 증상&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-1. 로그인 기능 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 시 다음과 같은 에러가 발생했습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/api/auth/error?error=Configuration
500 (Internal Server Error)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 로그인(구글 로그인 포함)이 전부 실패하였으며, 단순 인증 오류가 아닌 &lt;b&gt;서버 내부 에러(500)&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-2. 채팅 및 마이페이지 API 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 API 요청들이 모두 실패했습니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;/api/proxy/sessions
/api/proxy/users/me
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통적으로 500 에러가 발생했으며, 프론트엔드에서는 세션을 가져오지 못해 로그인 상태가 유지되지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순 로그인 문제가 아니라 &lt;b&gt;백엔드 전체가 정상적으로 응답하지 못하는 상태&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 로그에서 확인된 주요 오류&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. NextAuth 에러 로그&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 시 다음과 같은 에러가 확인되었습니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[auth][error] CallbackRouteError
[cause]: connect ETIMEDOUT
[details]: { provider: &quot;google&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러는 OAuth 콜백 처리 중 &lt;b&gt;DB 연결 단계에서 타임아웃이 발생했다는 의미&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. Vercel Functions 로그&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel 로그에서는 다음과 같은 오류가 확인되었습니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;TypeError: fetch failed
Connect Timeout Error (attempted address: 43.200.169.171:8000)
code: UND_ERR_CONNECT_TIMEOUT
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 부분은 다음입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;attempted address: 43.200.169.171:8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Vercel이 특정 IP 주소(이전 EC2 퍼블릭 IP)로 계속 요청을 보내고 있었고,&lt;br /&gt;해당 주소에서 응답을 받지 못해 &lt;b&gt;연결 타임아웃&lt;/b&gt;이 발생한 상황이었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 원인 분석&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. EC2 상태 검사 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon EC2 인스턴스 상태를 확인한 결과, 상태 체크가 &lt;b&gt;2/3 통과&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;System Status Check: 통과&lt;/li&gt;
&lt;li&gt;Instance Status Check: 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instance Status Check 실패는 보통 &lt;b&gt;OS 내부 문제(네트워크, 커널, 파일시스템 등)&lt;/b&gt; 를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에서 이미 인스턴스가 정상 동작하지 않는 상태였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-2. Stop/Start 이후 퍼블릭 IP 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 위해 EC2 인스턴스를 Stop &amp;rarr; Start 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 다음과 같은 일이 발생했습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;기존 IP: 43.200.169.171
변경 후: 새로운 퍼블릭 IP 할당
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 기본 설정에서는 &lt;b&gt;Elastic IP를 사용하지 않으면 Stop/Start 시 퍼블릭 IP가 변경됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음 환경변수들은 여전히 이전 IP를 가리키고 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vercel 환경변수&lt;/li&gt;
&lt;li&gt;백엔드 .env 파일&lt;/li&gt;
&lt;li&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;3-3. 환경변수 불일치로 인한 연쇄 장애&lt;/h2&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;EC2는 새 IP로 실행 중&lt;/li&gt;
&lt;li&gt;Vercel은 이전 IP로 요청&lt;/li&gt;
&lt;li&gt;요청이 도달하지 않음&lt;/li&gt;
&lt;li&gt;Connect Timeout 발생&lt;/li&gt;
&lt;li&gt;모든 API 500 에러 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 서비스 전체가 멈춘 것처럼 보였지만, 실제로는 &lt;b&gt;프록시 연결 실패 문제&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ 실제 해결 과정 (순서대로 정리)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-1. EC2 상태 정상화&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;재부팅 및 Stop/Start 수행&lt;/li&gt;
&lt;li&gt;상태 3/3 통과 확인&lt;/li&gt;
&lt;li&gt;SSH 접속 정상 여부 재확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-2. MySQL 외부 접속 설정 점검&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 설정 파일에서 다음을 확인했습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bind-address = 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 리스닝 여부 확인:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;ss -lntp | grep 3306
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3306 포트가 정상적으로 열려 있는지 점검했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-3. MySQL 사용자 권한 재설정&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE USER IF NOT EXISTS 'aura_user'@'%' IDENTIFIED BY '비밀번호123!';
GRANT ALL PRIVILEGES ON aura_ai.* TO 'aura_user'@'%';
FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 접속이 가능하도록 권한을 명확히 재부여했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-4. 백엔드 .env 수정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MYSQL_HOST를 새 EC2 퍼블릭 IP로 변경&lt;/li&gt;
&lt;li&gt;이후 systemd 재시작&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl restart aura-api
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-5. Vercel 환경변수 수정 및 재배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel 에서 다음 항목을 수정했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MYSQL_HOST&lt;/li&gt;
&lt;li&gt;BACKEND_URL (또는 프록시 대상 URL)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 새 IP로 변경 후 &lt;b&gt;Redeploy 진행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 로그인 및 API 요청이 정상 동작하는 것을 확인했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ 최종 원인 정리&lt;/h1&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;EC2 내부 상태 이상 발생&lt;/li&gt;
&lt;li&gt;Stop/Start 수행&lt;/li&gt;
&lt;li&gt;퍼블릭 IP 변경&lt;/li&gt;
&lt;li&gt;환경변수 갱신 누락&lt;/li&gt;
&lt;li&gt;Vercel 프록시 연결 실패&lt;/li&gt;
&lt;li&gt;모든 API 500 에러&lt;/li&gt;
&lt;/ol&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;배포가 정상이어도, 인프라 IP가 변경되면 서비스는 즉시 장애 상태가 될 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ 재발 방지 조치 (완료)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-1. Elastic IP 연결 (고정 IP 적용)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic IP 를 인스턴스에 연결했습니다.&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;Stop/Start 이후에도 IP가 유지됩니다.&lt;/li&gt;
&lt;li&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;6-2. 환경변수 일원화&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;Vercel&lt;/li&gt;
&lt;li&gt;EC2&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7️⃣ 앞으로 추가로 적용하면 좋은 개선 사항&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) CloudWatch 알람 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon CloudWatch 에서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StatusCheckFailed 감지 시 즉시 알림 수신&lt;/li&gt;
&lt;li&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;2) 백엔드 헬스 체크 엔드포인트 추가&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/health
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 상태 확인 API를 추가하여 모니터링 도구와 연동 가능하도록 개선 예정입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 도메인 기반 연결 구조로 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 대신 다음과 같은 도메인을 사용하는 것이 더 안전합니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;api.example.com
&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;IP 변경 시 DNS만 수정하면 됨&lt;/li&gt;
&lt;li&gt;환경변수 변경 불필요&lt;/li&gt;
&lt;li&gt;운영 안정성 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  최종 결론&lt;/h1&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;EC2 상태 검사 실패&lt;br /&gt;&amp;rarr; IP 변경&lt;br /&gt;&amp;rarr; 환경변수 불일치&lt;br /&gt;&amp;rarr; Vercel 프록시 타임아웃&lt;/p&gt;
&lt;/blockquote&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;&lt;b&gt;Elastic IP를 통한 고정 IP 적용&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 동일한 유형의 장애는 재발하지 않도록 안정화할 수 있었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;면접에서 활용 가능한 장애 대응 스토리 버전&lt;/li&gt;
&lt;li&gt;다이어그램 포함 정리본&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도 추가로 정리해 드리겠습니다.&lt;/p&gt;</description>
      <category>챗봇 공부 노트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/50</guid>
      <comments>https://frontend-diary-log.tistory.com/50#entry50comment</comments>
      <pubDate>Wed, 25 Feb 2026 10:06:33 +0900</pubDate>
    </item>
    <item>
      <title>[Aura_Ai] [6편] 프로젝트 배포</title>
      <link>https://frontend-diary-log.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  [프로젝트 배포 후기] Next.js + FastAPI + MySQL 멀티 환경 배포 설계 &amp;amp; 장애 대응 기록&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;br /&gt;이번 글에서는 &lt;b&gt;Aura AI 프로젝트를 실제 운영 가능한 구조로 배포하면서 설계한 아키텍처와, 배포 중 발생한 장애를 해결한 과정&lt;/b&gt;을 실무 관점에서 정리했습니다.&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;실제 어떤 문제가 발생했는지&lt;/li&gt;
&lt;li&gt;어떻게 해결했는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 4가지를 중심으로 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;실서비스 운영을 가정한 배포/운영/장애 대응 경험 기록&lt;/b&gt;이라고 보시면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ 배포 구조 개요 (실무 기준 아키텍처)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  사용 스택&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Frontend&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;NextAuth.js&lt;/li&gt;
&lt;li&gt;배포: Vercel&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Backend&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FastAPI + Uvicorn&lt;/li&gt;
&lt;li&gt;배포: Amazon Web Services EC2 (Ubuntu)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Database&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;EC2 내부 localhost 운영&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Browser
   &amp;darr;
Vercel (Next.js)
   &amp;darr;
Proxy API (/api/proxy)
   &amp;darr;
EC2 FastAPI
   &amp;darr;
MySQL (localhost)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  이 구조를 선택한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &amp;ldquo;편해서&amp;rdquo;가 아니라 &lt;b&gt;운영 관점에서 다음 기준을 만족시키기 위해 선택했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ Vercel&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTPS 기본 제공&lt;/li&gt;
&lt;li&gt;CI/CD 자동화&lt;/li&gt;
&lt;li&gt;프론트 배포 속도 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ EC2&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장시간 실행되는 백엔드/DB 운영에 적합&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;확장성 &amp;uarr;&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; 구조를 그대로 채택했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 실제 배포 중 발생한 핵심 문제 &amp;amp; 해결 과정&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;문제 1️⃣ Mixed Content 오류&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;그러나 사용자 정보 API 호출 실패&lt;/li&gt;
&lt;li&gt;브라우저 콘솔: Mixed Content 에러&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;pre class=&quot;properties&quot;&gt;&lt;code&gt;Vercel &amp;rarr; HTTPS
EC2 &amp;rarr; HTTP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 정책상&lt;br /&gt;  &lt;b&gt;HTTPS 페이지에서 HTTP 요청은 보안상 차단됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vercel 내부 Proxy API 도입&lt;/h3&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;/api/proxy/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 변경:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Browser &amp;rarr; HTTPS &amp;rarr; Vercel &amp;rarr; HTTP &amp;rarr; EC2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 HTTPS만 인식하고,&lt;br /&gt;Vercel이 내부에서 백엔드로 요청을 전달하도록 설계했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ Mixed Content 완전 해결&lt;br /&gt;✔ 프론트 API 주소 통일&lt;br /&gt;✔ 서버 주소 변경 시 프록시만 수정하면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;보안 + 유지보수성 동시 개선&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;문제 2️⃣ Vercel에서 MySQL 직접 접속 실패&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 증상&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ECONNREFUSED 127.0.0.1:3306
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NextAuth 로그인 실패&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel은 서버리스 환경이므로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장기 DB 커넥션 유지 불가&lt;/li&gt;
&lt;li&gt;내부 네트워크 접근 불가&lt;/li&gt;
&lt;li&gt;로컬 MySQL 직접 연결 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;구조적으로 DB 직결이 불가능&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역할 재설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;담당역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;인증/프록시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;td&gt;DB 작업 전담&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입/유저 CRUD를 전부 백엔드로 이동했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 로그인/회원가입 정상 동작&lt;br /&gt;✔ DB 접근 로직 백엔드 집중&lt;br /&gt;✔ 보안/확장성/유지보수성 개선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;아키텍처 분리의 필요성을 체감한 케이스&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;문제 3️⃣ EC2 SSH 접속 불능&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 증상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 접속 후 화면 멈춤&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 원인&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 데몬 응답 불가&lt;/li&gt;
&lt;li&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;p data-ke-size=&quot;size16&quot;&gt;EC2 콘솔 &amp;rarr; Reboot&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요 시:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 복구&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;운영 환경에서 재부팅은 가장 빠른 해결책이라는 점을 경험&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 배포 안정성 확보 &amp;mdash; systemd 도입&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 다음 방식으로 실행했습니다.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;nohup uvicorn ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널 종료 시 불안정&lt;/li&gt;
&lt;li&gt;재부팅 시 자동 실행 안 됨&lt;/li&gt;
&lt;li&gt;운영 환경에 부적합&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 개선 &amp;rarr; systemd 서비스 등록&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;SSH 종료와 무관&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;효과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 항상 실행 상태 유지&lt;br /&gt;✔ 다운타임 최소화&lt;br /&gt;✔ 실서비스 수준 안정성 확보&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ 배포 과정에서 수정한 핵심 코드&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프록시 라우트 (route.ts)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mixed Content 해결&lt;/li&gt;
&lt;li&gt;서버측 통신 브릿지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ apiClient.ts&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 주소 /api/proxy 통일&lt;/li&gt;
&lt;li&gt;FormData 업로드 자동 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 회원가입 API&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vercel &amp;rarr; FastAPI 프록시 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ Swagger docs 대응&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;openapi.json 프록시 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;&amp;ldquo;프론트는 통신만, 비즈니스 로직은 백엔드&amp;rdquo; 원칙 확립&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ 최종 배포 체크리스트&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vercel&lt;/h2&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;NEXT_PUBLIC_API_BASE_URL=/api/proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EC2&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;systemctl status aura-api &amp;rarr; active
ss -tlnp | grep 8000 &amp;rarr; LISTEN
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기능 테스트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원가입&lt;/li&gt;
&lt;li&gt;소셜 로그인&lt;/li&gt;
&lt;li&gt;사용자 정보 조회&lt;/li&gt;
&lt;li&gt;채팅 API&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 마무리 &amp;mdash; 실무 관점에서 배운 점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 배포에서 가장 중요했던 것은 **&amp;ldquo;단순 실행&amp;rdquo;이 아니라 &amp;ldquo;운영 가능한 구조 설계&amp;rdquo;**였습니다.&lt;/p&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;Mixed Content 해결&lt;/li&gt;
&lt;li&gt;서버리스 &amp;harr; DB 구조 분리&lt;/li&gt;
&lt;li&gt;systemd 자동 실행&lt;/li&gt;
&lt;li&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;p data-ke-size=&quot;size16&quot;&gt;  단순 구현이 아닌&lt;br /&gt;  &lt;b&gt;실서비스 운영을 고려한 배포/아키텍처 설계 경험을 쌓을 수 있었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트였지만,&lt;br /&gt;&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;</description>
      <category>코디챗봇 프로젝트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/49</guid>
      <comments>https://frontend-diary-log.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 13 Feb 2026 17:58:52 +0900</pubDate>
    </item>
    <item>
      <title>[19편] part 4 배포  배포 명령어/의미 + 오류 로그 대응표 + 체크리스트</title>
      <link>https://frontend-diary-log.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Aura AI 배포 기록 (Part 4)&lt;/h1&gt;
&lt;h1&gt;배포 명령어 정리 &amp;middot; 의미 설명 &amp;middot; 장애 대응 매뉴얼 &amp;middot; 운영 체크리스트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 &lt;b&gt;FastAPI + Next.js + Vercel + Amazon Web Services EC2 환경에서 실제 운영 시 사용하는 배포 명령어와 장애 대응 방법&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;/li&gt;
&lt;li&gt;실행/재시작/백그라운드 실행&lt;/li&gt;
&lt;li&gt;systemd 영구 실행&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;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 1️⃣ 배포용 핵심 명령어 + 의미 정리&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  EC2 서버 접속&lt;/h2&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;ssh -i &quot;C:\Users\dddd3\aura_ai\aura-key.pem&quot; ubuntu@51.21.202.235
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2 인스턴스 접속&lt;/li&gt;
&lt;li&gt;PEM 키 기반 인증 (비밀번호 대신 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 포인트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ PEM 없으면 접속 불가&lt;br /&gt;✔ 경로는 반드시 따옴표 사용&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  백엔드 폴더 이동&lt;/h2&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;cd ~/aura_ai/backend
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FastAPI 프로젝트 위치로 이동&lt;/li&gt;
&lt;li&gt;uvicorn 실행 기준 디렉토리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  가상환경 활성화&lt;/h2&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python 가상환경 활성화&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;p data-ke-size=&quot;size16&quot;&gt;✔ 시스템 Python 오염 방지&lt;br /&gt;✔ 의존성 충돌 방지&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  패키지 설치&lt;/h2&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 의존성 전체 설치&lt;/li&gt;
&lt;/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;requirements 수정 후&lt;/li&gt;
&lt;li&gt;서버 재구성 후&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  서버 실행 (임시 실행)&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;uvicorn main:app --host 0.0.0.0 --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FastAPI 서버 실행&lt;/li&gt;
&lt;li&gt;0.0.0.0 &amp;rarr; 외부 접속 허용&lt;/li&gt;
&lt;li&gt;8000 포트 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ 터미널 종료 시 서버 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  테스트용/디버깅용&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  서버 실행 (백그라운드 임시)&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;nohup uvicorn main:app --host 0.0.0.0 --port 8000 &amp;gt; uvicorn.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널 닫아도 유지&lt;/li&gt;
&lt;li&gt;로그를 uvicorn.log에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 간단한 임시 운영 가능&lt;br /&gt;❌ 재부팅 시 자동 시작 안 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  임시 운영 전용&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  프로세스 종료&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;pkill -f uvicorn
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 중인 uvicorn 프로세스 전체 종료&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;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  ⭐ 권장 방식 &amp;mdash; systemd (영구 실행)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 서버는 반드시 systemd 사용 권장합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  systemd 서비스 등록&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo tee /etc/systemd/system/aura-api.service &amp;gt; /dev/null &amp;lt;&amp;lt;'EOF'
[Unit]
Description=Aura FastAPI (Uvicorn)
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/aura_ai/backend
EnvironmentFile=/home/ubuntu/aura_ai/backend/.env
ExecStart=/home/ubuntu/aura_ai/backend/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&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;핵심 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 서버 부팅 시 자동 실행&lt;br /&gt;✔ 오류 발생 시 자동 재시작&lt;br /&gt;✔ SSH 종료와 무관하게 유지&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WorkingDirectory&lt;/td&gt;
&lt;td&gt;프로젝트 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EnvironmentFile&lt;/td&gt;
&lt;td&gt;.env 자동 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ExecStart&lt;/td&gt;
&lt;td&gt;서버 실행 명령&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart=always&lt;/td&gt;
&lt;td&gt;항상 재시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RestartSec&lt;/td&gt;
&lt;td&gt;재시작 딜레이&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  systemd 적용/실행&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable aura-api
sudo systemctl restart aura-api
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 서비스 등록 적용&lt;/li&gt;
&lt;li&gt;부팅 시 자동 실행 설정&lt;/li&gt;
&lt;li&gt;즉시 서버 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실행 상태 확인&lt;/h2&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;sudo systemctl status aura-api --no-pager
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 실행 여부 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정상 상태&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;active (running)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  포트 리스닝 확인&lt;/h2&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;sudo ss -tlnp | grep 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;8000 포트가 열렸는지 확인&lt;/li&gt;
&lt;li&gt;uvicorn 실제 실행 여부 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 2️⃣ 오류 로그 대응표 (실전 트러블슈팅)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 중 가장 많이 겪은 오류만 모았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ Mixed Content 오류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;증상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS 페이지에서 HTTP API 호출 시 차단&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ /api/proxy 적용&lt;br /&gt;✔ 프론트는 프록시 경유만 사용&lt;br /&gt;✔ Vercel 내부 HTTPS 브릿지 활용&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ ECONNREFUSED 127.0.0.1:3306&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;증상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 접속 실패&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel 서버리스 &amp;rarr; DB 직접 연결 불가&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ DB 접근은 전부 FastAPI 로 이동&lt;br /&gt;✔ Vercel은 프록시만 담당&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ Form data requires python-multipart&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;pip install python-multipart
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ ModuleNotFoundError: bcrypt&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;pip install bcrypt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식 이중 이스케이프&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;EMAIL_REGEX = re.compile(r&quot;^[^\s@]+@[^\s@]+\.[^\s@]+$&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ SSH 접속 멈춤&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ EC2 콘솔 &amp;rarr; Reboot&lt;br /&gt;✔ 또는 SSH 재시작&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 3️⃣ 배포 최종 체크리스트 (운영 전 필수)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 완료 후 반드시 확인합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Vercel 환경 변수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NEXT_PUBLIC_API_BASE_URL=/api/proxy&lt;/li&gt;
&lt;li&gt;백엔드 서버: 51.21.202.235:8000&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  EC2 상태 확인&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;systemctl status aura-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  active (running)&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;ss -tlnp | grep 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  uvicorn 리스닝 확인&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  기능 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 회원가입 성공&lt;br /&gt;✔ 소셜 로그인 성공&lt;br /&gt;✔ /users/me 정상&lt;br /&gt;✔ /sessions 정상&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ Part 4 전체 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 파트에서 정리한 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 서버 접속/실행 명령어 정리&lt;br /&gt;✔ systemd 영구 실행 설정&lt;br /&gt;✔ 실제 발생 오류 대응법&lt;br /&gt;✔ 운영 체크리스트&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  최종 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  운영 환경에서는 반드시&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;systemd 사용&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;이 4가지를 습관화하면 장애가 거의 발생하지 않습니다.&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;</description>
      <category>챗봇 공부 노트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/48</guid>
      <comments>https://frontend-diary-log.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 13 Feb 2026 17:53:42 +0900</pubDate>
    </item>
    <item>
      <title>[19편] part 3 배포 백엔드/회원가입/정규식/의존성 변경 상세</title>
      <link>https://frontend-diary-log.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  Aura AI 배포 기록 (Part 3)&lt;/h1&gt;
&lt;h1&gt;백엔드 회원가입 API &amp;middot; 정규식 오류 &amp;middot; 의존성 &amp;middot; 서비스 레이어 변경 총정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 &lt;b&gt;FastAPI 기반 백엔드에서 회원가입 기능을 직접 구현하면서 발생한 문제들과 구조 개선 과정&lt;/b&gt;을 정리한 기록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Part 1에서는 서버/배포/운영,&lt;br /&gt;Part 2에서는 프론트/프록시 구조를 다뤘다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Part 3는 &amp;ldquo;백엔드 비즈니스 로직 설계 + 보안 + 검증 + DB 처리&amp;rdquo; 핵심 파트입니다.&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;회원가입 API 신규 구현&lt;/li&gt;
&lt;li&gt;정규식 검증 오류 해결&lt;/li&gt;
&lt;li&gt;user_service 리팩토링&lt;/li&gt;
&lt;li&gt;requirements 의존성 추가 이유&lt;/li&gt;
&lt;li&gt;전체 회원가입 처리 흐름&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ Part 3 핵심 목표&lt;/h1&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;Vercel에서 DB 직접 접근 불가&lt;/li&gt;
&lt;li&gt;프론트에서 회원가입 처리 시 보안 취약&lt;/li&gt;
&lt;li&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;p data-ke-size=&quot;size16&quot;&gt;✔ 회원가입 로직 100% 백엔드 처리&lt;br /&gt;✔ 입력 검증 + 암호화 + DB 저장 일원화&lt;br /&gt;✔ OAuth/일반 가입 스키마 통합&lt;br /&gt;✔ 운영 안정성 확보&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ auth.py &amp;mdash; 회원가입 API 추가&lt;/h1&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;기존 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vercel 서버리스 환경은 MySQL 직접 연결 불가&lt;/li&gt;
&lt;li&gt;프론트에서 DB 로직 처리 시 보안 위험&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;pre class=&quot;excel&quot;&gt;&lt;code&gt;프론트 &amp;rarr; DB 직접 접근 ❌ (구조적으로 불가능 + 위험)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;회원가입을 전부 백엔드에서 처리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증&lt;/li&gt;
&lt;li&gt;암호화&lt;/li&gt;
&lt;li&gt;DB 저장&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;h2 data-ke-size=&quot;size26&quot;&gt;✅ 추가된 핵심 구조&lt;/h2&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;@router.post(&quot;/signup&quot;)
def signup(body: SignupRequest):
    ...
&lt;/code&gt;&lt;/pre&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;/signup POST JSON 요청 수신&lt;/li&gt;
&lt;li&gt;서버에서 입력값 직접 검증&lt;/li&gt;
&lt;li&gt;안전하게 DB 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;회원가입의 단일 진입점(Single Entry Point)&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ 입력값 검증 로직 (정규식)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 적용된 검증 규칙&lt;/h2&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;EMAIL_REGEX = re.compile(r&quot;^[^\s@]+@[^\s@]+\.[^\s@]+$&quot;)
PHONE_REGEX = re.compile(r&quot;^01[016789]-?\d{3,4}-?\d{4}$&quot;)
PASSWORD_REGEX = re.compile(r&quot;^(?=.*[A-Za-z])(?=.*\d).{8,}$&quot;)
&lt;/code&gt;&lt;/pre&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;이메일&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;user@domain.com 형식만 허용
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전화번호&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;010-1234-5678
01012345678
둘 다 허용
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비밀번호&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;8자 이상 + 영문 포함 + 숫자 포함 필수
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 효과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 서버 단에서 강력한 입력 검증&lt;br /&gt;✔ 잘못된 데이터 DB 유입 차단&lt;br /&gt;✔ 보안 강화&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ 정규식 오류 문제 (이메일 검증 실패)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 발생한 이상 현상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상 이메일도 실패&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;asdf@naver.com &amp;rarr; &quot;이메일 형식이 올바르지 않습니다&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식이 &lt;b&gt;이중 이스케이프&lt;/b&gt; 상태로 저장됨&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잘못된 코드&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;EMAIL_REGEX = re.compile(r&quot;^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❗ 문제점&lt;/h2&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;\\s
&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;공백 문자 ❌&lt;/li&gt;
&lt;li&gt;문자 &quot;&quot; + &quot;s&quot; 로 인식&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;h2 data-ke-size=&quot;size26&quot;&gt;✅ 수정 후 (정상)&lt;/h2&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;EMAIL_REGEX = re.compile(r&quot;^[^\s@]+@[^\s@]+\.[^\s@]+$&quot;)
PHONE_REGEX = re.compile(r&quot;^01[016789]-?\d{3,4}-?\d{4}$&quot;)
PASSWORD_REGEX = re.compile(r&quot;^(?=.*[A-Za-z])(?=.*\d).{8,}$&quot;)
&lt;/code&gt;&lt;/pre&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;핵심 포인트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Raw string(r&quot;&quot;)에서는&lt;/li&gt;
&lt;li&gt;\s, \. 그대로 사용해야 함&lt;/li&gt;
&lt;li&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON 직렬화&lt;/li&gt;
&lt;li&gt;복붙 과정&lt;/li&gt;
&lt;li&gt;문자열 escape 중복&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;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 이메일 검증 정상 동작&lt;br /&gt;✔ 회원가입 성공&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ user_service.py 변경&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 왜 변경했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구조는:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;OAuth 로그인 시에만 INSERT
&lt;/code&gt;&lt;/pre&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 추가된 핵심 기능&lt;/h2&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;def create_user(...):
    # 중복 이메일/닉네임 확인
    # 비밀번호 해시 저장
    # provider = &quot;credentials&quot;
&lt;/code&gt;&lt;/pre&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;1️⃣ 중복 검사&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일 중복 체크&lt;/li&gt;
&lt;li&gt;닉네임 중복 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 비밀번호 암호화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평문 저장 ❌&lt;/li&gt;
&lt;li&gt;해시 저장 ⭕&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ provider 설정&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;credentials
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 가입 구분&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 구조적 장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ 가장 중요한 포인트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  OAuth / 일반 가입 모두 &lt;b&gt;하나의 users 테이블 사용&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;br /&gt;✔ 유지보수 쉬움&lt;br /&gt;✔ 인증 방식 추가 쉬움&lt;br /&gt;✔ DB 단순화&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ requirements.txt 변경 (의존성 추가)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 추가된 패키지&lt;/h2&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;bcrypt
python-multipart
&lt;/code&gt;&lt;/pre&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;  bcrypt&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 해시 암호화&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;password &amp;rarr; hash &amp;rarr; DB 저장
&lt;/code&gt;&lt;/pre&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;  python-multipart&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;프로필 이미지&lt;/li&gt;
&lt;li&gt;FormData 요청&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;h2 data-ke-size=&quot;size26&quot;&gt;❌ 실제 발생한 에러&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;bcrypt 누락&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;ModuleNotFoundError: No module named 'bcrypt'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;multipart 누락&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;Form data requires &quot;python-multipart&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결&lt;/h2&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  교훈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  FastAPI + 파일 업로드 = python-multipart 거의 필수&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ main.py 변경&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 왜 수정했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터를 만들어도 등록하지 않으면 동작하지 않습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 추가 코드&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;from routers.auth import router as auth_router
app.include_router(auth_router)
&lt;/code&gt;&lt;/pre&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;auth.py 연결&lt;/li&gt;
&lt;li&gt;/signup 엔드포인트 실제 활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7️⃣ 회원가입 전체 흐름 정리&lt;/h1&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;① 프론트&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/api/signup 호출
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Vercel&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/signup &amp;rarr; EC2 프록시
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ FastAPI auth.py&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;bcrypt 해시&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;④ user_service.py&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;DB INSERT&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑤ 성공 응답&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{ &quot;message&quot;: &quot;회원가입 완료&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8️⃣ 왜 이 구조가 가장 효율적인가?&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 프론트 처리 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안 취약&lt;/li&gt;
&lt;li&gt;로직 노출&lt;/li&gt;
&lt;li&gt;DB 직접 접근 위험&lt;/li&gt;
&lt;li&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;p data-ke-size=&quot;size16&quot;&gt;✔ 보안 강화&lt;br /&gt;✔ 검증/저장 통합&lt;br /&gt;✔ 비즈니스 로직 집중&lt;br /&gt;✔ OAuth/일반 가입 통합&lt;br /&gt;✔ 확장성 &amp;uarr;&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;회원가입은 반드시 서버 책임으로 처리하는 것이 정석 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ Part 3 전체 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 파트에서 수행한 작업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ auth.py 회원가입 API 구현&lt;br /&gt;✔ 정규식 검증 추가&lt;br /&gt;✔ 이중 이스케이프 버그 수정&lt;br /&gt;✔ user_service 생성 로직 추가&lt;br /&gt;✔ bcrypt / multipart 의존성 추가&lt;br /&gt;✔ 라우터 등록&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  최종 결과&lt;/h2&gt;
&lt;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;OAuth/일반 가입 통합&lt;/li&gt;
&lt;li&gt;운영 안정성 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>챗봇 공부 노트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/47</guid>
      <comments>https://frontend-diary-log.tistory.com/47#entry47comment</comments>
      <pubDate>Fri, 13 Feb 2026 17:52:25 +0900</pubDate>
    </item>
    <item>
      <title>[19편] part 2 배포 프론트/프록시 구조 변경 상세 정리</title>
      <link>https://frontend-diary-log.tistory.com/46</link>
      <description>&lt;h1&gt;  Aura AI 배포 기록 (Part 2)&lt;/h1&gt;
&lt;h1&gt;프론트 구조 &amp;amp; Vercel 프록시 아키텍처 변경 상세 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 &lt;b&gt;Next.js + NextAuth.js + FastAPI + Vercel + Amazon Web Services EC2&lt;/b&gt; 환경에서&lt;br /&gt;프론트엔드 통신 구조를 개선하며 &lt;b&gt;Mixed Content 문제를 완전히 해결한 과정&lt;/b&gt;을 정리한 실전 기록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Part 1이 서버/배포/운영 중심이었다면,&lt;br /&gt;이번 글은 &lt;b&gt;프론트 코드 레벨 구조 개선 + 프록시 설계 이유 + 실제 수정 내역&lt;/b&gt;을 다룹니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ Part 2 핵심 목표&lt;/h1&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;브라우저에서 HTTP 백엔드 직접 호출 시 Mixed Content 차단 발생&lt;/li&gt;
&lt;li&gt;일부 API/업로드/Swagger/docs 동작 실패&lt;/li&gt;
&lt;li&gt;API 주소가 여기저기 흩어져 유지보수 어려움&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;p data-ke-size=&quot;size16&quot;&gt;✔ 모든 요청을 HTTPS 내부에서만 처리&lt;br /&gt;✔ 브라우저 &amp;rarr; Vercel &amp;rarr; EC2 구조 확립&lt;br /&gt;✔ API 호출 방식 통일&lt;br /&gt;✔ 유지보수성 향상&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1️⃣ route.ts (프록시 라우트) 추가&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 왜 추가했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 보안 정책 때문입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 구조&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;브라우저 &amp;rarr; http://EC2:8000 직접 호출
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우:&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Mixed Content 차단 발생 ❌
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 &lt;b&gt;HTTPS 페이지에서 HTTP 요청을 보안상 차단&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 구조적으로 직접 호출은 불가능합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 전략&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vercel 내부 프록시 생성&lt;/h3&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;/api/proxy/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 변경:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;브라우저 &amp;rarr; Vercel(HTTPS) &amp;rarr; EC2(HTTP)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 &amp;harr; 서버 통신은 Mixed Content 제한이 없습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 무슨 의미인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/api/proxy/* 는&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;/p&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;예시&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;GET /api/proxy/users/me
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 클라이언트 &amp;rarr; Vercel&lt;br /&gt;2️⃣ Vercel &amp;rarr; &lt;a href=&quot;http://ec2:8000/users/me&quot;&gt;http://EC2:8000/users/me&lt;/a&gt;&lt;br /&gt;3️⃣ 응답 그대로 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 HTTPS만 보게 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ Mixed Content 완전 차단&lt;br /&gt;✔ 백엔드 주소 숨김 (보안 &amp;uarr;)&lt;br /&gt;✔ API 주소 통일&lt;br /&gt;✔ 서버 주소 변경 시 프록시만 수정하면 됨&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2️⃣ apiClient.ts 수정&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 왜 수정했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시를 도입했다면&lt;br /&gt;&lt;b&gt;모든 API 호출이 반드시 /api/proxy를 타야 합니다.&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;a href=&quot;http://ip/&quot;&gt;http://IP&lt;/a&gt; 호출&lt;/li&gt;
&lt;li&gt;fetch/axios 혼용&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;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;1. baseURL 통일&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;baseURL: &quot;/api/proxy&quot;
&lt;/code&gt;&lt;/pre&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;2. FormData 처리 방식 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존:&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;headers: { &quot;Content-Type&quot;: &quot;multipart/form-data&quot; }
&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;브라우저가 boundary 자동 생성 못함&lt;/li&gt;
&lt;li&gt;파일 업로드 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정:&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;FormData일 경우 Content-Type 설정 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 자동으로 처리하도록 위임&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ JSON 요청 정상&lt;br /&gt;✔ 파일 업로드 정상&lt;br /&gt;✔ 헤더 오류 제거&lt;br /&gt;✔ 코드 일관성 확보&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 효과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;모든 통신을 apiFetch 하나로 통일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 난이도가 크게 감소했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3️⃣ Swagger docs(/docs) 프록시 처리&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 왜 문제였는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 Swagger는 내부적으로:&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/openapi.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 호출합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;/api/proxy/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 접속하면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;openapi.json &amp;rarr; Vercel 기준 경로 &amp;rarr; 404
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발생&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 방법&lt;/h2&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/openapi.json도 프록시 통과
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 하위 경로를 그대로 전달하도록 route.ts 확장&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;/api/proxy/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서도 Swagger UI 정상 표시&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 개발/테스트 환경 유지 가능&lt;br /&gt;✔ 문서 접근성 확보&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4️⃣ /api/signup 프록시 처리&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 왜 수정했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입은 DB 접근이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;Vercel 서버리스 환경은 MySQL 직접 연결 불가&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Vercel &amp;rarr; MySQL ❌
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 로직을 전부 &lt;b&gt;FastAPI&lt;/b&gt; 로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel은 단순 전달만 수행&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 흐름&lt;/h2&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/api/signup &amp;rarr; Vercel &amp;rarr; EC2 /signup
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ DB 접근 안정성 확보&lt;br /&gt;✔ 인증/비즈니스 로직 분리&lt;br /&gt;✔ 프론트 코드는 변경 없음&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5️⃣ LocationModal.tsx 수정&lt;/h1&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;API 직접 호출 (HTTP)&lt;/li&gt;
&lt;li&gt;Mixed Content 위험&lt;/li&gt;
&lt;li&gt;한글/문자열 깨짐&lt;/li&gt;
&lt;li&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;1. apiFetch 사용&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;직접 호출 &amp;rarr; apiFetch
&lt;/code&gt;&lt;/pre&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;2. 깨진 텍스트 복구&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한글 인코딩 문제 해결&lt;/li&gt;
&lt;li&gt;UI 문구 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 검색 결과 처리 안정화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 핸들링 추가&lt;/li&gt;
&lt;li&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;p data-ke-size=&quot;size16&quot;&gt;✔ Mixed Content 예방&lt;br /&gt;✔ UI 정상화&lt;br /&gt;✔ 유지보수 쉬움&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6️⃣ ProfileSection.tsx 수정&lt;/h1&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;이미지 업로드/삭제 &amp;rarr; HTTP 직접 호출&lt;/li&gt;
&lt;li&gt;Mixed Content 발생&lt;/li&gt;
&lt;li&gt;문자열 깨짐&lt;/li&gt;
&lt;li&gt;UX 혼란&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;1. 업로드/삭제 &amp;rarr; apiFetch 통일&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 이미지 URL도 프록시 기반&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/api/proxy/uploads/...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 텍스트/문구 정리&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ HTTPS 환경 정상 동작&lt;br /&gt;✔ 파일 업로드 안정성 확보&lt;br /&gt;✔ 사용자 경험 개선&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7️⃣ 왜 프록시 구조가 가장 효율적인가?&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 대안 1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 직접 HTTPS 구축&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nginx 설정&lt;/li&gt;
&lt;li&gt;Let's Encrypt + Certbot&lt;/li&gt;
&lt;li&gt;도메인 구매&lt;/li&gt;
&lt;li&gt;SSL 관리&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;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 현재 방식 (Vercel Proxy)&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;✔ SSL 자동 처리&lt;br /&gt;✔ 구현 매우 간단&lt;br /&gt;✔ 유지보수 최소화&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;  &lt;b&gt;구현 난이도 대비 효과가 가장 좋은 선택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업/개인 프로젝트에 최적의 구조입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ Part 2 전체 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 파트에서 수행한 작업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ Vercel Proxy Route 도입&lt;br /&gt;✔ 모든 API 호출 apiFetch 통일&lt;br /&gt;✔ Mixed Content 완전 해결&lt;br /&gt;✔ Swagger 문서 프록시 처리&lt;br /&gt;✔ 회원가입 API 백엔드 이관&lt;br /&gt;✔ Location/Profile 컴포넌트 안정화&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTPS 환경 100% 호환&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;</description>
      <category>챗봇 공부 노트</category>
      <author>frontend-diary-log</author>
      <guid isPermaLink="true">https://frontend-diary-log.tistory.com/46</guid>
      <comments>https://frontend-diary-log.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 13 Feb 2026 17:50:43 +0900</pubDate>
    </item>
  </channel>
</rss>