@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

2017年にやった技術的なチャレンジ

d.hatena.ne.jp

をみて、なるほどと思ったので、自分の成果も棚卸しをしておきます。

一年を通して

一年を通して見ると、Rubyを使ったHTTPサーバーで非同期に結果を返し、ブラウザで非同期に受け取った結果を画面に反映した一年でした。

2016年に比べて成長を実感するのは、一見難しいことを簡単に実装できるようになったことです。 「DOM更新アルゴリズムの実装」「同時処理リクエスト数制限の実装」は一見難しそうです。 やる前は、「正しく動くようにするのに1〜2週間はかかるかな」と思いました。実際にやってみたら数日で実装できました。

まだ、自分でも何が変わったのかはよくわかっていません。 必要な要件から、問題の核心をより小さいサイズに絞り込む能力が身についたのかもしれません。

伝統武術のでいう「心・技・体の体」ができたような気がします。 そのつもりで、今年は体を生かして、技を増やそうと思います。 今までやったことないことに、色々チャレンジしようと思います。

チャレンジ

UIのデザイン変更のためにDOM更新アルゴリズムの共通化

LODQA : Question-Answering over Linked Open Dataユーザーインターフェースの大幅な修正を行いました。表示項目は今までの開発経験により絞り込めていましたが、リンクドデータに馴染みのないユーザ向けに配置や表示順を工夫する必要がありました。特に検索結果を詳細に表示するのではなく、サマリーをまとめて表示する変更を行いました。

これまで検索結果の表示は、受け取った結果を随時、末尾に追加するのみでした。ブラウザのDOM APIを使って、DOM要素を追加して表示を更新していました。サマリー表示のためには、追記の他に既存DOM要素の中間への挿入が必要になりました。

そこでReactのVirtualDOMのような差分更新アルゴリズムが欲しくなりました。一方、すでにHTMLテンプレートとしてHandlebarsを使っていました。これをJSXに置き換えることは避けたいです。

そこで、DOM更新アルゴリズムを自前で実装し、DOM APIを使ったDOM要素更新処理を置き換えました。これにより、アプリケーション上のDOM更新のロジックは新規作成と更新が全く同じになりました。プレゼンテーションロジックの記述量が減ったため、修正工数も減りました。大幅なユーザーインタフェース変更に対応することができました。

この時作ったDOM更新アルゴリズムの説明は

ledsun.hatenablog.com

にあります。

サーバサイドのクエリ生成・実行処理の無限リスト化

LODQA : Question-Answering over Linked Open Data では

  1. 自然言語(英語)の質問からSPARQLへの変換
  2. 登録されている全てのSPARQLエンドポイントへの問い合わせ

を自動で行います。ここで問題になったのは特定の英文をSPARQLに変換する際に、サーバ上のメモリをGB単位で消費することでした。その英文からは、50万を超えるSPARQLを生成してしまいます。

そこでRubyEnumeratorクラスを作って

  1. SPARQL生成
  2. SPARQL実行
  3. クライエントへの結果送付

を逐次的に行う実装に修正しました。 これによりサーバのメモリ消費を、同時処理リクエスト数によらず、100MB程度に抑えることができました。

サーバ処理を並列化するときに懸念されるのは、レスポンスの悪化です。 クエリのボトルネックはSPARQLエンドポイントにあります。 SPARQLの生成に比べるとSPARQL実行の方が10倍以上遅いです。 サーバのレスポンス低下は、ユーザーから見ると誤差の範囲でした。

Wampリクエスト処理の並列化

IoT機器と通信を行うRailsアプリケーションを作成する際にWampRailsというGemを使いました。 性能上の要件を満たすために、Wampリクエスト処理の並列化をしました。 WamapRailsはEventMachineに依存しているため、EM.deferメソッドを使ってマルチスレッド化することで並列化を実現しています。 これによりRailsの1リクエスト=1スレッドというRail Wayを外れることになりました。

まず、高負荷時に応答を返せるように、同時処理数に上限を儲けました。 たとえば、Rails+Pumaではワーカスレッド数で同時処理リクエスト数を制御しています。 同様の制限を行います。 WamapRailsは非同期にレスポンスを返すためにDeferインスタンスを生成します。 この生成処理を利用し、Deferインスタンスの同時生成数を管理することで、同時リクエスト数制限を実装しました。

もしこれをスレッドプールとして実装した場合は、スレッドプールのテスト自体の工数がバカになりません。 さらに、Pumaのスレッド管理、EventMachineのスレッド管理が既に存在するので、運用上スレッドに何らかの問題が見つかった場合に、原因切り分けが困難になるでしょう。

次にDBコネクションの解放処理を追加しました。 RailsではRackミドルウェアで使用済みのDBコネクションを解放しています。 WamapRailsのための作成したスレッドは処理を終了しても、Rackミドルウェアを呼び出すことはありません。 これもDeferインスタンスを活用し、レスポンス返送時に、明示的にDBコネクションを解放することで対応しました。

RailsのDBコネクションプールの統計値を取得

あるRailsアプリケーションで複数のデータベースを扱いました。

この時、Google Cloud Platform上のデータベースとのコネクション状態がActiveRecordのコネクションプール場では検知できない現象に悩まされました。 実際のコネクションが切断されていてもコネクションプール上では切断が検知されず、実際にSQLが発行されるときになって例外が出る現象に出会いました。

残念ながら、原因を特定するに至りませんでした。 アプリケーションの特性上、GCP上のデータベースは参照機会が限られていました。 データベース利用終了時にコネクションを明示的に切断して対応しました。

この調査の際にActiveRecordのコネクションプールの状態を取得したかったのですが、Rails 5.0には適当なAPIがありませんでした。そこでRails5.1に追加された、ActiveRecord::ConnectionAdapters::ConnectionPool.stat をバックポートして使いました。

その他

Redmineプラグインの作成

ledsun.hatenablog.com

IoTゲートウェイモデリング

ledsun.hatenablog.com

技術同人誌執筆

ledsun.hatenablog.com

ledsun.hatenablog.com