본문으로 건너뛰기

Node.js + PDF와의 전쟁

· 약 9분
Austin Lee
DevOps Engineer @ Allganize

PDF

최근 업무를 진행하면서 PDF 파일을 처리할 일이 많았고, 현재도 진행 중입니다.
생각보다 코드로 PDF 파일을 처리하는 것은 쉽지 않은 일이었습니다. 시행착오를 상당히 많이 거쳤고, 그 과정을 간단히 적어 보려 합니다.
(Node.js로 PDF 파일 처리할 일이 흔하지는 않겠지만..)

1. PDF 파일 암호화

문제의 시작은 한 백엔드 팀원이 파일 업로드 서버의 부하를 발견한 것이었습니다. 해당 서버는 PDF, 엑셀 파일을 작성해 S3 저장소에 업로드하는 간단한 역할만 수행 중이었는데, 그럼에도 메인 서버보다 CPU 사용량이 높을 때도 있었습니다.

코드를 분석해 보니 외부 저장소에 업로드하기 전, 임시 파일을 컨테이너 내부에 저장하는 로직이 있었습니다. 이 부분이 문제라고 판단하고 개선을 위해 임시 파일을 저장하지 않고 버퍼 상태에서 직접 업로드할 수 있도록 공수를 파악하기 시작했습니다.
(다른 개선점도 있었지만, CPU에 큰 부하를 줄 만한 요소는 아니었습니다.)

그런데 추가 확인 과정에서 PDF 파일 암호화에 사용하는 node-qpdf2 라이브러리가 실제 파일 경로를 필요로 한다는 것을 확인할 수 있었습니다.
해당 라이브러리는 C++로 작성된 CLI를 감싼 것으로, 이 모체가 파일 경로를 요구하고 라이브러리 코드는 명령어를 생성해 주는 것이었기 때문에 이를 고칠 수는 없었습니다. (원본 코드)

해당 라이브러리를 사용할 경우 임시 파일을 컨테이너 내부에 저장하는 것은 현실적인 선택이었고, 임시 파일을 삭제하는 로직을 추가하는 정도가 한계였습니다. 당연히 근본적인 문제 해결법이 아니었기 때문에 방법을 계속 찾았지만 쉽지 않았고, 한 때는 백엔드 팀끼리 오픈소스 프로젝트를 할까 논의가 오가기도 했습니다. 🤣

의외로 해결법은 다른 곳에서 나왔습니다. PDF 파일 생성에 사용하고 있던 pdf-lib 라이브러리를 누군가 Fork 하여 암호화 기능을 추가한 것을 발견할 수 있었기 때문입니다. 이를 적용해 보는 것으로 합의되었고, 글을 쓰는 시점에는 다른 팀원에게 할당되어 있습니다.

2. PDF 파일 압축과 생성 로직

현재 업무에서는 주기적으로 PDF 형식의 보고서를 발행해야 하는데, 다른 업체와 협업을 하면서 300KB 이하의 저용량 PDF가 필요한 상황이었습니다. 제가 업무를 받은 시점에서는 파일 용량이 5MB 이상인 경우도 있었고, 대응을 위해서 생성된 파일을 압축하거나, 로직을 개선하여 저용량의 파일을 만드는 과정이 필요했습니다.

기존 파일 압축의 경우 몇 가지를 고려해 보았으나 부적절하다는 판단을 내렸습니다.

  • 시중에 검색하면 나오는 압축 사이트나 도구는 보안 문제와 수동 작업의 문제가 있었습니다.
  • pypdf 등의 오픈소스 도구는 그 수도 적고, 테스트 결과 압축 성능도 좋다고 판단하기는 어려웠습니다.
  • Ghostscript는 가장 잘 알려져 있고, 만족스러운 압축 성능을 보여 주었습니다.
    하지만 추가 조사 결과 AGPL 라이선스가 걸려 있어 무료 버전을 사용하기 위해서는 관련 소스를 공개해야 하고, 상업적 이용을 위해서는 개발사 측에 로열티를 지불해야 했습니다.
    실제로 한컴 측이 이를 지키지 않았다가 소송에 걸려 과징금을 문 적이 있었습니다.
    그리고 로열티가 찾아보니 만만치 않았습니다.. (수백 달러부터 출발한다고 합니다)

결국 PDF 생성 로직을 개선할 수 있는지 조사에 들어갔는데, 다행히도 크게 2가지 개선점을 찾을 수 있었습니다.

  • 원본 템플릿 파일의 크기가 큰 편이었습니다. 이를 압축을 통해 150KB 이하로 줄였습니다.
  • PDF 처리 과정에서 텍스트를 입력할 때 폰트 파일 전체를 로딩하고 있었습니다.
    보고서 PDF 생성 과정이 크게 2단계로 나뉘어 있었는데, 이 2개 단계에서 모두 비슷한 수준의 큰 용량 증가를 확인했습니다. 다른 부분에서 원인을 찾지 못하다가 양쪽 로직 모두 폰트를 로딩하는 과정이 포함되어 있음을 발견했고, 실제로 폰트 자체 용량이 꽤나 큰 편이었습니다.
    관련 라이브러리를 조사한 결과, 폰트 파일 전체를 로딩하는 것이 기본값이고, 이를 옵션을 통해 일부만 로딩할 수 있도록 설정할 수 있다는 것을 발견했습니다. (문서)
    하지만 폰트 처리에 사용하는 @pdf-lib/fontkit 라이브러리는 한글을 포함한 특정 언어 글꼴에서 옵션을 적용하면 글자가 제대로 들어가지 않는 문제가 있었습니다. 이와 관련하여 오류를 수정한 라이브러리가 있어 이를 도입해 해결했습니다. (이슈)

위 2가지 개선점을 적용해 3 ~ 6MB 정도 나오던 결과 파일 용량을 150 ~ 200KB까지 크게 줄일 수 있었습니다.

느낀 점

  • 지속적으로 관리되는 라이브러리는 이런 문제가 적을 수 있지만, 비주류 기능이거나 방치된 라이브러리 문제를 해결하는 것이 쉽지 않았습니다. 이 경우도 PDF 처리가 생각보다 흔한 경우가 아니었고, 관련 라이브러리가 오랜 기간 방치되어 있거나 유료 전환이 되어 있어 공수가 커졌습니다.
  • 오픈소스를 사용할 때는 항상 라이선스에 관해 주의가 필요하다는 것을 다시 느꼈습니다. 개인 프로젝트에서는 이를 고려할 일이 거의 없었는데, 실무에서 이를 크게 느낄 수 있었던 사례였습니다.