본문 바로가기

Graduation Project

응답 스트리밍(2) - SSE

영상 생성 기능이 약 3분 이상 소요되기 때문에 진행 상황을 클라이언트에게 공유하고자 응답 스트리밍을 구현하고자 한다. 일단 사용하던 Lambda에 있는 기능인 응답 스트리밍을 사용하고자 했는데 청크가 뭉쳐서 받아져오는 문제가 있어 일단 보류.... 그 다음 생각해낸 것이 SSE이다. 

 

Server Sent Events

요청을 보낸 후 일정 시간동안 연결을 유지한 후, 서버에서 message를 emit하는 방식이다.

연결이 끊기기 전까지는 Client가 추가로 요청할 필요가 없기 때문에 메시지가 쌓일 수록 0.5RTT만큼의 시간이 절약된다고 생각할 수 있다. 이후 서버에서 message를 전송하면 클라이언트는 WebSocket 통신과 유사한 방식으로 메시지를 받을 수 있으나, 클라이언트에서 서버로 메시지를 전송할 순 없다.

 

우리 프로젝트는

1. 클라이언트에서 메세지를 보낼 필요가 없고,

2. 특정 시간 동안만 연결을 유지하고 작업이 완료되면 연결을 해제하면 되고,

3. 연결이 유지되 기간 동안 지속적으로 메세지를 전달해야하기 때문에,

SSE가 적합하다고 생각했다. 

 


참고한 블로그

더 자세한 내용을 포함하고 있으니 꼭 참고하자

 

https://velog.io/@yisuho/OpenAI-API-%EC%9D%91%EB%8B%B5-%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D-%EA%B5%AC%ED%98%84

 

OpenAI API 응답 스트리밍 구현

프로젝트에서 OpenAI를 활용해 면접 질문에 대한 답변과 그 답변의 피드백을 제공하는 기능을 구현했다. 그러나 답변의 길이가 매우 길어져 사용자가 완성된 답변을 받기까지의 대기 시간이 길어

velog.io

https://velog.io/@reljacer/SSE%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC

 

 작업이 오래 걸리는 서비스에 대해 어떻게 대응 할 것인가

팀 프로젝트 진행 중, 내가 맡은 서비스가 최장 5분까지 시간이 소요되는 일이 있었다.노션의 API를 호출해서 특정 사용자의 모든 데이터를 읽어오고, 이를 가공하기 위해 Fastapi 서버에 자연어 처

velog.io


SSE 연결 시 고려해야 할 것들

Response 헤더 설정

 

  • Content-Type : text/event-stream
    • 표준으로 지정되어 있다.
      text형식의 간단한 stream으로 구성되어 있으며, 각 데이터는 \n\n로 구분된다.
  • Connection : keep-alive
    • SSE의 persistent connection을 위해 keep-alive로 지정해야 한다.
  • Transfer-Encoding : chunked
    • SSE는 동적으로 생성된 컨텐츠를 스트리밍하기 때문에 서버에서 미리 본문의 길이를 알 수 없다. 따라서 청크단위로 끊어서 보낸다.
  • charset : UTF-8
    • event-stream은 binary로 해석될 수 없으며, utf-8방식으로 인코딩이 되어야 한다.

 

SSE 데이터 구조

event: <client에서 처리할 이벤트 명>
data: <메시지 내용>
\n\n

 

 

 

간단한 테스트 코드 구현

//handler: ./src/index.handler
import controller from './controller.js';
import service from './service.js';

const OK = 200;
const CREATED =  201;
const UNAUTHORIZED = 401;
const BAD_REQUEST = 400;
const NOT_FOUND = 404;
const INTERNAL_SERVER_ERROR = 500;

export const handler = awslambda.streamifyResponse(async (event, responseStream, _context) => {
   const metadata = {
      statusCode: 200,
      headers: {
        "Content-Type": "text/event-stream",
        "Connection": "keep-alive",
        "charset": "UTF-8",
        "Transfer-Encoding": "chunked",
        "X-Accel-Buffering": "no"
      }
    };

    // Assign to the responseStream parameter to prevent accidental reuse of the non-wrapped stream.
    responseStream = awslambda.HttpResponseStream.from(responseStream, metadata);

    responseStream.write("data: Streaming with Helper \n\n");
    await new Promise(r => setTimeout(r, 3000));
    responseStream.write("data: Hello 0 \n\n");
    await new Promise(r => setTimeout(r, 3000));
    responseStream.write("data: Hello 1 \n\n");
    await new Promise(r => setTimeout(r, 3000));
    responseStream.write("data: Hello 3 \n\n");
    await new Promise(r => setTimeout(r, 3000));
    responseStream.write("data: Hello 5 \n\n");
    await new Promise(r => setTimeout(r, 3000));
    responseStream.write("data: Hello 7 \n\n");
    await new Promise(r => setTimeout(r, 3000));
    responseStream.end();
    //await responseStream.finished();
  }
);

 

 

이렇게 수정해도 청크가 뭉쳐서 오는 이슈는 아직도 존재했다. 아마 Lambda를 사용하기 때문에 이렇 문제가 SSE를 사용해도 그대로 발생하는 것 같다. 그리고 프론트에서 화면 이동을 하면 이 연결이 끊긴다고 말해서 결국은 웹소켓을 통한 연결을 시도해보기로 했다.