取り扱う問題
NFS上に開発リポジトリがあり、それをwebpackの実行ホストと異なるホスト(DeveloperHost)からファイルを編集する際、webpackがファイルの変更を検知するのに数秒~数十秒掛かるという問題があった。
この問題を解決したので色々書き残しておく。
TL;DR
- webpackはwatchpackを使ってファイルの変更を監視している
- NFSの場合はwatchpackのオプションをpollingにしないとファイル変更を検知できない
- pollingの内部は
fs.stat()
を使ってファイル属性を定期的に取得して差分を見ている - NFSではファイル属性がデフォルトで3秒~60秒キャッシュされるので、mount時のオプションで調整する
- ※webpackとファイル変更が同じホスト上で行われている場合、ファイル属性はすぐに更新されるので問題ない
Node.jsの監視システム
Node.jsでファイル監視システムにはfs.watch()
とfs.watchFile()
の2つがある。fs.watch()
はOSに近い機能を用いて監視しており、fs.watchFile()
はfs.stat()
を定期的に呼び出してファイルの属性値を監視している。
watchFile()
は現在では非推奨とされているが、NFSの場合はwatch()
で用いられるネイティブな機能(Linuxの場合はinotify)が使えないため、原始的にファイルの属性値をポーリングするwatchFile()
が用いられる。なぜ使えないかと言うと、例えばLinuxの場合のinotifyはVFS層で実装されており、別のマシンで実行されたファイルシステムの操作を認識できないためである。
これに関しては「Node.js の fs.watch() と fs.watchFile() の違い - てっく煮ブログ」でも解説されている。
watchpack v1.6では内部的にchokidarというNode.jsのファイル監視ラッパーを使用しており、このchokidarはオプションがpollingの場合、Node.jsのfs.watchFile()
をinterval毎に呼び出している。(ソースコードはここ)
watchpackのv2.0.0-beta.2ではchokidarを用いずwatchpack側で独自に実装しているが、実際にはchokidarと同じくfs.watchFile()
でpollingしているだけである。
NFSのマウントオプション
NFSの場合、ファイル属性はLinuxカーネル側でデフォルトで3秒~60秒ほどキャッシュされている。linux/nfs_fs.h at v4.20 · torvalds/linux · GitHubにもある通り、NFS_DEF_ACREGMINに3秒、NFS_DEF_ACREGMAXに60秒が設定されている。
これはNFSのマウントオプションで指定できるacregminやacregmaxと同じであるため、mount時にmount -o acregmin=3,acregmax=20
のように指定してやればいい。
acregmin=n
一般のファイル (regular file) の属性 (attribute) がキャッシュされる 最小の時間を秒単位で指定する。 この時間内では、サーバーへの新たな情報の問い合わせは行われない。 デフォルトは 3 秒。acregmax=n
一般のファイルの属性がキャッシュされる最大の時間を秒単位で指定する。 この時間を越えると、必ずサーバーへ新たな情報の問い合わせが行われる。 デフォルトは 60 秒。
acregminとacregmaxの適正値
これらの値を0にするのは、NFSやマシン上のCPU・ネットワーク負荷が急増し、マシンの動作を非常に遅くしてしまう。自分の場合はacregmin=3,acregmax=3とした所、負荷的にも監視の速度的にも満足のいく性能になった。
ちなみにディレクトリ属性のキャッシュ時間を決めるacdirmin・acdirmaxなどもあるが、自分はいまのところデフォルト値で問題なく動いている。(プロジェクトによってはこちらも調整する必要があるかも知れない。)