안녕하세요, 이상혁입니다

☁️ 프론트엔드의 한계에 부딪혔을 때, AWS 친구들을 부르는 법

☁️ 프론트엔드의 한계에 부딪혔을 때, AWS 친구들을 부르는 법

2025년 8월 27일

사건의 발단 (a.k.a 배경)

개발한 서비스를 지속적으로 배포하고 사용자 유입이 늘어나면서 예상치 못한 이슈들이 발생하기 시작했다. 그 중에서도 미리보기 관련 화면이 간헐적으로 보이지 않거나, 미리보기가 제대로 로딩되지 않아 다음 단계로 진행할 수 없다는 고객들의 목소리가 들려오기 시작했다.

미리보기 기능에 사용되는 파일들은 대부분 PDF 형태였고, PDF 페이지들을 렌더링하기 위해 별도의 PDF 라이브러리를 활용한 뷰어 컴포넌트를 개발해 사용자들에게 제공하고 있었다. 테스트 당시 PC와 모바일 브라우저에서 PDF 페이지들이 문제없이 렌더링되었고, QA 과정도 무사히 마쳤기에 안심하고 있었다.

하지만 지속적으로 PDF 뷰어 관련 문제 신고가 들어왔고, Sentry와 Datadog 모니터링을 통해 원인을 파악한 결과, 다음과 같은 상황에서 문제가 지속적으로 발생하고 있음을 확인할 수 있었다.

  • 사용자의 디바이스 환경이 열악한 경우 (구버전 모바일 브라우저, 인앱 브라우저, 저사양 단말기 등)
  • 고화질 PDF이거나 페이지 수가 많은 경우 (100페이지 정도를 예상했는데, 실제로는 600페이지 분량을 업로드하는 케이스들이 발생)

어쨌든 조직 내에서 해당 타겟층이나 상황들이 충분히 고려되지 않았던 것이 사실이었고, 이런 새로운 데이터들이 향후 프로젝트나 기능 도입 시 유용한 인사이트가 될 것 같았다. 하지만 개발팀 입장에서는 결국 이런 환경의 사용자들도 동일한 기능을 문제없이 이용할 수 있도록 대응 처리를 해야 하는 상황이었다 😭

브라우저 PDF 뷰어와의 사투 일기

PDF 뷰어 컴포넌트를 개발할 때는 react-pdf 라이브러리를 활용했다. 이 라이브러리는 Mozilla에서 개발한 PDF.js(JavaScript 기반의 PDF 렌더링 라이브러리)를 기반으로 하고 있어서, React 환경에서 PDF를 쉽게 렌더링할 수 있게 해준다. 처음에는 기본적인 예제를 참고해서 뷰어를 만들었는데, 특정 사용자의 브라우저에서 Array.at(), Promise.allSettled() 같은 ES 최신 문법을 지원하지 않아서 에러가 발생했다. 이 문제는 Polyfill을 추가해서 해결할 수 있었다.

하지만 얼마 지나지 않아 훨씬 오래된 브라우저 버전을 사용하는 사용자로부터 PDF 뷰어가 아예 로드되지 않는다는 피드백이 들어왔다. 결국 PDF 관련 라이브러리들을 상당히 낮은 버전으로 다운그레이드하고, 소스 코드들도 구버전 브라우저와 호환되도록 전면 수정해서 대응했다.

다행히 PDF를 보여주는 것 자체는 성공했지만, 테스트 과정에서 새로운 문제가 발견됐다. 스크롤할 때마다 화면이 끊기는 현상이 발생한 것이다. PDF 용량을 줄일 수도 없는 상황이었고, 성능 개선 작업은 또 다른 호환성 이슈를 불러올 수 있는 위험한 도전이라고 판단했다. 일단 ‘보여주는 것’만으로도 감사한 상황이었다.

이 사건 이후로는 PDF 관련 VoC가 또 들어올까 봐 마음이 조마조마했고, 솔직히 PDF 뷰어를 건드리는 것 자체가 점점 부담스러워지기 시작했다.

[사진 1] VoC 들어올 때마다 걱정하는 나의 모습

[사진 1] VoC 들어올 때마다 걱정하는 나의 모습

VoC 대응을 거의 마무리하고 약간의 회고 시간을 가질 수 있게 되었을 때, PDF 관련 소스 코드를 다시 한번 살펴보았다. 더 이상 클라이언트 사이드에서 처리하기에는 한계가 명확하다는 것을 확실히 깨달았다. 결국 서버 사이드에서 처리해야 하는 상황이었고, 이를 위해서는 백엔드 코드 수정이나 추가 개발이 필요했다. 하지만 백엔드팀도 바쁜 시기라 부탁드리기가 부담스러운 상황이었다.

