NESTJS

Lambda에 올라간 NestJS에서 큰 용량의 다운로드가 필요하게 되었다

김창섭 프로필 이미지

김창섭

2023.08.24

9 min read

Lambda에 올라간 NestJS에서 큰 용량의 다운로드가 필요하게 되었다

안녕하세요. 저는 코멘토에서 백엔드를 담당하는 김창섭이라고 합니다. 이번에 NestJS에서 파일 다운로드 관련 수정사항이 있었는데요. 짧지만 그 내용을 공유해보고자 합니다.

우선 코멘토 백엔드에 조금이라도 관심이 있으시다면 의아한 부분이 있을텐데요. 저희 코멘토에서 메인으로 쓰는 백엔드 언어는 PHP입니다. 그런데 NestJS라니요?

제가 생각하는 저희 팀은 기술적 욕심이 있는 팀이라고 생각합니다. 그렇기 때문에 간단한 프로젝트를 제작할 때가 되면 종종 다른 언어를 시도해보기도 합니다. 그래서 위에서 언급한 NestJS도 새로운 프로젝트에서 써보게 되었고  Serverless Framework까지 사용하여 Lambda에 올린 프로젝트입니다.

오랜만에 만난 에러

사실 위 프로젝트는 가끔 사용하는 서비스이고 그러다보니 유지보수가 거의 없는 프로젝트입니다. 이번에 만난 에러도 오랜만에 피드백이 들어오게 되었는데요.  처음에는 '파일을 다운을 받아도 열수가 없다' 라는 연락을 받게 되었습니다.

이렇게 되면 생각해볼 수 있는 가설은 많지만 다행히 테스트를 하는 과정에서 바로 에러 메세지를 만날 수 있었습니다.

원인이 무엇일까?

errorMessage를 보면 size에 대한 얘기가 나와 있었고 이 문제는 파일의 크기 때문에 나타나는 에러입니다.

이 에러가 나는 이유는 이 프로젝트가 Lambda에 올라가 있기 때문입니다.  AWS Lambda의 경우 requeset, response의 payload는 6MB로 제한을 하고 있습니다. 문제가 되었던 파일은 6MB가 넘는 파일 사이즈였고 그렇기 때문에 다운로드 하려고 하면 payload size에 대한 에러가 나타나게 된 것입니다.

(또 다른 Lambda의 limit이 궁금하다면 이 링크에서 확인할 수 있습니다.)

시도할 수 있는 방법

작은 스타트업에서 항상 겪는 문제와 고민은 리소스를 얼마나 투입하면 좋을지에 대한 고민 같습니다. 만약 사용자가 많지 않고 이슈에 대한 민감도가 낮은 서비스라면 큰 리소스를 쓰는게 부담이 될 수 있습니다.

그렇다고 리소스를 적게 쓰고 유저의 UI/UX를 해치는 방법도 좋지 않습니다. 그렇기 때문에 다양한 방법을 펼쳐놓고 빠르고 유용한 방법부터 하나씩 시도하는 방법이 좋다고 생각했습니다.

제가 생각한 방법은 아래와 같습니다.

  • AWS Lambda response streaming

Resonse streaming에 대한 기술은 비교적 최근에 나온 기술로 6MB의 payload 제한을 20MB까지 올릴 수 있는 방법입니다. 2023년 4월에 올라온 블로그 글이 마침 생각나서 가장 빠르고 쉽게 테스트 할 수 있는 방법이라고 생각했습니다.

Introducing AWS Lambda response streaming | Amazon Web Services
Today, AWS Lambda is announcing support for response payload streaming. Response streaming is a new invocation pattern that lets functions progressively stream response payloads back to clients. You can use Lambda response payload streaming to send response data to callers as it becomes available. T…
  • Stream Download

이 부분은 사실 긴가민가한 부분이었지만 이 또한 빠르게 시도해볼 수 있다고 생각해서 같이 테스트를 해보기로 했습니다.

  • Signed url

이 생각은 비교적 나중에 나와서 아쉬웠습니다. 지금 프로젝트에서는 파일을 다운로드 하는 방식을 쓰고 있다면 Signed url을 사용하게 된다면 api를 통해 url을 건네받고 그 url을 새창으로 띄우는 방식으로 다운로드를 유도할 수 있습니다.

  • 그 외

나머지 방법은 유저의 UI/UX를 해치거나 리소스를 크게 잡아먹는 방법으로 파일 업로드 크기를 제한하고 여러개를 올릴 수 있는 방법, 사이즈가 큰 파일은 이메일로 제공하는 방법, Serverless를 포기하는 방법등등이 나왔습니다.

해결 과정

해결 과정은 위에 나열된 순서대로 시도를 해보게 되었습니다.

  • AWS Lambda response streaming

