あまり書き慣れていないので、まとまりのない文章になるであろうことはご容赦ください。
フレームワーク概要
https://crates.io/crates/senax
説明が https://yossyx.github.io/senax/ にありますが、古すぎて全く参考になりませんので、読まなくても良いですし、説明ができていないので試しに動かしてみようとかされなくても良いです。
開発開始から5年以上経過しており、現在の状況としては、Symfony1 のようなyamlファイルを記述すると、DBとの差分マイグレーション、ORM、GraphQL APIコードを自動生成します。
大手SIerに採用され、政府系案件に実際に導入されています。
そこでは、Rustの知識がまったくないクライアント側開発者がDBの構成を考えてyamlファイルを記述すると、コードを再生成してビルドするだけでサーバの修正が完了し、特殊なAPI実装のみRust技術者が記述すれば良いという開発環境ができています。
病状説明
指定難病というのは具体的にはパーキンソン病で、さらにおまけで五十肩と脊柱菅狭窄症まで付いてきます。
パーキンソン病というのはドーパミンが出なくなり片手が震え続けていて、将来的には体が動かなくなる病気で、脊柱菅狭窄症は足に痛みがあり歩きにくくなる病気です。
それぞれは関係なさそうに見えますが、パーキンソン病でずっと左手が震えているので、その影響で左肩が五十肩になり、パーキンソン病の薬を飲み始めた1週間後に異常脱力状態で悪い姿勢を取っているときに腰が変に曲がって痛みが走り、脊柱菅狭窄症になったようです。異常脱力状態というのは私の造語ですが、うつ病の薬を飲んだときなど、普通の人がどんなに力を抜いてもそれ以上抜けないような最低限の支える筋肉の力が抜けてしまう状態で、私の経験では昔、電車に乗っているときに顔の表情筋の力が抜けてしまい、向かいに座っていた女性が悲鳴を上げたことがあります。その時どんな顔をしていたのか再現できないので分かりませんが。
それでも症状的にはまだ軽症と言えるのですが、問題は薬で、1ヶ月以上全く開発が進まない時期があり原因がわからなかったのですが、これが脊柱菅狭窄症の鎮痛薬の副作用でした。本人でも単にサボっているだけとしか感じないのですが、薬を止めると明らかに集中力が戻ってくるのです。
また、パーキンソン病の薬のレボドパ・ベンセラジド塩酸塩を飲むと不眠になり、止めると眠れるようになるという不眠の副作用を4回ほど繰り返して確認しており、薬の説明にも不眠の副作用があり注意するように記載されているにも関わらず、医者はそんな副作用は存在しない、嫌なら飲まなければ良いというスタンスで解決方針が見えていません。手の震えも全く収まる様子がなく、考えようとするたびに手が震えて邪魔します。
つまり、薬を飲めば問題ないわけではなく、開発に必要な集中ゾーンに入れず、パーキンソン病は脳の病気なので年単位で見ると開発力が劣化することは避けられないと思われます。
フレームワークの特徴
高速性
Rustは速い言語なのでサーバに使いたいというのは当然ですが、よく考えるとWebサーバが速くなってもDBサーバがそれについてこれなかったら意味がないんじゃないかと。そこで、キャッシュが重要になってきます。リレーションを含めたオブジェクトの状態でキャッシュすることによって、検索時はidのみを取得するSQLをDBサーバで実行し、その他のカラムや、リレーションの内容はプロセス内のキャッシュから取得することによって高速化し、更新時は自動的にキャッシュを更新するようになっています。
ローコード
Symfony1 はyamlファイルからコード生成だったのですが、その後はコード内にアノテーションでリレーション構造を埋め込むのが主流でした。その場合、コードの自由度は上がりますが、プログラマがコードを書く必要があります。yamlファイルからのコード生成に戻ることにより、コードを書く必要がなくなり、ローコードのようになりました。将来的にはクライアント側も管理画面程度は自動生成できるのではと考えています。
多数のテーブル対応
Ver 0.3では数個のクレートにしか分割していなかったので、500テーブルぐらいある場合にデバッグビルドに1時間以上掛かる可能性がありました。現在開発中のVer 0.4では500テーブルを機能的に50グループに分けるとして、200クレートぐらいに分割することにより3分ぐらいでビルドできるようになってます。rust-analyzerがメモリ不足で落ちるのは間違いないので試してませんが。。。
ちなみに分割したグループ間の相互参照は普通にクレートで考えるとできませんが、対応しています。
その他
・ドメインについてはSenaxに依存しないクリーンアーキテクチャコード生成
・Aurora高速フェイルオーバー対応
・遅延一括更新によるカウンタなどの高速化
課題
PostgreSQL, axum対応
現在のバージョンではMySQL、Actix構成のため、PostgreSQL、Axumにも対応中。
PostgreSQLにはunsignedがなく、sqlxはunsignedかsignedかを厳密に区別しないといけないのが地味に嫌な感じ。Senaxではunsignedをデフォルトにしていて、データベースにsignedを保存するのがレアケースと考えているので。
キャッシュ改善
Mokaは優れたキャッシュライブラリですが、管理領域がメモリをどれだけ使用するかコントロールできません。また、クラウドのコンテナではメモリスワップが利用できないので、かなり余裕のある設定が必要です。
そのため、メモリマップトファイルを使用して、スワップファイルの代わりにキャッシュ領域をメモリとストレージ間で柔軟に移動できるキャッシュを開発中です。Ver 0.3の説明にあるディスクキャッシュは実装が雑すぎたので、それとは別になります。
ヒット率ではなく、Fargate エフェメラルストレージ想定で安く大容量での改善に期待。
many_to_many対応
many_to_manyは更新時に曖昧なのでhas_manyからのbelongs_toで良いのではと思って対応していませんでしたが、一応対応も検討中。
リンカー
キャッシュをサーバ間で同期するための仕組みをリンカーと呼んでいます。なんかもっと良い名前があったら教えて下さい。
現状の実装では従来型のサーバ上でサービスを無停止でホットデプロイするために内部通信中継のためのプロセスを挟むようになっていて、クラウドのコンテナをローリングアップデートするような想定になっていません。
中継用のサービスを立ち上げれば可能ですが、これを中継用サービスなしで可能なように対応予定です。
その他
・クライアント側自動生成
募集について
いきなりコードを書いてとは言いません。とりあえず興味のある方に説明しながらドキュメントを整えていければという感じです。
ドーパミンが出ないならノルアドレナリンを出せばいいじゃないと言ったとか(言わない)、なんとか私のモチベーションが維持できるようにお願いします。今年はともかく、来年以降の収入のあても心配です。
ドキュメントを書ける方とか英語が得意な方とかクライアント側実装が得意な方とかも歓迎です。
興味ある方は下記のどちらかにコメントください。
https://rust-lang-jp.zulipchat.com/#narrow/channel/525419-progress.2FSenax.E9.96.8B.E7.99.BA
https://x.com/yossyX1
雑記
定番のベンチマークについて
つい最近、RustとGoとPythonを比較してRustがPythonより遅いという記事を見かけて、未だにこんな勘違いをしている人がいるのかと思ったので、下記に注意。
・cargo run -r
でリリースモードで実行
・基本的に非同期なのでスレッドスリープではなく tokio::time::sleep().await
を使用する
Actixかaxumか
Actixはオールインワンという感じで必要なものが揃っていて使いやすいです。axumはtokioやhyperと分業しているので、またがる処理が難しいと思います。例えば、接続してから初回のヘッダー部分を処理するまでのタイムアウトとか、アクセスログで最終的に何バイトを返したのかわからなかったり。
ただ、Actixはワーカースレッド間でタスクが移動しないので、CPU負荷のかかる処理が入ると他のあらゆるところで原因不明のタイムアウトが発生します。そういうのはブロッキングスレッドで実行が定番ですが、500MBぐらいのGraphQLレスポンスを返す場合、async-graphqlでのJSON化に非常に時間がかかり、自分が呼び出すのではなく、呼び出される側なのでどうしようもありません。axumはワーカースレッド間でタスクが移動するので、多めににスレッドを立ち上げておけば緩和されるはず。
たまたま、Go言語の記事を見かけたらGoはプリエンプティブで10ms以上負荷が続くと強制的に切り替えられるそうで、ちょっと羨ましいですね。Rustでも対策はされているようですが、https://tokio.rs/blog/2020-04-preemption
あと、特に問題というわけではないですが、ActixはURL末尾のスラッシュ有無が曖昧で、axumは厳密に区別するので注意が必要です。
tracingについて
Senaxではtracingはあまり対応していません。
これはtracingが日本でも有名になり始めた頃に使ってみたのですが、リクエストの開始時にリクエストIDを保存すれば下層でもリクエストIDを参照してログに出力できるというのが、1リクエストでは問題ないのに負荷テストをかけると全然できていなかったので、使用していません。Actixとの相性が悪いのかもしれませんが、この問題についてご存じの方はおられますでしょうか。バグ報告とかすべきと言われるかもしれませんが、tracingに根本的に出来てないのではと切り込む勇気はありませんでした。
また、tracingにnon-blocking-loggerというのがあったのですが、これが全然ノンブロッキングじゃなかったので、ベンチマークが上がらずMokaに疑いがかけられてFAST_CACHEという機能が作られました。Mokaの冤罪が晴れた後、ノンブロッキングロガーを作り直して解決しました。現在のtracingではtracing_appender::non_blocking()のようになっているようですが、これと同じだったか変わったのか覚えていません。