技術本部でログ解析基盤を担当している善明です。
Amebaのログ解析基盤Patriot向けに開発したワークフロースケジューラーをオープンソースとして公開したので、その紹介をさせて頂きたいと思います。
開発の背景
Patriotのようなログ解析基盤では、データの取り込み、変換、集計という一連の処理の流れ(ワークフロー)を管理する必要があります。 これがないと、例えば、必要なデータがそろってないのに集計処理が実行され誤った数字がレポーティングされる、といった事態を引き起こしてしまいます。
PatriotではAmebaの様々なサービスのデータを扱っており、それを処理するためのワークフローは大規模で複雑なものになります。 また、例えば、ブログとピグの両方を使っているユーザ数などの複数のサービスをまたぐ集計も行っているのでサービス毎にワークフローを分割して管理するのは難しく、継続率や累計利用日数など処理によって必要となるデータの範囲も異なるためワークフローを期間(月毎など)で分割するのも適切ではありません。 このためには、一つの巨大で複雑なワークフローを効率的に管理する必要があります。
現在、様々なワークフロースケジューラが開発されておりオープンソースとして公開されているものも多くあります。 Patriotも初期は他のオープンソースのジョブ管理システムを使っていたのですが、上記の要件が強くなってくるとともに、既存のものでは対応が厳しくなり独自で開発しました。
アーキテクチャ
下の図はこのスケジューラのアーキテクチャです。
ジョブストアと読んでいるジョブ管理データベース(実際はRDBMSを使います)でジョブとその依存関係を管理し、各ワーカープロセスがジョブストアから実行可能なクエリを検索して実行します。
ジョブと依存関係は以下のようなモデルを想定しています。
各ジョブ(角丸四角形)をプロダクト(四角形)を参照して生成するものとします。 また、プロダクトは自身を生成するジョブが全て正常終了している場合に利用可能になるものとします。 そして、ジョブは自身が参照するプロダクトが全て利用可能である場合に実行可能になります。
例えば、上図のprocess1は参照しているプロダクトがdata1のみでそれを生成するジョブがimport1だけなので、import1が終了すれば実行可能になります。 それに対してprocessAllは参照しているプロダクトがall data のみですが、それを生成するジョブが二つあるので両方とも終了しないと実行可能になりません。
ジョブと依存関係は以下のようなDSLで設定します。 このスケジューラはRubyで実装されており、DSLもRubyの内部DSLになります。
sh{ require ['product1'] # run after 'product1' is created name "consumer" commands "echo 'this is a consumer'"}sh{ produce ['product1'] # this job creates 'product1' name "producer" commands "echo 'this is a producer'"}
上記では二つのechoコマンドを実行するジョブ(consumerとproducer)が定義されています。 参照、生成するプロダクトはそれぞれ、require、produceを用いて指定します。
インストールと設定
まず、スケジューラ本体のgemをインストールして、スケジューラのベースディレクトリを初期化します。
% sudo gem install patriot-workflow-scheduler% patriot-init ${BASE_DIR}
この時点でサンプルのジョブが動作できる状態になります。 ジョブの実行は以下のようにpatriotコマンドを用いて行います。
% cat test.pbcsh{ name "test" commands "echo '#{_date_}' > /tmp/test.out"}% ./bin/patriot execute 2015-04-01 test.pbc% cat /tmp/test.out2015-04-01
patriotコマンドの引数は、サブコマンド、ジョブの対象日、バッチ設定ファイルになります。 バッチ設定ファイル中の_date_は引数に指定したジョブの対象日で置き換えられます。
次にジョブをRDBMS(MySQL)で管理するための準備をします。 まず、データベースを作成します。データベース名やユーザ名等はなんでもいいです。 なお、最後でつかっているDDLはここにあります。
% mysql -u root -p> create database ${PATRIOT_DB};> grant all on ${PATRIOT_DB}.* to ${PATRIOT_USER}@'%' identified by '${PATRIOT_PASSWORD}';( > grant all on ${PATRIOT_DB}.* to ${PATRIOT_USER}@'localhost' identified by '${PATRIOT_PASSWORD}'; # if case of localhost)> exit;% mysql -u ${PATRIOT_USER} -h ${PATRIOT_DBHOST} --password=${PATRIOT_PASSWORD} ${PATRIOT_DB} < misc/mysql.sql
つぎににMySQLに接続するためのプラグインをインストールします。
% cd ${BASE_DIR}% sudo ./bin/patriot plugin install patriot-mysql2-client
最後に上でつくったDBを参照するための設定をします。${BASE_DIR}/config/patriot.ini を以下のように編集してください。 PATRIOT_DBなどの変数には上記で作成したデータベース名等を設定してください。
% cat ${BASE_DIR}/config/patriot.ini[common]plugins=patriot-mysql2-clientjobstore.root.class=Patriot::JobStore::RDBJobStorejobstore.root.adapter=mysql2jobstore.root.database=${PATRIOT_DB}jobstore.root.host=${PATRIOT_DBHOST}jobstore.root.username=${PATRIOT_USER}jobstore.root.password=${PATRIOT_PASSWORD}log_factory = Patriot::Util::Logger::Log4rFactorylog_level = INFOlog_format = "[%l] %d %C (%h) : %m"log_outputters = stdoutlog_outputter.stdout.class = Log4r::StdoutOutputter[worker]nodes=testnode.test.type=anynode.test.threads=1log_outputters = filelog_outputter.file.class = Log4r::DateFileOutputterlog_outputter.file.dir = /tmp/log_outputter.file.file = patriot-worker.log
これでジョブとその依存関係をMySQLで管理できるようになります。 MySQLへのジョブ登録は以下のようにregisterコマンドをつかって行います。
% cat workflow_test.pbcsh{ require ['product_#{_date_}'] # run after 'product1' is created name "consumer_#{_date_}" commands "echo 'this is a consumer'"}sh{ produce ['product_#{_date_}'] # this job creates 'product1' name "producer_#{_date_}" commands "echo 'this is a producer'"}% ./bin/patriot register 2015-07-01 workflow_test.pbc
また、ワーカプロセスの起動は、workerコマンドをつかって行います。
% sudo ./bin/patriot worker start
これで、起動したワーカが登録したジョブを実行します。
% tail -f /tmp/patriot-worker_2015-07-03.log[INFO] 2015-07-03 12:19:23 Patriot::Worker::MultiNodeWorker (main) : get 1 jobs[INFO] 2015-07-03 12:19:23 Patriot::Worker::MultiNodeWorker (worker_test_1) : executing job: sh_producer_2015-07-01_2015-07-01[INFO] 2015-07-03 12:19:23 Patriot::Command::ShCommand (worker_test_1) : start shell command[INFO] 2015-07-03 12:19:24 Patriot::Command::ShCommand (worker_test_1) : executing echo 'this is a producer': results stored in /tmp/patriot-workflow-scheduler/2015-07-03/jsh_producer_2015-07-01_2015-07-01_20150703_121923[INFO] 2015-07-03 12:19:24 Patriot::Command::ShCommand (worker_test_1) : echo 'this is a producer' is finished[INFO] 2015-07-03 12:19:24 Patriot::Command::ShCommand (worker_test_1) : end shell command[INFO] 2015-07-03 12:24:23 Patriot::Worker::MultiNodeWorker (main) : get 1 jobs[INFO] 2015-07-03 12:24:23 Patriot::Worker::MultiNodeWorker (worker_test_1) : executing job: sh_consumer_2015-07-01_2015-07-01[INFO] 2015-07-03 12:24:23 Patriot::Command::ShCommand (worker_test_1) : start shell command[INFO] 2015-07-03 12:24:24 Patriot::Command::ShCommand (worker_test_1) : executing echo 'this is a consumer': results stored in /tmp/patriot-workflow-scheduler/2015-07-03/jsh_consumer_2015-07-01_2015-07-01_20150703_122423[INFO] 2015-07-03 12:24:24 Patriot::Command::ShCommand (worker_test_1) : echo 'this is a consumer' is finished[INFO] 2015-07-03 12:24:24 Patriot::Command::ShCommand (worker_test_1) : end shell command
ワーカプロセスはジョブストアから定期的(デフォルトは5分毎)に実行可能なジョブを取得し実行します。 上のログをみると、最初のサイクルでproducerのジョブを事項し、次のサイクルでproducerジョブの完了により新たに実行可能になったconsumerジョブを実行していることが分かります。
まとめ
以上、簡単にAmebaのログ解析基盤Patriotで使っているワークフロースケジューラについて紹介させて頂きました。 Patriotでは、2012年くらいからこのスケジューラでワークフローを管理しており、一日あたり最大で13000超のジョブを管理してきました。(現在は整理、最適化をすすめ一日あたりのジョブ数は7000くらいになっています。)
また、今回は単純なコマンドを実行するジョブのみ紹介しましたが、ジョブのグループ化やテンプレート化、カスタマイズしたジョブタイプをプラグインとして追加可能など、複雑なジョブを効率的に実装、実行するための機能を組み込んでいます。(http://cyberagent.github.io/patriot-workflow-scheduler/pbc.html) 興味を持って頂けた方は試してみて頂ければと思います。