개요
Next 프로젝트에 사용자경험 증대를 위해 Suspense의 스트리밍 기능을 도입했습니다.
하지만 로컬 환경에서 정상동작하던 스트리밍이 배포환경에서 제대로 동작하지 않는 문제가 있었습니다.
원인
로컬에선 정상동작하는데 배포환경에선 안되는게 상당히 의문이었습니다.
개발 환경과 로컬의 차이가 무엇인지 생각하던 중, 개발 환경에서는 Next.js 프로젝트 앞단에 nginx가 있다는 점을 알게 되었고, 그걸 단서로 원인을 찾다가 github 이슈를 발견했습니다.
nginx란
nginx는 클라이언트와 백엔드 서버 사이에서 동작하는 웹 서버입니다. 여러 클라이언트의 요청을 효율적으로 처리하기 위해 비동기·이벤트 기반 아키텍처를 사용합니다.
때문에 클라이언트 커넥션 한 개마다 한 개의 스레드를 생성하는 다른 아키텍처와 달리 한개의 쓰레드에서 비동기 이벤트 방식으로 여러 커넥션을 처리할 수 있고, 대규모 클라이언트의 웹서버 역할을 하는데 훨씬 효율적입니다.
nginx buffer
그렇다면 어떻게 소수의 쓰레드를 사용하면서도 커넥션마다 블로킹 없이 비동기 방식으로 요청, 응답을 할 수 있을까요?
바로 버퍼 공간을 활용합니다.
nginx는 백엔드 서버로부터 받은 응답을 내부 버퍼에 저장하고, 전체 응답이 버퍼링될 때까지 클라이언트에 데이터 전송을 시작하지 않습니다. 이를 버퍼링이라고 합니다.
버퍼 공간을 활용해 백엔드는 nginx 에게 클라이언트의 수신 속도를 고려하지 않고 응답을 그냥 던져주고, nginx 는 그것들을 버퍼에 저장했다가 클라이언트의 수신 속도에 맞추어 보내주면 되죠.
이렇게 하면, 백엔드 서버는 불필요한 유휴 시간을 줄일 수 있다는 장점이 있습니다.
반대로 버퍼링이 비활성화 상태라면, nginx 는 백엔드 서버의 응답을 받는 즉시 클라이언트에 동기적으로 전송합니다.
이 과정에서 백엔드 서버는 nginx 가 다음 응답 세그먼트를 받아들일 수 있을 때까지 대기해야 합니다. 불필요한 유휴 시간이 발생하는 것이죠.
따라서, nignx 버퍼링 기능은 근본적으로 백엔드 서버의 불필요한 부하를 줄이기 위한 것이지 클라이언트를 위한 것이 아닙니다. 오히려, 응답을 다 받아야지(혹은 버퍼가 가득 차면) 전송을 시작하기 때문에 빠른 클라이언트에서는 상대적으로 느린 응답을 받을 수 있습니다.
그래서 nginx는 buffering 기능이 기본적으로 on 상태입니다. 이는, 서버로부터의 응답을 내부 버퍼에 저장하고, 전체 응답이 버퍼링될 때까지 클라이언트에 데이터를 전송하지 않는다는 의미죠.
Next.js 스트리밍
스트리밍은 라우트를 더 작은 청크로 나누고, 이를 점진적으로 전송하는 기술입니다. 이로써 사용자는 모든 데이터가 로드 될때까지 기다리지 않고서도 페이지의 일부를 보고 상호작용 할 수 있는 장점이 있습니다.
React에서 제공하는 Suspense를 활용해 Next 프로젝트에서 스트리밍 기능을 구현할 수 있는데요. 앞서 본 nignx의 버퍼링은 이러한 스트리밍을 막습니다.
프론트엔드(Next.js) 서버는 스트리밍으로 응답을 보내고 있지만, 그 응답을 브라우저로 전달하는 nginx가 버퍼를 사용해 응답을 모두 모은 뒤에야 클라이언트로 보냅니다. 그 결과, 응답이 한 번에 도착하게 되어 Next.js 입장에서는 fallback UI를 보여줘야 하는 중간 상태를 구분할 수 없게 됩니다.
해결
따라서, nginx 와 같이 서버 응답을 버퍼링해놓는 프록시를 사용한다면 버퍼링 기능을 막아야지 스트리밍을 제대로 사용할 수 있습니다.
Nextjs 공식문서의 deploy의 streaming and suspense를 보면 스트리밍을 지원하기 위한 내용이 있는데요.
nginx 프록시를 사용하는 경우 버퍼링을 비활성화 해야한다 명시되어있어 next.config 파일을 다음과 같이 수정해 이슈를 해결했습니다.
async headers() {
return [
// 나머지 속성
{
source: '/:path*{/}?',
headers: [{ key: 'X-Accel-Buffering', value: 'no' }],
},
]
},
궁금한 점
상술했듯이 nginx 버퍼링 기능은 백엔드 서버 부하를 줄이는 역할인데 스트리밍을 도입하면 백엔드 서버에 부하가 높아지는 것 아닐까라는 생각이 들었습니다. next.js 의 스트리밍이 정확히 어떤 방식으로 이루어지는지 더 깊이 알아보고 이것이 서버 성능 및 부하와 어떤 연관이 있는지 좀 더 알아봐야할 것 같습니다.
스트리밍을 지원하기 위해서는 버퍼링을 막아야 하는데, 이렇게 되면 백엔드 서버에 부하가 높아지는 것 아닐까? next.js 의 스트리밍이 정확히 어떤 방식으로 이루어지는지 더 깊이 알아보고 이것이 서버 성능 및 부하와 어떤 연관이 있는지 공부해야할 듯 싶습니다.