본문으로 건너뛰기
:tech
← Articles

AI 번역본입니다. 영어 원문 보기

Ghost Town으로 PhantomJS 확장하기

Ghost Town으로 PhantomJS 확장하기

버즈빌에서 제가 맡은 첫 프로젝트는 PhantomJS로 대량의 이미지를 안정적으로 렌더링하는 시스템을 개발하는 일이었습니다. 그 결과 Ghost Town: 간단한 큐·클러스터 기반 PhantomJS 처리 모듈이라는 작은 Node.js 모듈이 탄생했습니다.

문제

PhantomJS는 다루기 쉬운 라이브러리가 아닙니다. 비정상 종료와 멈춤 현상이 흔하고, 메모리 사용량이 높으며 시작도 느립니다. 확장 작업도 직접 해야 합니다. 게다가 PhantomJS와 phantomjs-node는 관리가 소홀해진 프로젝트이므로 이런 문제가 언제까지 이어질지 알 수 없습니다. 버즈빌에서는 다양한 이미지를 짧은 지연 시간으로 안정적으로 렌더링해야 했고, 이를 위해 높은 동시 처리 성능이 필요했습니다. 기존 프로젝트 중에는 이 요구를 제대로 해결하는 것이 없었습니다. 그래서 장애에서 유연하게 복구하고 자동으로 확장할 수 있는 모듈을 조사하고 설계했습니다.

해결책

그렇게 Ghost Town이 등장했습니다. PhantomJS를 실행하고 관리하며 작업을 큐에 넣고 처리하는 복잡한 일은 Ghost Town이 맡고, 나머지는 개발자가 원하는 대로 구현할 수 있게 합니다. 각 작업은 워커가 준비될 때까지 마스터의 큐에 머물다가 할당됩니다. 처리 시간이 초과되거나 할당된 워커에 문제가 생기면 Ghost Town이 작업을 다시 큐에 넣습니다. 안정성을 확보하는 방법입니다! 메모리 누수를 방지하기 위해 워커마다 별도의 PhantomJS 프로세스를 만들고, 생성한 페이지 수에 따라 주기적으로 프로세스를 다시 실행합니다.

구현

Ghost Town 초기화는 간단합니다. 실행 방식과 효율을 조정하는 설정 옵션도 여러 가지 제공하며, 자세한 내용은 문서를 참고하세요. 설정 없이 시작하려면 다음과 같이 작성합니다.

var town = require("ghost-town")();

다음으로 일반적인 cluster 모듈을 사용할 때처럼 마스터와 워커를 구현합니다. 보통은 간단히 if (town.isMaster)를 확인하면 충분합니다.

if (town.isMaster) {
    // master code
    // queue items here
} else {
    // worker code
    // process items here
}

마스터의 핵심 메서드는 town.queue(data, next)입니다. data가 담긴 작업을 큐에 넣고, 워커가 작업을 처리하면 next(err, data)를 호출합니다. 마스터가 작업을 큐에 넣는 방식은 자유롭게 구현할 수 있으며, 메시징 시스템을 사용하는 것도 한 방법입니다. 버즈빌에서는 Thrift를 사용합니다. 마스터는 서버를 시작하고 렌더링 요청을 받을 때마다 큐에 넣습니다.

thrift.createServer(Renderer, {
    render: function (html, width, height, next) {
        town.queue({
            html: html,
            width: width,
            height: height
        }, function (err, data) {
            next(err, !err && new Buffer(data, "base64"));
        });
    }
}).listen(1337);

워커의 유일한 훅은 town!queue(page, data, next) 이벤트입니다. Ghost Town이 모든 것을 자동으로 관리하므로 page는 언제나 새로 생성한 PhantomJS 페이지입니다. 페이지를 설정하고, 마스터에서 받은 data를 처리한 뒤, next(err, data)를 호출해 결과를 돌려주기만 하면 됩니다. 버즈빌에서는 HTML 문서를 렌더링합니다. 워커가 페이지의 크기, 헤더, 콘텐츠 등을 설정하고 이미지로 렌더링합니다.

town.on("queue", function (page, data, next) {
    // setup code
    // sequentially configure the page here
    // page.set("viewportSize", ...)
    // page.set("customHeaders", ...)
    // page.set("onLoadFinished", ...)
    // page.set("content", ...)

    page.renderBase64("jpeg", function (data) {
        next(null, data);
    });
});

마무리

Ghost Town은 한 달 넘게 대규모 프로덕션 환경에서 성공적으로 동작하고 있으며, 기존에 직접 동시 처리를 관리하던 방식보다 크게 개선된 결과를 보여주고 있습니다. Node.js v0.12가 출시되면 더 나아질 것으로 기대합니다. MIT 라이선스로 공개한 Ghost Town이 커뮤니티에도 도움이 되길 바랍니다. 즐겁게 코딩하세요!

전체 보기