그럼에도 현재 적용된 PDF 뷰어는 마치 시한폭탄처럼 느껴졌다. 언제든 다음 사용자에게 동일한 문제가 발생할 수 있는 불안정한 상태였기 때문에, 반드시 빠른 시일 내에 해결해야 한다는 생각이 들었다. 먼저 백엔드 담당자에게 PDF 업로드 플로우에 대한 간단한 설명을 듣고, 세부 구현 방식들을 직접 확인해보았다. 그 과정에서 서로의 작업에 방해되지 않는 선에서 문제를 해결할 수 있는 방법을 찾아낼 수 있었다.

결국 기존 PDF 뷰어를 완전히 제거하고, 서버 사이드에서 PDF 페이지들을 이미지로 변환해서 이미지 뷰어로 미리보기를 제공하는 방식으로 전환하기로 결정했다. 사용자는 미리보기를 이미지로 확인하고, 실제 다운로드할 때는 원본 PDF를 받을 수 있도록 하는 기능 개선 작업에 착수했다.

AWS 형님들과 손잡고 문제 해결하기

서비스마다 첨부파일 관리 방식은 달랐지만, 내가 담당하던 서비스들은 최근 S3를 활용하여 파일을 관리하고 있었다. 그 중에서도 Pre-Signed URL을 이용해 파일을 업로드하고, 업로드가 완료되면 최종 URL을 서비스에 전달하여 정보를 저장하는 방식이었다. 여기서 떠올린 아이디어는 ‘파일 업로드가 시작될 때 “PDF를 이미지로 변환하는 Lambda 함수를 실행하라”는 트리거를 보내면 되지 않을까?’ 라는 생각이 들었다.

예전에 해커톤 대회에서 AWS IoT Core를 간단하게 사용해본 경험이 있었는데, 그때 Rule(규칙)이라는 개념을 통해 MQTT 메시지를 다른 AWS 서비스들(Lambda, DynamoDB, S3 등)과 쉽게 연동할 수 있었던 기억이 났다.

그래서 S3 관련 문서들을 찾아보니, ‘S3 Event Notifications를 통한 Lambda 함수 호출’에 대한 공식 문서를 발견할 수 있었다. 내 아이디어가 충분히 실현 가능하다는 것을 확인한 후, 공식 문서와 함께 전체적인 흐름도를 다시 한번 정리해보았다.

[사진 2] S3 트리거에 대한 AWS 공식 문서

[사진 2] S3 트리거에 대한 AWS 공식 문서

이번 작업을 진행할 때 중요하게 고려해야 할 포인트들을 다음과 같이 정리했다.

  • PDF에서 이미지로 변환하는 Lambda Function은 빠르게 처리되어야 하며, 과부하를 일으킬 수 있는 기능은 제거한다.
  • 이미지를 확대했을 때 글자가 흐릿하게 보이지 않도록 적절한 해상도를 유지해야 한다.
  • 절대 원본 PDF를 훼손하거나 수정해서는 안 된다.
  • 해당 버킷에 PDF 파일이 업로드될 수 있는 다양한 케이스를 고려하여, 트리거할 prefix를 정확하게 설정해야 한다.
  • 작동 여부에 대한 로그 수집이 원활하게 이루어지도록 처리해야 한다.
  • 출력물에서 metadata.json을 통해 이미지(PDF 페이지)별 정보를 확인하고 이를 바탕으로 렌더링한다.

이러한 요구사항들을 바탕으로 아래와 같은 흐름도를 설계할 수 있었다.

[사진 3] S3 트리거 기반으로 PDF 문서들을 이미지로 변환할 때의 흐름도

[사진 3] S3 트리거 기반으로 PDF 문서들을 이미지로 변환할 때의 흐름도

설계 과정에서 JavaScript(TypeScript) 언어에 익숙한 상황이라 Node.js 런타임 기반으로 Lambda 함수를 만들고 싶었지만, ChatGPT와 몇 차례 논의한 결과 흥미로운 사실을 알게 되었다. PDF.js는 브라우저 환경에 특화된 렌더링 라이브러리로서, 다른 PDF 처리 라이브러리들에 비해 성능이 상당히 느리다는 것이었다.

내가 이해한 바로는, PDF.js 기반 라이브러리들은 브라우저의 Canvas API를 활용해 원본 PDF를 읽고 이를 픽셀 단위로 그려나가는 방식으로 동작한다. 이러한 과정은 상당히 무거운 작업이었고, 특히 서버 사이드 환경에서는 더욱 비효율적이었다.

반면 서버 사이드로 전환하면서 그에 맞는 전용 PDF 처리 라이브러리를 활용하면, PDF를 직접 파싱하여 처리하기 때문에 네이티브 수준의 훨씬 빠른 성능을 얻을 수 있다는 것을 알게 되었다. 그래서 Node.js 런타임을 포기하고 Python 런타임을 채택하기로 했으며, PDF 처리 분야에서 가장 인기 있고 빠르다는 평가를 받고 있는 PyMuPDF를 적용해보기로 결정했다.

