🦀

PHPで開発していたWEBサービスをRustに乗り換えてみた

2023/09/25に公開

はじめに

よくある話ですがPHPで開発していて色々辛い、GolangやRustなどの新しい言語に変えて一気にリプレイスしたい!
エンジニアならよくある衝動を抑えずに乗り換えてみた内容です。

乗り換える前の状況

2023年現在でフロントエンドはNuxt3、バックエンドはPHP8.0でLaravelは8系で運用していました。
正直これまでも何度もバージョンアップを繰り返しコードを整えたり、テストコードを書いたり次のバージョンアップでは手間なくアップデートできるように努めていました。
が、まぁそんな上手くいくわけでもなく影響範囲の調査や生きてるのかわからないコードが大量に残っていたりします。(何度繰り返してもこうなるの何故ー???)

Rustに乗り換えたからって解決するわけではないのはわかってますが
パフォーマンス面でも大量のデータを扱ったりする関係上メモリの枯渇や終わらないバッチなどが発生したりで辛く、単純に瀑速で有名なRustに興味を持ち乗り換えを決めました。

どう乗り換えたか

PHPからRustにするのに気持ち的には全部一気にしたいわけですが、そんな長期の工数が取れるわけでもなく少しづつ行いました。
全体の流れとしては

  1. メモリや速度がボトルネックとなってる大量データを扱うバッチ処理の移行
  2. WEBサービスから認証を伴わない部分のAPI移行(TOPページ・一覧ページの表示や検索)
  3. 認証が必要でサービスの基幹機能の移行
  4. バッチ処理の移行
  5. 管理画面系のAPI移行

の手順で行っています。

API側から仕様してる技術関連のサービスとして、

  • MySQL / ElasticSearch
  • Slack API
  • Google Spreadsheet
  • Yahoo API
  • SendGrid
  • S3

などがありましたが、PHPと違ってまともに運用できるライブラリはほとんど無くラッパーなど自前で実装して運用することになりました。
MySQLのコネクタなんて「mysql」→「diesel」→ 「sqlx」と何度変えたことか。。。

mysqlは名称的に選択してしまいましたが構造化との連携がかなり辛くてリタイヤ
dieselはある程度は良かったのですがORM的な方が強く、プレースホルダーの提供が無くて要件的にきつく最後にsqlxになりました。
RustだとQueryBuilderが使いやすいものなく、このあたりも自前での実装になります。

PHPからRustに変わってどうだったのか?

「Rustって速度が速い」って部分から話しますが、「WEB APIを置き換えてそんな速くなる?普通に言語的な差分って誤差的なものだし速度上げたければSQL部分見直さないと意味なくない?」って意見は事前にもらったりしてました。
これはホントその通りではあって、スロークエリ部分は解決しないと遅いものは遅いです。
ただ結果としてサービスの速度は向上しています。

APIの速度が改善されたポイントとしては

  • Laravelからactix-webの移行をしてるので、そもそもの初期レスポンスが全然違う(40msくらい何もしなくても改善してます)
  • 並列処理の取り入れ、DBアクセスが複数伴うような条件がある部分やログ書き出しを並列化
  • MySQLのコネクションプールを適切に設定したので、DBレスポンスが改善(Rust関係ない)
  • レスポンスで遅い部分は確実にDBボトルネックといって過言でないので、その部分のみを調整すれば改善できた。
    → クエリビルダー頼らないでSQLを書くようになったので、SQLのチューニングがしやすくなった(Rust関係ない)

といった感じです。
API以外の元々が負担かかっていた部分に関してはRustの恩恵は大きいです。
PHPと比べメモリ消費量が低く、OOM Killerが発生しがちでメモリ設定を上げていた部分の処理だと、3分の1程度に抑えられてバッチの実行時間は1時間程度かかっていたものが数分で終わるまで改善できています。
並列処理に関してはPHPでも出来なくは無いのですが圧倒的にRustでの実装が楽です。
tokio::spawnで並列化したい処理を書くだけで、受け取るも受け取らないも自由に選択できるのが良いです

async fn example() {
    tokio::spawn({
        async {
            // 並列化したい内容
        }
    })
}

メンテナンス面に関しては今の所良い感じで動作しています。そもそもPHPと違って型付言語なので配列に値入ってるかどうかで怯えることもなく、DBからの取得とAPI側の型定義(struct)さえ切り分けてれば全く問題ないです。
途中でデータを追加したり、構造を変更してもコンパイルさえ通ってば基本迷うことは無かったですね。(API側の定義変更してるのにフロント合わせないとか別よ?)

Rustに乗り換えた結果として

パフォーマンスチューニングをきちんとしてればPHPでも実装できそうだったり、RustでなくてGolangの方が採用も楽だしよいのでは?という意見もあるかもしれませんが、私はRustを選んでプロダクトの中心にして正解だったと思ってます。

PHPに比べRustの良い点として

  • 書き心地や他の言語に比べ圧倒的に良い
    • Result / Option →? を伝播させて使えること
    • if let / matchとenum / 並列処理の書きやすさ
    • From/IntoとTraitの考え
  • 良く言われるがコンパイルエラーが優秀
  • Laravelと違って処理が遅い部分の特定に時間がかからない、パフォーマンスチューニングが楽
  • メモリ消費量が少なく大量データの処理がすぐ完了するので開発検証が楽
  • 仕様の変更やコードの変更に対して必要以上に怯えなくなる
  • 不透明な処理・定義を曖昧に許容できない
  • DBテーブルの型定義、適当にしてたの辞めようと決心した(unsigned揺れてたりとか)
  • PHPのようにDocker必須の開発環境が必要でなくなりパッケージ管理が楽
  • 言語(Rust)のバージョンアップが楽

逆に今でも辛いのが

  • コンパイル時間が長い、GithubActionでDockerファイル作ってるのですがPHP3分に対してRust15分くらいかかってます。
  • ライブラリ(crate)が少ない
  • 偶に良さげなcrate見つけてもtokioのバージョンが合わずインストールできないことがある

といった感じです。
デメリットのコンパイル遅いのは開発PC強強にしてれば一定解消できるのと、無いライブラリは作れば良いです。
WEB系に関してはおそらく困るもの、ほとんど無いのかな?と思ってる次第(ぶち当たっったら解決できないとキツイかも?)

結果としてサービスに新規機能や修正を取り入れるスピードも上がり、SEOとしてのページスピードも改善されていて文句ないです。

まとめ

  • 自社開発で長期的なプロダクトに関しては、もうRust一択で採用予定
  • 「単発の開発」や「受託開発でメンテナンスを第三者に移譲するようなサービス」の場合PHP推奨
  • Rustはいいぞ!!!
GitHubで編集を提案

Discussion