Response streaming을 시도하면서 가장 어려웠던 부분은 NestJS에 적용하는 부분이었습니다. 특히 Serverless를 올릴 때에 다른 패키지에 의존성이 있는 상태였으며 원하는 형태로 원하는 함수를 제공을 바라는 입장이다 보니 난이도와 시간이 생각보다 많이 필요한 상황이 되었습니다.

만약 시도를 해서 성공했다고 해도 20MB의 제한 때문에 이 방법으로 긴 시간동안 서비스를 유지하기에 어렵다고 생각해서 빠르게 시도를 접고 다음 방법을 생각했습니다.

  • Stream download

이 방법도 빠르게 시도를 해볼 수 있었는데요. 이 방법은 애초에 위 문제를 해결하는 방법이 아닌 방법이었기 때문에 Lambda의 payload를 해결할 수 없었습니다.

  • Signed url

미리 얘기하자면 저희는 결국 Signed url을 사용하는 방법으로 해결을 하게 되었습니다. 생각해보면 지금도 많은 프로젝트에서 Signed url 사용을 하고 있는데요. 이 생각을 빠르게 해보지 못해서 아쉬웠습니다. 단 NestJS에서 만들어보기는 처음이었는데요. 쓰는 방식이 언어에 따라 크게 다르지 않아 비교적 쉽게 만들어서 테스트 할 수 있었습니다.

Signed url 사용 방법

Signed url을 사용하는 방법은 s3.getSignedUrl 형태를 따릅니다. 그리고 parameter로 Bucket, Key, Expires를 주는게 기본 형태입니다. 추가로 저는 파일 다운로드시 이름을 변경해주고 싶었기 때문에 ResponseContentDisposition까지 설정하게 되었습니다. 여기서 파일 이름의 encode 부분도 같이 신경 써 주셔야 합니다.

const encodedFilename = encodeURIComponent(file.title);

const url = await this.S3.getSignedUrl('getObject', {
	Bucket: this.configService.get('AWS_PRIVATE_BUCKET'),
	Key: file.path,
	Expires: 60,
	ResponseContentDisposition: 'attachment; filename="' + encodedFilename + '"',
});

이렇게 해서 나온 url을 새창으로 띄워주면 파일을 다운받을 수 있습니다.

v2에서 v3으로 변경

작업을 하면서 뜻하지 않게 만나게 된 메시지는 지금 쓰는 sdk의 버전을 바꾸라는 메세지였습니다. 그래서 이번에 v3으로 바꾸는 작업까지 같이 진행했습니다.

Presigend url 만드는 부분에 대한 aws 블로그 글이 있기 때문에 그 부분을 참조하셔도 좋습니다.

Generate a presigned URL in modular AWS SDK for JavaScript | Amazon Web Services
On December 15th, 2020, we announced the general availability of the AWS SDK for JavaScript, version 3 (v3). This blog shows you how to generate a presigned URL for an Amazon S3 bucket using the modular AWS SDK for JavaScript. Motivation A presigned URL gives you access to the object identified in t…

v3에서 s3 client를 생성하고 signed url을 생성하는 전체 소스는 아래와 같습니다.

const s3 = new S3Client({
	region: this.configService.get('AWS_REGION'),
	credentials: {
		accessKeyId: this.configService.get('AWS_KEY'),
		secretAccessKey: this.configService.get('AWS_SECRET'),
	},
});

const encodedFilename = encodeURIComponent(file.title);

const command = new GetObjectCommand({
	Bucket: this.configService.get('AWS_BUCKET'),
	Key: file.path,
	ResponseContentDisposition:
'attachment; filename="' + encodedFilename + '"',
});

const url = await getSignedUrl(s3, command, { expiresIn: 60 });

마치며

처음에 이 문제를 만나고 해결하려고 할 때에는 생각보다 Lambda의 Response streaming에 생각이 많이 매몰되어 있어서 Lambda, NestJS, Serverless 패키지를 보고 분석하는데 시간을 많이 썼습니다. 지금 생각해보면 NestJS에서 Response streaming을 써서 문제를 해결한다면 스스로가 더 멋져보일거란 욕심이 있었던 것 같습니다.

기술적인 공부는 문제를 해결한 지금 시작하는게 맞는 방법이라고 생각합니다. 다행히 코멘토에는 문제를 해결하는 방법과 기술적인 시도를 할 때에 고민과 의견을 나눌 수 있는 좋은 동료들이 있는 곳입니다.

앞으로도 서로 도움이 될 수 있는 좋은 동료들이 계속 함께하길 기대해봅니다.


커리어의 성장을 돕는 코멘토에서 언제나 함께 성장할 개발자를 기다리고 있습니다. 채용 페이지에서 코멘토가 어떤 회사인지, 어떤 사람을 찾는지 더 자세히 확인해보세요. 😊