OpenAI API 스트리밍 응답 처리 중 연결 끊김 문제 해결
문제 상황
챗봇 서비스에서 OpenAI API의 스트리밍 응답을 처리하는 중 사용자가 긴 응답을 받을 때 간헐적으로 연결이 끊기는 이슈가 발생했다. 특히 1분 이상 소요되는 응답에서 문제가 두드러졌다.
원인 분석
- Vercel 함수 타임아웃: 무료 플랜의 10초, Pro 플랜의 60초 제한
- 네트워크 계층 타임아웃: 중간 프록시나 로드밸런서의 idle timeout
- 클라이언트 측 재연결 로직 부재
해결 방법
1. Edge Function으로 전환
// app/api/chat/route.ts
export const runtime = 'edge';
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o',
messages,
stream: true,
}),
});
return new Response(response.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
2. 클라이언트 재연결 처리
const fetchWithRetry = async (retries = 3) => {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ messages }),
signal: AbortSignal.timeout(120000),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader!.read();
if (done) break;
const chunk = decoder.decode(value);
processChunk(chunk);
}
return;
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
};
3. Heartbeat 메커니즘
OpenAI API는 자체적으로 주기적인 데이터를 보내지만, 필요시 서버에서 명시적인 keep-alive를 추가할 수 있다.
const encoder = new TextEncoder();
const heartbeat = setInterval(() => {
controller.enqueue(encoder.encode(': heartbeat\n\n'));
}, 15000);
결과
Edge Function으로 전환 후 타임아웃 문제가 해결되었고, 재연결 로직으로 네트워크 불안정 상황에서도 사용자 경험이 개선되었다. 모니터링 결과 평균 응답 완료율이 94%에서 99.2%로 향상되었다.