Misskey & Webテクノロジー最前線

バックエンドのメモリ使用量の削減

本連載は分散型マイクロブログ用オープンソースソフトウェアMisskeyの開発に関する紹介と、関連するWeb技術について解説を行っています。

今回は、Misskeyバックエンドのメモリ使用量を削減させる取り組みについて紹介します。

健康診断

Misskeyでは定期的に実装の「健康診断」を実施しています。

まず、フロントエンドのバンドルを目視または分析ツールで解析したり、バックエンドにChromeのインスペクタを接続するなどして、バンドルサイズがどれくらいか・メモリをどれくらい使用しているか・それらの内訳はどうなっているかを確認します。

次に、得られた情報をもとに、無駄なデータが含まれていないか・データを削減する余地はないかを検討し、必要に応じて改修を行います。

開発が継続して行われていると、気づかないうちに不要な参照が発生していたり、不必要に大きなパッケージへの依存が発生していたり、メモリを無駄に消費するデータ構造が生まれていたりすることがあります。

この健康診断で、Misskeyが過剰に太ることを防止しています。

ノート

メモリ使用量の削減は"塵も積もれば山となる"で、わずかな削減でも積み重ねれば大きな削減になります。1MBしか減らない改修でも10回行えば10MB減ります。

さらに、Misskeyの場合一つのサーバー内でもクラスタリング運用されることがあるため、プロセスの数だけ削減効果が増幅されます。

Misskeyプロセスを10個立ち上げて運用していたとしたらトータルで100MBもメモリ使用量が減ることになります。

今年もこの健康診断を行った結果、バックエンドのメモリ使用量を削減する改修をいくつか行えました。

重要性の低いパッケージ依存の削除

cli-highlight

コンソールに出力されるデバッグログのSQLを色付けするために当該パッケージを使用していましたが、SQL以外の言語のパーサー実装なども含まれておりメモリを消費していました。

SQLに色がついていることの重要性が低いことを鑑み、色付け自体を廃止して当該パッケージの依存を削除しました。

jsdomhappy-dommicroformats-parser, parse5

HTML文字列をパースするために当該パッケージを使用していました。

ただ当該パッケージは、内部的にほかの大きめのライブラリに複数依存していたほか、ブラウザのAPIをシミュレートするための実装などがフルで含まれていて、メモリを多く消費していました。

MisskeyではHTMLがパースさえできればよく、そこまでの機能を必要としていませんでした。

そこで、より軽量なライブラリ(node-html-parser)に乗り換えたり、ライブラリを使うまでもない簡単な処理は使わない実装に書き換えるなどして、メモリ使用量を削減しました。

node-fetch

ブラウザのfetchと同様の関数を使うためにインストールされていましたが、Node.js v18から標準でfetchが実装されたので不要となっていました。

js-yaml

ランタイムでYAMLで書かれた設定ファイルをパースするために使用していました。

Misskeyの場合、ランタイムでYAMLをパースする必要はないので、起動前に設定ファイルをJSONに予めコンパイルしておくようにしました。

ただ、コンパイル処理が必要になるコストに見合っていないのではという意見もあるので、方針検討中です。

バックエンドのminify

一般にコードのminifyはフロントエンドの領域で行われることが多いですが、実はバックエンドでもminifyのメリットがあります。

Node.js(V8)はメモリにコメントなどすべて含むソースコード自体をそのまま保持するため、minifyするとメモリ使用量を削減できます。

特に依存関係を含むコードベースが大きいとminifyの効果は大きくなります。

バックエンドのコードをminifyするようにしたことで、1プロセスにつき、平均30MBのメモリを削減できました。

さらに、今後node_modulesを含めたminifyも計画していて、実現できればより省メモリになります。

メモリ使用量比較Action

PRごとに、そのPR前後でバックエンドのメモリ使用量がどう変化するかを検証するGitHub Actionを実装しました。

GC(ガベージコレクション)前、GC後、HTTPリクエスト受信後の各タイミングで計測している

省メモリ化改修の実際の効果を確認できるほか、意図せずメモリ使用量が大きく増加するような実装ミスの検知に役立ちます。

まとめ

今回は最近行ったバックエンドのメモリ使用量の削減の取り組みを紹介しました。

メモリ使用量の削減は、依存パッケージを減らすことで達成したものも多いです。

また、外部依存は少ないほうがサプライチェーン攻撃のリスクを減らせる等のメリットもあります。

むやみに依存を発生させるのではなく、ライブラリが本当に必要な処理か・外部依存を発生させるメリットが見合っているかどうかを吟味すべきでしょう。

そして、省メモリ化にバックエンドのminifyは有効ですが、まだあまり一般的にはなってない気がします。ひと手間かかりますが、パフォーマンスを追求したいなら検討の価値があると思います。

おすすめ記事

記事・ニュース一覧