제공 :
한빛 네트워크
저자 : Amy Unruh
역자 : 이대엽
원문 :
Asynchronous Processing with PHP on App Engine
참고사항: 구글 클라우드 플랫폼 개발자 관계팀에 근무 중인 에이미 운러는 올해 OSCON에서 환상적인 강연자 중 한명입니다. 에이미의 강연이나 다른 여러 멋진 강연에 참석할 의향이 있다면
OSCON 웹사이트를 방문하신 후 할인 코드(OS13PROG)를 이용해 등록비를 20% 할인받을 수 있습니다.
올해 구글 I/O에서는 구글 클라우드 플랫폼의 일부인 구글 앱 엔진용
PHP 런타임이 출시됐습니다. 앱 엔진은 구글에서 자체적으로 제공하는 각종 애플리케이션을 구동하는 것과 동일한 확장 가능한 인프라스트럭처를 사용해 웹 앱을 개발할 수 있는 서비스입니다. 앱 엔진을 이용하면 유지 보수해야 할 서버가 없으며, 애플리케이션을 업로드하기만 해도 실행할 준비가 끝납니다.
앱 엔진의 서비스는 앱 개발의 다양한 측면을 지원하고 단순화합니다. 그러한 서비스 가운데 하나는
작업 큐(Task Queue)인데, 작업 큐를 이용하면 여러분이 제작하는 PHP 앱에
비동기 백그라운드 처리(asynchronous background processing)를 더해 애플리케이션을 동시에 좀 더 응답성 있고, 신뢰할 수 있으며, 확장성 있게 만들 수 있습니다.
앱 엔진 작업 큐(App Engine Task Queue) 서비스를 이용하면 애플리케이션에서 작업을 정의하고 그것들을 큐에 추가한 다음, 큐를 이용해 작업을 백그라운드에서 동시에 처리할 수 있습니다. 그러고 나면 앱 엔진에서는 자동으로 큐 설정과 처리 용량에 맞춰 처리 규모를 확장/축소합니다. 작업(Task)을 정의할 때는 매개변수(선택적인)나 작업 내용 및 다른 설정과 함께 작업에 대한 핸들러의 애플리케이션에 특화된 URL을 지정한 후 그것을 작업 큐에 추가하기면 됩니다.
예제
PHP 앱 엔진 앱에서 작업 큐를 이용하는 것이 얼마나 쉬운지 살펴봅시다.
사진 공유 사이트를 제작하고 있다고 가정해 봅시다. 이 사이트에서는 다수의 사용자가 이미지를 업로드할 수 있습니다. 사용자가 사이트에 사진을 올릴 때 몇 가지 영상 처리를 거친 후 다양한 크기의 이미지를 생성해서 저장하고 싶다고 해봅시다. 하지만 이 같은 작업이 진행되는 동안 사용자를 마냥 기다리게 할 필요는 없습니다. 그렇게 하는 대신 작업을 백그라운드에서 처리하겠습니다.
이를 위해 여기서는 앱 엔진 작업 큐를 사용하겠습니다. 먼저 영상 처리를 하는 작업 요청 핸들러를 정의합니다. 이 핸들러를 /image_processing.php라고 하고 file_id(업로드된 이미지 파일을 가리키는 문자열)와 파일을 업로드한 사용자의 user_id로 두 개의 매개변수를 받는다고 가정해 봅시다. 이 핸들러의 URL을 이용해 다음과 같이 작업 큐의 작업을 정의합니다.
require_once "google/appengine/api/taskqueue/PushTask.php";
use googleappengineapi askqueuePushTask;
$task = new PushTask("/image_processing.php", ["file_id" => $fileid, "user_id" => $userid]);
PushTask 생성자의 첫 번째 인자는 작업 핸들러의 URL이고, 앞의 두 매개변수를 두 번째 인자로 전달합니다. 기본적으로 작업은 POST 요청으로 수행되므로 이것들은 POST 매개변수로서 접근할 수 있습니다.
그리고 나서 다음과 같이 앱의 default 작업 큐에 작업을 추가합니다.
$task_name = $task->add();
image_processing.php 핸들러가 실행되면(백그라운드 작업으로) 작업을 정의할 때 사용한 매개변수에 접근할 수 있습니다.
$file_id = $_POST["file_id"];
특정 큐에 작업이 얼마 없으면 해당 큐에 들어 있는 작업은 (거의) 곧바로 클라이언트 요청/응답 주기로부터 비동기적으로 실행될 것입니다. 좀 더 일반적인 경우라면 특정 작업 큐에는 여러 개의 작업이 존재할 수 있습니다. 앱 엔진은 자동으로 큐 처리 용량을 큐 설정과 희망하는 처리량에 맞춰 처리 규모를 확장/축소하고 성공적으로 처리된 후에는 작업을 삭제합니다. 작업이 실행하는 데 실패하면 앱 엔진에서는 여러분이 설정할 수 있는 기준을 토대로 작업을 재시도합니다.
작업 팬아웃과 작업 스케줄링
다음으로 사진 공유 사이트의 각 사용자의 경우 정기적으로 사용자의 소셜 미디어 피드로부터 사진을 가져오고 싶다고 가정해 봅시다. 소셜 미디어 계정을 여러 개 가지고 있는 사용자가 많을 경우 이렇게 하려면 백그라운드에서 처리할 양이 많습니다! 하지만 앱 엔진 작업 큐와 작업 팬아웃(fanout)을 이용하면 높은 처리량으로 이러한 처리를 할 수 있습니다.
먼저 /ingestion.php라고 하는 피드 처리를 시작할 핸들러를 작성합니다. 이 핸들러에서는 각 사용자 계정에 대한 피드 처리를 수행합니다. 사용자 계정 처리 핸들러를 /user_processing.php라고 해봅시다.
모든 사용자 처리 작업을 수행하는 /ingestion.php의 코드는 다음과 같습니다.
require_once "google/appengine/api/taskqueue/PushTask.php";
use googleappengineapi askqueuePushTask;
// ... 모든 사용자 계정의 정보를 가져옵니다...
foreach ($user_accounts as $acct) {
$task = new PushTask("/user_processing.php", ["user_account" => $acct]);
$task_name = $task->add();
}
여기서는 굉장히 많은 수의 "사용자 계정" 작업을 작업 큐에 추가하고 있습니다(그리고 이러한 작업을 훨씬 더 빨리 추가하기 위해 이 단계를 공유할 수도 있습니다). 그러고 나면 다수의 사용자 계정 작업이 동시에 처리될 것입니다.
특정 사용자에 대한 작업이 실행되면 다시 한번 하위 작업들(이 경우 사용자가 설정한 각 소셜 미디어 소스)을 수행합니다. 그러므로 /user_processing.php의 코드는 다음과 같습니다.
require_once "google/appengine/api/taskqueue/PushTask.php";
use googleappengineapi askqueuePushTask;
if (isset ($_POST["user_account"]) {
$user_account = $_POST["user_account"];
// ... 사용자의 계정 정보를 토대로 사용자의 소셜 미디어 소스를 가져옵니다...
foreach ($sources as $source) {
$task = new PushTask("/ingest_source.php", ["user_account" => $user_account,
"source" => $source]);
$task_name = $task->add();
}
}
이번에도 여러 가지 작업을 큐에 추가하고 있는데, 그러한 작업들은 동시에 처리될 것입니다. 이러한 팬아웃 패턴을 이용하면 손쉽고 빠르게 굉장히 많은 작업을 처리할 수 있으며, 앱 엔진 앱에서는 필요에 따라(작업 큐 설정 명세에 따라) 추가적인 인스턴스를 가동함으로써 상황에 맞춰 처리 규모를 확장/축소합니다.
마지막으로 가령 2시간마다 실행되는 크론 잡(cron job)을 통해 핸들러(피드 처리 팬아웃을 시작하는)를 시작하게 합니다. 크론 잡을 설정하려면 다음과 같은 내용이 담긴 cron.yaml 파일을 앱에 배포하기만 하면 됩니다.
cron:
- 설명: 사용자에 대한 피드 처리 크론 작업
url: /ingestion.php
schedule: every 2 hours
크론을 설정하고 나면 2시간마다 모든 사용자에 대한 피드 처리를 수행할 것입니다. 앱 엔진에서는 증가된 활동을 처리하기 위해 자동으로 처리 규모를 확장할 것이므로 백그라운드 처리가 클라이언트 요청 처리에 영향을 줄까봐 걱정하지 않아도 됩니다.
지금까지 작업 큐로 할 수 있는 것에 대해 수박 겉