회사 테크 블로그 기고 - PhantomJS를 Headless Chrome(Puppeteer)로 전환하며

이 포스팅은 제가 버즈빌 테크 블로그에 기고한 글을 전제한 것입니다. 버즈빌에서는 모바일 잠금화면에 내보내기 위한 광고 및 컨텐츠 이미지를 생성하기 위한 PhantomJS 렌더링 서버를 다수 운영하고 있습니다. 일반적으로 PhantomJS는 웹페이지 캡쳐에 많이 쓰이지만, 기본적으로 headless하게 웹페이지를 렌더링하고 캡쳐할 수 있다는 특성 때문에 동적인 이미지 생성에도 많이 활용됩니다. 버즈빌의 렌더링 서버는 200개 이상의 컨텐츠 프로바이더로부터 실시간으로 잠금화면 컨텐츠 이미지를 생성하고 있어 분당 수백 건의 이미지를 안정적으로 생성하는 것이 가능해야 합니다. 렌더링 서버의 스케일링 이슈를 해결하기 위해 버즈빌에서는 여러 대의 렌더링 서버를 둬서 횡적으로 확장을 함과 동시에, 개별 서버 내에서도 리소스 사용률을 높이기 위해 Ghost Town이라는 라이브러리를 작성해 PhantomJS 프로세스 풀을 구성하여 사용하고 있었습니다(Scaling PhantomJS With Ghost Town ) 한편, 시간이 지나면서 잠금화면에서 렌더링하는 이미지 템플릿의 종류가 다양해지고, emoji 및 여러 특수문자를 표현하기 위해 렌더링 서버에 여러 폰트(대표적으로 Noto Sans CJK)를 설치해야 하는 요구사항이 추가됐는데, PhantomJS에서 폰트 렌더링이 일관적이지 않은 문제가 발생했습니다. 이 문제의 정확한 원인은 결국 찾지 못했지만 PhantomJS의 이슈였거나 시스템 상에 폰트가 시간이 지나면서 추가 설치됨에 따라 font cache가 서버마다 일관되지 않은 상태가 되었기 때문인 것으로 짐작하고 있습니다. 다른 워크로드와 마찬가지로 렌더링 서버도 최초에는 packer를 이용해 일관되게 이미지를 빌드하고 업데이트하려고 했지만, 자주 기능이 추가되거나 배포되는 서비스가 아니기에 서버를 오래 띄워놓고 수동으로 유지보수를 한 케이스들이 누적되어 더 이상 packer를 이용해 시스템이나 폰트를 최신 상태로 유지하는 것이 어려운 상태였습니다. 모든 눈꽃송이가 자세히 보면 조금씩 다르게 생겼다는 것에서 비롯된 snowflake, 즉 배포된 서버들이 시간이 지남에 따라 조금씩 다른 상태가 된 것입니다. 평소에는 문제가 없어 보이지만, 추가적인 확장성이 필요해 scale out을 하거나 새로운 템플릿을 개발해 배포를 하면 문제가 발생하는 상황이었습니다. 사실 더 큰 문제는 PhantomJS 프로젝트가 더 이상 관리되지 않는다는 점이었습니다. 2017년 Google Chrome 59버전부터 Headless Chrome이 내장되기 시작하였고, 곧바로 Node API인 puppeteer가 릴리즈 되어, 현시점에서 가장 많이 쓰이는 렌더링 엔진을 손쉽게 headless로 사용할 수 있는 환경이 되었습니다. 때문에 PhantomJS 관리자가 사실상의 중단을 선언하였고, 2018년에는 최초 개발자에 의해 프로젝트가 아카이브 되었습니다. 프로젝트가 업데이트되지 않는 것은 템플릿에 최신 CSS 스펙을 사용하지 못한다는 것을 의미하고, 버그 수정도 되지 않기에 어플리케이션의 유지보수가 굉장히 어려워짐을 의미합니다. 현재까지의 문제점을 정리하면 아래와 같습니다. ...

September 22, 2019 · Hoseong Hwang

Docker를 이용한 bundle install 및 Gemfile.lock 업데이트하기

여러개의 ruby 프로젝트를 작업하다보면 rbenv, rvm 등으로 버전별, 프로젝트별 ruby와 gem들을(rvm의 경우 gemset) 관리해야하는 귀찮음이 생긴다. 지난번에 로컬에 프로젝트 관련 gem을 깔지 않기로 결심했으니 rubygems의 디펜던시를 기록하는 Gemfile.lock 파일 역시 docker를 이용해 업데이트를 하기로 했다. 아래와 같은 Gemfile이 있다고 하자. # Gemfile ruby '2.3.1' source 'https://rubygems.org' gem 'sinatra' 위 Gemfile에서 ruby 버전을 2.3.1로 명시하고 있기 때문에 rvm이든 rbenv든 이용해서 로컬에 해당 버전을 설치해 줘야 bundle install을 실행할 수 있다. 하지만 docker를 이용하면 원하는 ruby 버전으로 one-off container를 만들어 현재 경로를 mount한 채로 bundle install을 실행하면 Gemfile.lock을 올바르게 업데이트 할 수 있다. ...

September 28, 2016 · Hoseong Hwang

로컬 postgres 없이 heroku pg:psql 커맨드 사용하기

최근 로컬 개발환경을 리셋하면서 redis, postgres 등 각종 개발 디펜던시를 설치하지 않고, 오직 docker만 이용해서 로컬 환경을 최대한 깔끔하게 유지하기로 마음먹었다(똥고집이다). 그런데 난관이 등장했으니, 프로덕션 배포 중인 heroku 앱의 postgres 디비 콘솔에 접근하기 위해 heroku toolbelt에서 제공하는 heroku pg:psql 커맨드를 자주 사용하는데, 이 때 로컬 경로의 psql을 이용한다는 것이다. $ heroku pg:psql ---> Connecting to DATABASE_URL sh: psql: command not found 굳이 toolbelt를 쓰지 않고 docker run -it --rm postgres psql 커맨드에 적당히 credential을 넣어주면 되지만, 매번 credential을 알아오기도 귀찮고 heroku toolbelt를 썩히기도 아까워 해결책이 있을지 고민해봤다. 처음에는 간단히 alias를 걸면 될 줄 알았는데 heroku command에서 쉘 환경을 리셋시켜버려서 command not found가 떴다. ...

August 31, 2016 · Hoseong Hwang