바코드 라벨 PDF 생성 서버 구현기(Rust, Skia, Flex layout, Serverless)

Heehong Moon
bgpworks
Published in
9 min readFeb 22, 2021
바코드 프린트 템플릿

박스히어로(https://www.boxhero-app.com) 에서는 바코드를 스티커로 인쇄하는 기능을 제공한다. 고객이 원하는 내용을 커스터마이징 해서 프린트 할수 있다. 첫버전 출시 이후 고객들로 부터 프린트 관련 오류, 개선점을 많이 듣고 새로 개선한 버전의 라벨지 프린트 기능을 어떻게 구현했는지 공유하려고 한다.

첫번째 버전(html+css)

웹에서 프린트를 지원하는 가장 쉬운 방법은 html+css 를 이용하는 방법이었다. 바코드 이미지를 생성하는 javascript 라이브러리를 이용하고, css로 용지크기, 레이아웃을 맞춘뒤 브라우져 프린트 api를 이용해 프린트가 가능했다. 이 방식의 가장 큰 장점은 노력대비 적당히 돌아가게 할수 있다는 것이었다.

좁은폭/넓은폭 레이아웃만 제공하고, 폰트 크기를 조절 등 제한적인 커스터마이징 기능을 제공했다.

대부분의 경우는 잘 동작했지만, 일부 고객으로부터 이상하게 프린트 된다는 문의를 여러번 받았다.

10장을 프린트 했는데, 총 20장이 나오고, 1장씩 빈 내용이 채워진 것이다. 정확한 용지 크기로 layout을 했는데도 1픽셀의 오차로 용지 크기를 벗어났고, html 프린트의 기본 특성에 따라 빈 페이지를 하나 더 그린것이다. html은 문서(Document)형식 프린트에는 적합하지만 바코드 라벨처럼 고정된 크기에 딱 맞게 프린트하는건 맞지 않다는걸 알게 되었다.

또한 고객마다 프린트 하려는 용지 크기가 다양하고 원하는 디자인이 서로 다르기 때문에 고객 별로 라벨 디자인 설정이 가능한 구조가 필요했다. 고객마다 프린트할 디자인을 html 템플릿 형태로 보관할 수 있겠지만, html 템플릿을 커스터마이징하는 UI를 만드는것은 어려워 보였다.

프린트에는 PDF가 짱이다.

프린트 하고 싶은 내용을 가장 정확하게 표현하는 파일 포멧은 어떤것일까 찾아봤는데, 결론적으로 말하면 pdf가 가장 정확하고 모든 플랫폼(윈도우, 맥, 아이폰, 안드로이드 등)을 지원한다. 심지어 맥의 프린트 api에는 아예 pdf를 인풋으로 받는다.

프린트하고자 하는 내용을 pdf로 정확히 만들어 낼수 있다면, 언제 어디서든 정확하게 프린트 가능하다.

레이아웃 시스템

pdf는 기본적으로 레이아웃 시스템이 들어가 있지 않다. html에서는 css의 여러가지 style(padding, margin, flex 등)로 쉽게 레이아웃을 할수 있지만 pdf에서는 레이아웃 시스템이 없기 때문에 쉽게할수 있는 방법이 없다. pdf는 고정 크기의 용지를 가정하기 때문에 고정된 좌표값으로 글자, 이미지 등의 엘리먼트를 넣어줘야 한다.

시중에 나와있는 바코드 라벨 인쇄 프로그램을 보면 마치 파워포인트 처럼 “바코드”나 “텍스트”를 추가하고 드래그 하여 위치를 맞춘다. 역시 최고의 UI/UX는 고객이 가장 익숙한 것이라는 생각으로 라벨 템플릿을 그릴수 있는 프로토타입을 만들어봤다.

라벨 디자인툴 프로토타입
  • 바코드와 텍스트를 추가하여, 마우스로 배치할 수 있다.
  • 결과를 JSON형태로 저장하여 템플릿을 저장해둘수 있다.
  • 고객이 직접 어디에 제품명을 넣을지, 바코드를 넣을지 배치 해야 한다.
  • 용지 크기를 바꾸었을때, 하나씩 드래그 해서 크기 조절을 해줘야만 한다.

자유롭게 레이아웃을 할수 있다는 장점이 있지만, 과연 대부분의 고객이 원하는게 맞을까?

만약 pdf에서도 html버전에서 쓰던 style(size, padding, margin, flex)을 쓸수 있다면 더 좋지 않을까? flex 레이아웃으로 구성된 다양한 기본 템플릿을 제공해준다면 고객은 클릭 몇번으로 인쇄를 해볼수 있게 된다.

새버전 구현(pdf, flex layout, serverless)

  • pdf로 그릴수 있어야 한다.
  • flex layout을 사용할 수 있어야 한다.
  • 웹/모바일 둘다 사용가능 해야 한다.

위 세가지 요구사항을 만족할수 있는 여러가지 방안을 생각해봤고, Skia와 Stretch를 써서 렌더링하는 서버를 구현한뒤 AWS Lambda에 올려서 서비스 하는 방향으로 정했다.

Skia(https://skia.org)

구글에서 오픈한 오픈소스 프로젝트이며, 2d 벡터 드로잉에 특화된 라이브러리이다. skia는 구글 크롬 브라우져, 안드로이드, Flutter의 렌더링 백엔드로 꽤 오래동안 사용되고 있다. 다르게 말하면 구글 크롬, 안드로이드에서 보이는 모든 GUI는 결국 skia가 그려내고 있다는 말이다.

파일 저장 옵션으로는 png, jpg, pdf, svg등 다양한 백엔드 형식을 지원한다. 또한 텍스트 관련된 추가기능을 제공하는데, text wrap(자동 줄바꿈), font fallback(다국어 지원을 위한 폰트 자동 찾기)등이 있다.

c++를 쓴지도 너무 오래되기도 했고, 마침 요즘 핫한 Rust binding이 오픈소스(https://github.com/rust-skia/rust-skia)로 있어 공부할겸 한번 써보기로 했다.

Flex layout

Flex layout쪽으로 가장 유명한 라이브러리는 페이스북에서 오픈한 yoga(https://yogalayout.com)이다. React native의 레이아웃 백엔드로 사용되고 있고 다양한 언어/플랫폼에서 가져다 쓸수 있는 장점이 있다.

Rust쪽에는 visly에서 오픈한 stretch(https://vislyhq.github.io/stretch/) 라이브러리도 거의 동일한 기능을 제공한다. yoga는 rust binding을 써야 하지만, stretch는 pure rust이기 때문에 stretch를 사용해보기로 했다.

Serverless

웹과 모바일 모두 사용하기 위해서 AWS Lambda에 서버리스 형태로 올려 어디서든 라벨지를 그릴수 있게 했다.

라벨 디자인 스펙

Json형태로 어떤 레이아웃으로 라벨지을 그릴지 템플릿 스펙을 정의 했다. 컨테이너(Container), 글자(Text), 바코드(Barcode), qr코드(Qrcode)를 지원하며 각각의 요소를 어떻게 배치할지는 css와 비슷한 padding, margin, size, flex 등 속성으로 지정 가능하다.

디자인 템플릿에 제품명 또는 제품 속성등 다양한 정보를 포함하는것은 mustache 템플릿 스타일을 적용하여, {{name}}을 입력하면 제품명이 치환되어 들어가게 했다. 이 방식을 채택한 이유는 일반적으로 한번에 100장 정도 프린트를 하는데, 디자인/레이아웃은 동일하지만 내용만 달라지는 경우 서버에 전송하는 데이터양을 줄이기 위해서다.

이제 라벨 템플릿을 Json형태로 저장해둘 수 있고, html보다 훨씬 편하게 커스터마이징이 가능하다. css 코딩을 조금만 해본 사람이라면 쉽게 구조를 파악할 수 있고 직접 커스터마이징 할 수 있다.

전반적인 구조

AWS lambda에 올려서 Serverless 형태로 운영한다.
  • 디자인 요소를 포함하는 레이아웃 Json, 내용을 포함하는 데이터 Json을 렌더링 서버 보내면 그 결과로 PDF파일을 리턴한다.
  • AWS Lambda에는 Node.js로 빌드하여 http request를 받는다. Rust native 모듈에 받은 데이터를 넘겨주고, 결과로 받은 png/pdf 파일을 http response로 돌려준다. Neon(https://www.neon-bindings.com) 프로젝트를 사용하여 NPM native 모듈로 쉽게 만들수 있었다.
  • PDF는 프린트에 적합한 포멧이기 때문에, 미리 보기 용도로 PNG형태로도 렌더링 하게 했다.
  • 바코드 렌더링은 EAN13, Code128, QR코드를 지원한다.
  • Noto Sans 폰트를 포함하여, 영어뿐 아니라 한국어, 중국어, 태국어 등 다양한 언어 렌더링을 지원한다.

렌더링 속도 & Latency

AWS Lambda에서 매번 렌더링하는데도 반응성이 꽤 좋다.
  • 일반적인 라벨지 한장 렌더링에 걸리는 시간은 ~10ms 정도이다.
  • 한국 기준 AWS Lambda 까지 도달하는데 ~30ms 이다.
  • 약 100장 정도의 라벨지가 포함된 pdf를 생성한다고 치면 약 1초 이내로 렌더링이 가능하다.
  • AWS Lambda의 경우 새로운 인스턴스가 뜨는데 Cold Start로 2–3초가 걸리는기 때문에 Provisioned concurrency를 설정했다.
  • AWS Lambda는 Memory 크기로만 인스턴스 사양 조절이 가능한데, 1GB 메모리로 설정했다. 기본인 128MB로 했을때 렌더링 속도가 꽤 느렸다. 단순 메모리가 아니라 CPU/IO 성능 차이도 나는것으로 보인다.

결론

바코드 라벨 프린트 기능을 기존 html 방식에서 pdf(skia, flex, serverless)로 변경함으로써 아래와 같은 장점을 얻을수 있었다.

  • 고객마다 다른 라벨 디자인을 설정 가능하고, 이를 기반으로 안정적으로 프린트 가능하다.
  • 더 이상 브라우져 특성에 따라 발생하는 이상한 버그가 없다.
  • 다양한 국가의 언어를 지원할 수 있다.
  • Flex 레이아웃을 도입함으로써 용지 크기와 무관한 템플릿을 미리 만들어 제공해줄수 있다.
  • 서버 형태로 구현했기 때문에 PC 웹 뿐만 아니라 모바일에서도 프린트가 가능하다.
  • AWS Lambda는 자동 Scale되기 때문에 많은 사용자가 동시에 사용하더라도 서버 관리 이슈가 없다.

기타 노트

  • Rust 언어를 처음 써봤는데 생각보다 진입장벽이 꽤 높았다. Rust 언어 자체의 진입장벽도 있지만, 방대한 규모의 skia 라이브러리도 처음 쓰다보니 힘들었던것 같다.
  • Rust 공식 홈페이지에 가면 Rust를 배울수 있는 한권의 책이 있다. 이 책은 쉽게 읽히는 느낌은 아니어서, 처음 Rust를 배운다면 https://learning-rust.github.io/ 사이트를 추천한다.
  • skia 공식 문서 보다는 .NET 버전(https://github.com/mono/SkiaSharp)이 오히려 문서화가 잘 되어 있다.
  • skia를 aws lambda에서 실행하기 위해서는 몇가지 shared library(libfontconfig.so, libfreetype.so)가 필요하다. https://hub.docker.com/r/lambci/lambda/ Docker 컨테이너에서 필요 라이브러리 설치 후 추출해서 사용했다.

--

--