ErlangのスケジューラGASchedulerがオープンソースに

フリーで使える分析プラットフォームであるGameAnalyticsは、gaschedulerをオープンソースにした。gaschedulerは Erlangのライブラリで、分散タスクを並列に実行する汎用スケジューラを提供する。InfoQは作者であるChris de Vries氏に話を聞いた。

Gaschedulerの設計はいくつかのアイディアに則っている。

  • スケジューラはクライアントからリクエストを受け、ペンディングキューとランニングキューを通じて処理する。
  • クライアントはスケジューラにコールバックを送信する。コールバックはふたつのキューのどちらかに入る。
  • nodeがリクエストを処理できるようになると、スケジューラはペンディングキューからランニングキューへリクエストを動かし、ノードへリクエストを送り込む。
  • ノードはコールバックを処理するためのプロセスのプールを持つ。
  • ノードの処理が終わったら、タスクのステータスをクライアントに非同期で送信する。
  • 失敗した場合に備えて、ワーカーノードはリトライができる。耐久型の失敗でない限り、何度でもリトライできる。

下の図は、クライアントとスケジューラ、ノードの間のメッセージの流れを描いている。

スケジューラが作成され開始されると、クライアントは、次のコードのように使う。

    %% Execute hello:world(1) asynchronously.
    %% In the hello module exists, world(N) -> N.
    ok = gascheduler:execute(Name, {hello, world, [1]}),

    receive
        {Name, {ok, Result}, Node, MFA = {Mod, Fun, Args}} ->
            io:format(“hello world ~p from ~p~n”, [Result, Node]);
        {Name, {error, Reason}, Node, MFA = {Mod, Fun, Args}} ->
            io:format(“task ~p failed on ~p because ~p”, [MFA, Node, Reason])
    end

スケジューラに送られるコールバックは、副作用はなく冪等性を持つ。

InfoQは、GameAnalyticsにgaschedulerの詳細を聞いた。

GameAnalyticsについて簡単に教えてください。

GameAnalyticsはゲーム開発者の為の無料の分析サービスです。ユーザ獲得やエラーの追跡のような一般的な指標やプレイヤーのエンゲージメント、マネタイズ、プレイヤーの進捗、ゲームに特化したイベントタイプやメトリクスを設計しています。

また、ゲーム開発者向けにSDKを提供しています。簡単に使えるSDKで、5分で利用開始できます。

Erlangが適してるのはなぜでしょうか。

GameAnalyticsはErlangを使って大規模なウェブトラフィックを処理できる、リアルタイムな対障害システムを構築しています。また、Erlangはスタートアップにとってもアドバンテージがあります。コードを素早くシップして、ホットロードして、最小限の努力で複雑な分散システムの要素を管理できる。

また、Native Interface Functions (NIFs)を使って、Cで作ったモジュールとErlangを統合しています。別の私たちのオープンソースプロジェクトで実装しているHyperLogLogデータ構造を使うことで、ネイティブの実行を使って、優れた性能を実現しました。Erlangを使えば、素晴らしい並列処理と分散プロセッシングの特徴を使って、素早くプロトタイピングができます。また、Cのようなネイティブ言語のコードの重要な部分を最適化できます。

gaschedulerはどのように使っているのでしょうか。

gaschedulerライブラリは、インフラのコードとビジネスロジックの分離がしっかりと定義されていなかった既存のコードをリファクタリングしたものです。当初、このライブラリは、小さなイベントバッチの処理で使われていました。そのバッチは、他のいくつかの分散システムと通信し、イベントを注釈するものでした。そして、そのコードを他のキューが必要なバックエンドシステムで再利用したのです。スケジューラ内で数千のタスクをキューする場合もありますが、システムのリソースのために、同時に実行するタスクは制限したいと考えています。

また、タスクは失敗したらリスタートできるようにしておきたいです。分散システムはいつでも恐ろしい状態になりえます。また、スケジューラによって複数のマシンでタスクを実行できるようになりました。キャパシティを必要に応じてスケールできるのです。これが重要なのは、私たちのようなゲーム産業は常にトラフィックが増えているからです。Erlangはこのような動きを監視するのに大きな力を発揮します。長年、C++のプログラマをやってきましたが、私はErlangのソリューションがより正確でより読みやすくわかりやすいと思います。機能全体がひとつのモジュールに適合し、1回のコードレビューで簡単に把握できます。Erlang VMのこの賢い設計の利点を活用しています。

gaschedulerの設計の理論的根拠を教えてください。

理論的根拠はシンプルさです。入力を受け取り出力をするタスクがあります。このようなタスクを複数のマシンで実行し、失敗した場合はリスタートできるようにしたいです。このライブラリは、MapReduceのマップのシンプルな実装に見えますが、分散ファイルシステムの仕事はしません。その部分はスケジューラを実装するアプリケーションに任されています。例えば、開発者はAWSのS3のような分散ファイルシステムにデータを保存し、s3erlライブラリを使うかもしれまんせん。また、私たちは、コールバックのキューを管理し、処理をするノードがダウンしたり、例外が発生した場合にコールバックが確実に実行されるようにする必要があります。これによって、もうひとつの設計根拠が生まれました。タスクは一貫性に問題を発生させずに複数回実行できる、ということです。私たちはこの問題を処理の冪等にすることで対処します。1度以上タスクを実行しても、同じ状態に適用すれば、同一の解に達するということです。

将来は、マルチマスタ設計にして、クラスタ内に複数のマスタを持つようにするかもしれません。しかし、現時点では、ひとつのマスタで十分だと考えています。単一障害点になってしまいますが。

なぜこのライブラリは今のような状態になったのでしょうか。

イベントデータを処理するという、私たちが現実に取り組んでいるケースから生まれました。開発を推し進めた一番の要因はコードの再利用です。すでに持っているのと同じような機能が欲しいと思っていましたが、コードが再利用できなかったのです。それで、オープンソースのライブラリにして、誰でも使えるようにすることにしたのです。

プロセスのかわりにタスクを使うのは、メモリやディスク、ネットワークIOなどの限られたリソースしかないからです。すべてを同時に実行したら、問題が発生し、利用できる物理メモリを超えてしまうかもしれません。Erlangは、メッセージングの世界のアプリケーションでは、ミリ秒のレスポンスタイムで知られていますが、私たちが実行するタスクはErlangの世界ではかなり長く実行されるもので、処理に数秒かかります。

gaschedulerのタスクはErlangのプロセスの薄く抽象化したものです。ユーザはどこでどのようにタスクが完了したかを意識する必要はありません。gaschedulerが仕事の割り当てを自動でやってくれます。各タスクはErlangのひとつのプロセスの中で実行されます。しかし、定型的な対障害の仕組みは、タスクの抽象を通じてgaschedulerによって行われます。私たちが再利用できると思ったのはこのロジックだったのです。