[사진 4] PyMuPDF Github 페이지 장면

[사진 4] PyMuPDF Github 페이지 장면

Python을 코딩 테스트 목적으로만 배웠던 기억을 되살리며 처음부터 코드를 작성하기에는 시간이 다소 소요될 것 같아, Claude Code를 활용했다. 내 설계 및 환경에 대해 상세히 설명하고 프로젝트 초안을 만들어 달라고 요청했더니, 정말 주요 기능들이 정상적으로 잘 동작하는 코드를 받을 수 있었다. 덕분에 세부적인 부분만 수동으로 조정하면 되는 수준까지 진행할 수 있었다.

[사진 5] Claude Code로 Lambda 실행 코드 작성 완료한 장면

[사진 5] Claude Code로 Lambda 실행 코드 작성 완료한 장면

로컬 테스트를 완료하고 본격적으로 AWS에 배포하는 과정에서는 SAM(Serverless Application Model) CLI를 활용했다. 이는 서버리스 애플리케이션에 특화된 IaC(Infrastructure as Code) 도구로, Terraform과 유사한 방식으로 동작한다. 매번 코드를 압축해서 Lambda에 직접 업로드할 필요 없이, template.yaml 파일 하나로 필요한 정책 및 권한 설정을 자동으로 처리해주고, Lambda 관련 설정도 템플릿 기반으로 구성해준다.

[사진 6] AWS SAM으로 배포하여 Lambda가 적용되어 있는 장면

[사진 6] AWS SAM으로 배포하여 Lambda가 적용되어 있는 장면

모든 설정을 완료한 후 S3 버킷에 메타데이터와 변환된 이미지들이 정상적으로 업로드되는 것을 확인했다. 기존의 문서들에 대한 마이그레이션을 모두 처리한 후 문제가 많았던 PDF 뷰어 컴포넌트를 이미지 뷰어로 대체하면서, 드디어 쾌적하게 문서를 사용자들에게 제공할 수 있게 되었다.

[사진 7] 로컬에서1200개의 PDF를 이미지로 변환하여 마이그레이션한 장면

[사진 7] 로컬에서1200개의 PDF를 이미지로 변환하여 마이그레이션한 장면

이렇게 해서 뭐가 좋아졌을까? (feat. 기대효과)

뷰어에 지연 로딩(Lazy Loading)을 적용할 수 있게 되어, 기존에 모든 문서 페이지를 다 다운로드해야만 PDF 뷰어에서 보여줄 수 있었던 것과 달리, 이제는 문서 페이지의 일부를 먼저 로딩하고 사용자가 스크롤을 통해 더 많은 내용을 보려고 할 때 필요한 만큼의 이미지만 추가로 불러올 수 있게 되었다. 이를 통해 네트워크 리소스 전송을 훨씬 효율적으로 관리할 수 있게 되었다.

[사진 8] web.dev에서 말하는 이미지 지연 로딩

[사진 8] web.dev에서 말하는 이미지 지연 로딩

해당 Lambda 함수는 다른 서비스에서도 재사용할 수 있다는 장점이 있다. 즉, 다른 서비스에서 S3 기반으로 PDF를 관리하고 있다면, 해당 버킷 경로에 트리거를 설정하는 것만으로 자연스럽게 연동이 가능하다. 또한 중간에 해당 기능이 더 이상 필요하지 않게 된다면, 트리거 단에서만 제거하면 되기 때문에 매우 유연하고 유용한 구조라고 할 수 있다.

회고: 삽질하면서 얻은 깨달음들

회사에 프론트엔드 개발자로 입사했을 때는 “나는 프론트엔드니까 프론트엔드만 하면 돼”라는 생각이었다. 솔직히 백엔드나 인프라 쪽은 어렵고 복잡해 보여서 굳이 건드리고 싶지 않기도 했다. 하지만 시간이 지나면서 여러 업무를 맡다 보니, 프론트엔드만으로는 어떻게 해볼 수 없는 벽에 자꾸 부딪히게 되었다. 특히 이번 PDF 뷰어 문제는 정말 답답했다. 아무리 코드를 고치고 라이브러리를 바꿔도 근본적인 해결이 안 되는 거였다.

처음에는 “내가 실력이 부족해서 그런가?”라고 자책도 많이 했는데, 알고 보니 브라우저 자체의 한계였던 것. 그때 깨달았다. 가끔은 문제를 다른 각도에서, 다른 영역에서 접근해야 할 때가 있구나. 이번 경험을 통해 백엔드나 인프라 쪽도 조금씩 알아가야겠다는 생각이 들었다. 무서워하지 말고 하나씩 배워보자는 마음가짐으로 바뀐 것 같다. 결국 사용자한테 좋은 서비스를 만들어주는 게 목표니까.

help with using aws 10