Amazon SWFのActivityをGraceful shutdownさせる

このエントリーは、AWS Advent Calendar 2013の23日分です。


Amazon SWFを利用してワークフローを実行する際、概念的には以下のような構成になります。



SWFでメインで処理を行うのはActivityで、この数を増やす事で処理能力を上げられるのが、SWFの大きな利点です。

Activityの数を減らす

処理能力を増やすため、一時的に増やしたActivityは、通常はインスタンスをシャットダウンすることで、数を減らす事ができます。
しかしながら、シャットダウンしたインスタンスでActivityがタスクを実行中だった場合、その処理は無駄になってしまいます。


タスクが実行完了しなかった場合は別のActivityが再度実行してくれますが、出来れば実行中のタスクが終わってからインスタンスがシャットダウンできれば、処理を無駄にせずに済みます。

Graceful Shutdownを実装する

いくつかアイデアはあるかと思いますが、今回はシャットダウン実施自体をタスクにして実行する、という方法で実装してみました。


これがActivityのインターフェース

@Activities(version = "1.0")
@ActivityRegistrationOptions(defaultTaskStartToCloseTimeoutSeconds = 120, defaultTaskScheduleToStartTimeoutSeconds = 60)
public interface GracefulShutdown {
	@Activity(name = "gracefulShutdown")
	void gracefulShutdown();
}

そしてこちらが実装クラスです。

public class GracefulShutdownImpl implements GracefulShutdown {

	private ActivityWorker worker;

	public GracefulShutdownImpl(ActivityWorker worker) {
		this.worker = worker;
	}

	@Override
	public void gracefulShutdown() {
		
		worker.setDisableServiceShutdownOnStop(true);
		worker.shutdownNow();
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				// インスタンスシャットダウン
				System.out.println("Instance shutdown");
			}
		});
	}
}

キモはActivityでActivityWorkerに対する参照を持つ事と、ActivityWorkerのshutdownNow()を呼ぶ前にsetDisableServiceShutdownOnStop()を呼ぶ事です。この呼び出しをしておくと、通信を切らずにshutdownNowできるので、この処理(つまりGracefulshutdownのタスク)の返答を正しく返す事が出来ます。


これと対応するように、Workflowクラスを作成します。Activityのメソッドを呼び出すだけです。

//インターフェース
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60, defaultTaskStartToCloseTimeoutSeconds = 300)
public interface GracefulShutdownWorkflow {

	@Execute(version = "1.0")
	public void executeGracefulShutdown();
	
}
//実装クラス
public class GracefulShutdownWorkflowImpl implements GracefulShutdownWorkflow {

	private GracefulShutdownClientImpl activity = new GracefulShutdownClientImpl();

	@Override
	public void executeGracefulShutdown() {
		activity.gracefulShutdown();
	}

}

ここまで実装したあとは、ActivityWorkerとWorkflowWorker、そして2つのWorkflowStarterを実装します。
ActivityWorkerは、通常処理のActivityに加えて、GracefulShutdownのActivityを実行出来るように設定します。

public class MyActivityWorker {
	public static void main(String[] args) throws Exception {
		AmazonSimpleWorkflow swfClient = Helper.createSWFClient();
		String domain = Helper.getConfigHelper().getDomain();
		ActivityWorker worker = new ActivityWorker(swfClient, domain,Constants.ACTIVITY_LIST1);

		// 実行するアクティビティを登録
		worker.addActivitiesImplementation(new MyActivities1Impl());
		worker.addActivitiesImplementation(new MyActivities3Impl());
		//追加でGracefulShutdownを登録
		worker.addActivitiesImplementation(new GracefulShutdownImpl(worker));

		worker.start();
	}
}

WorkflowWorkerでは、通常処理のDeciderと、GracefulShutdownのDeciderを2つ動かします(別々のインスタンスでもいいですが、こちらのほうが効率がよいでしょう)

public class MyDeciderWorker {

	public static void main(String[] args) throws Exception {
		AmazonSimpleWorkflow swfClient = Helper.createSWFClient();
		String domain = Helper.getConfigHelper().getDomain();
		WorkflowWorker worker = new WorkflowWorker(swfClient, domain,Constants.DECIDER_LIST);
		//通常処理のDecider
		worker.addWorkflowImplementationType(MyWorkflowImpl.class);
		worker.start();
		//Graceful ShutdownのDeciderを登録
		worker = new WorkflowWorker(swfClient, domain,Constants.GRACEFUL_SHUTDOWN_DECIDER_LIST);
		worker.addWorkflowImplementationType(GracefulShutdownWorkflowImpl.class);
		worker.start();
	}
}

あとは通常処理を行いたい場合は、通常処理用のWorkflowStarterを使用し、インスタンスをシャットダウンしたい場合は、シャットダウン用のWorkfloStarterを使用します。

	public static void main(String[] args) throws Exception {

		final AmazonSimpleWorkflow swfClient = Helper.createSWFClient();
		final String domain = Helper.getConfigHelper().getDomain();

		final GracefulShutdownWorkflowClientExternalFactory factory = new GracefulShutdownWorkflowClientExternalFactoryImpl(
				swfClient, domain);
		final String workflowId = "shutdown"
				+ new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss")
						.format(new Date());
		final GracefulShutdownWorkflowClientExternal client = factory
				.getClient(workflowId);

		StartWorkflowOptions options = new StartWorkflowOptions();
		options.setTaskList(Constants.GRACEFUL_SHUTDOWN_DECIDER_LIST);
		client.executeGracefulShutdown();
	}

通常処理用とシャットダウン用でDeciderのタスクリストを分け、呼び分けるのがポイントです。


このGracefulShutdownStarterを呼び出すと、シャットダウンタスクがいずれかのインスタンスで実行されます。正しくタスクが呼ばれると、数分後にそのタスクを実行したActivityは終了します。今回の例のように、shutdownHookに引っ掛けておけば、インスタンスも落とす事が出来ます。
たとえば管理ツール等を作っておいて、このGracefulShutdownStarterを呼ぶようにしておけば、好きなタイミング/好きな数のインスタンスをGracefulにシャットダウン出来ます。


おわりに

今回はタスクを利用してシャットダウンさせる方法でしたが、要はActivityWorkerのshutdownを呼ぶ事が出来ればよいので、例えばActivityWorkerで定期的にファイルやタグを確認してシャットダウンする、という方法でも良いかと思います。