v3.8.0で入ったImprove path matching in the routerの変更でもRegExpRouterの速度を維持できた(むしろ速くなったかもしれない)理由

  • 投稿日:
  • by
  • カテゴリ:

これは何か

Honoのv3.8.0のリリースでは「Improve path matching in the router」が入り、内部で利用しているルーターの仕様が変更になったのですが、高速なルーターの実装であるRegExpRouterは変更に対応しつつも速度を維持することができました。これはその経緯を解説する記事です。

「Improve path matching in the router」とはどのような変更であったか?

RegExpRouterの話に入る前に「Improve path matching in the router」の説明をします。リリースノートにもありますが以下のような定義のとき、以前は1つのアプリケーション内で「異なる階層に、同じ名前のパラメータ」を指定すると例外が投げられてしまい実行できなかったのですが、これができるようになりました。

この例だけ見ると「なぜ今までこれができなかったのか?」というように見えますが、例えば以下のような定義のときに、今までは内部のインターフェイスが「`c.req.param()` の戻り値をすべてのハンドラで共有」となっていて、両方に一致した時に妥当な結果を返せなかったため、ルーティングの登録の時点で例外を投げる仕様にしていました。

この仕様を改善するために、「`c.req.param()` の戻り値をすべてのハンドラで共有」となっていた内部のインターフェイスを「`c.req.param()` の戻り値は対応するルーティングで指定されたもののみ」とするように変更して、パラメータ名がルーティング間で重複するような定義も受け入れるようにしたのが「Improve path matching in the router」の変更になります。

リクエストとレスポンスの対応で見ると、以下のように変わっています。

この変更のルーターへの影響

以前の仕様だとルーターが返すのは以下でしたが、

  • 一致したハンドラのリスト
  • 一致したパスのパラメータ

新しい仕様だと以下になります。

  • 一致したハンドラのリスト
  • ハンドラ毎のパラメータ

インターフェイスはだいたい以下のように変わっています。(最終的にはもう少し複雑になります。)

以前の仕様だと、一致したハンドラの数に関わらずリクエスト時に生成するオブジェクトの数は一定でしたが、このインターフェイスだとハンドラの数に比例して増えることになります。

オブジェクトの生成はどのランタイムでも特別遅いわけではないので、一般的なJSのアプリケーションの中ではそこまで避けるべきものでもないと思いますが、RegExpRouterは「JSのルーター界隈」という(ニッチな😀)世界で戦っているのでこの辺りが問題になりました。

RegExpRouterでの対応

RegExpRouterは(ご存知でない方は過去の資料を参照してください)正規表現の結果を可能な限りそのまま使うことで高速化しているので、それを活かせるようにルーター側のインターフェイスの方でも歩み寄ってもらって、以下のようにすることで対応しました。

急にまとまってしまって戸惑うかもしれませんが、minifyしたときのコードが小さくなるようにRecordからArrayにするというのも入れつつ最終的にこのようになりました。実際のコードではコメント付きでこんな感じになっています。詳細については実際のコードを見てもらうほうがいいかもしれません。

RegExpRouterに関してはこのインターフェイスにあわせて、上で出した例に対してはこのようなルーティングの結果を返すようになりました。

これは `[T, ParamIndexMap][]` と `ParamStash` のタプルですが、前者は `match()` の呼び出し前に準備しておくことができ、後者についてはコメントにある通り `String.prototype.match()` の戻り値そのものとなっています。`c.req.param(key)` の呼び出しではこのデータ構造から値を引いて返すので、その際には以前の仕様では発生しなかったオーバーヘッドが発生するのですが、実際の運用の環境でも多くは以下のような条件を満たすので、

  • 同じkeyで何度も `c.req.param(key)` が呼ばれるようなことはない
  • パスから取得するパラメータは多くても2〜3程度
  • middlewareは '*' や、静的なパス以下の '/js/*' のような使い方が多く、パラメータを取るケースは少ない

実運用の環境でも、そしてベンチマークでも、このオーバーヘッドが見える形で影響してくることはないものと思われます。

以上が、RegExpRouterが「Improve path matching in the router」の変更でも速度を維持できた(パラメータが少ない場合にはむしろ速くなるケースもある)理由です。

この記事について

この記事は Hono Advent Calendar 2023 の1日目の記事です。明日から毎日楽しみです。よろしくお願いします!