Goでジョブキューを実装した
HQというGoで実装したジョブキューを公開しました。
WebのUIもあります。
概要
以下の特徴があります。
- Goによる実装で、シングルバイナリ。
- スタンドアロンのHTTP APIサーバー。ジョブのデータベースも組み込みであるため、別途特別な依存を必要としないで動作する。
- シンプルでプログラミング言語非依存。HTTP APIでジョブを投入し、ジョブはHTTP POSTメッセージをワーカーアプリケーション(Webアプリ)に送信するというアーキテクチャ。
- フロントエンドとしてCLIとWebUIを組み込みでサポート。
上記のリポジトリのREADMEにも載せてありますが、ざっくりジョブのフローを図解すると、以下のようなアーキテクチャになっています。
HTTP APIでジョブ(JSON)を投入します。HQはジョブを取り出し、ジョブに記載されたURLにHTTP POSTして、別途用意されたワーカー用のWebアプリケーションにジョブを実行させる、という流れになっています。
HQ自体は、HTTP APIベースのキューだけを提供しており、ジョブの優先度にもとづく制御や、細かなプロセスのスケジューリングなどはありません。単にFIFOでジョブをキューイングして、WebアプリのURLにHTTPリクエストを送信するだけです。
HQがキューイングしているジョブは組み込みのCLIかWebUIで管理できます。以下はCLIでジョブの一覧を表示させた例です。
背景
自分が開発しているPHPのWebアプリケーションに、非同期に処理を実行する機構が欲しかったので実装をはじめました。自分が必要とする要件から、実装方針を整理すると
- 小規模のシステム構成に組み込めるように、シンプルなスタンドアロンのサーバープロセス。
- プロセスダウン時にジョブをロストしない、最低限の信頼性のあるデータストア。
- プログラミング言語非依存のインターフェース。
- 対障害性や分散処理は現状では求めていない。また、これらはジョブキュー自体に備わっていなくても、別レイヤで対応できる。
- ジョブの優先度設定や細かなスケジューリングができることは現状では求めていない。
となります。
技術解説
HTTP APIベースでのジョブキュー実装のアーキテクチャとして
をかなり参考にさせていただきました。基本的なコンセプトはほぼ一緒ですが、HQはよりシンプルに最小限のキューの実装と、DBを内蔵して外部依存を排除することで、小規模なWebシステムにも組み込みやすくしてあります。仮にシステムが大きくなった場合も、複数のインスタンスを立てて前段にロードバランサをおくなど、なんらかの対応ができると思っているので、まず小さく始めるのことができるように意識して実装しました。
キューからジョブを取り出すワーカースレッドを、Goルーチンで実装するコードは
の内部を参考にさせていただきました。
内蔵の組み込みデータベースは
です。トランザクションサポートありのKVSで、Goの組み込みデータベースとしてはかなり枯れているライブラリであると認識しています。
WebUIもバイナリにバンドルしてあります。TypeScript + React + WebpackによるSPAになっています。このへんは単に自分の好みと、新しめの技術に触ってみるためにあえて選んだ技術スタックになっています(今更ながらReact Hooksをはじめて使ってみました)。
実装後の所感
個人的に、Goはこのような独立性の高いミドルウェアを実装しやすい言語だと思っています。Httpサーバーをかんたんに実装でき、データベースのライブラリも豊富、WebUIむけのJavaScriptやHTMLなどのリソースをバイナリにバンドルするツールも揃っています。ところで、このようにスタンドアロンのサーバーを実装すると、機能面の実装のほかにも、実はいろいろと、考慮しないといけないことがあることに気が付きます。たとえば以下のようなことです。
- シグナルへの適切な反応と、サーバーのグレイスフルシャットダウン。
- ログのローテーション。または外部ツール(Linuxのlogrorateなど)でローテーションしたときにファイルハンドルを再オープンするための機構。
普段PHPなどでWebアプリを書くときは、アプリケーションサーバー(Apacheとかnginxとか)が面倒をみてくれるようなところです。そして、今回はWebUIもSPAで実装したので、これによって、サーバーサイドからフロントエンドまでの技術スタックを広く網羅して、小さくまとめた感じに実装できて、いい達成感を得ました。
自分の技術の棚卸しができた、という感じです。