Nextcloudを高速化するためにやったこと
Nextcloudは、写真や動画を大量に扱い始めると「重い」と感じることがあります。
私が保守しているNextcloud環境でも、重いと感じることがありました。
この記事では、そのために行ったストレージ、Web、PHP、DB、アプリの調整をまとめます。
構成
- Nextcloud 34.0.4
- Nginx + PHP 8.3-FPM + MariaDB 10.6 + Redis
- CPU 16コア、メモリ32 GB
- OS、MariaDB、プレビューキャッシュ: 240 GB SATA SSD
- Nextcloudの実データ: 8 TB HDD 2台のRAID1
以下は、この構成と用途に合わせて実際に使っている設定です。
メモリ容量、接続数、ストレージ構成、扱うファイルの種類が違えば最適な値も変わります。設定値だけをそのままコピーすることはおすすめしません。
プレビューだけをSSDへ逃がす
Nextcloudの実データは、HDD 2台のRAID1に保存しています。
HDDは大容量の写真や動画を置くには向いていますが、サムネイルのような小さなファイルを大量に読む用途は得意ではありません。
Nextcloudはプレビューをappdata以下に保存します。この環境ではプレビューが約36万ファイルあり、写真一覧を開くたびにHDDへのランダムアクセスが集中していました。
そこで、appdata全体ではなく、プレビュー用ディレクトリだけをSSDへbind mountしています。
HDD RAID1
└─ Nextcloudの実データ
SSD
└─ appdata/.../preview
└─ サムネイル・プレビューキャッシュ
これにより、大容量ファイルはHDDに置いたまま、一覧表示で頻繁に読む小さなプレビューだけをSSDから返せます。
プレビューは必要なら再生成できるキャッシュなので、限られたSSD容量を使う対象としても相性がよいです。
一方で、SSDの空き容量と、bind mountが起動時に正常に張られていることは監視対象になります。
また、Preview Generatorは導入していません。
あまりアクセスされない画像まで事前生成すると、SSD容量をプレビューが消費してしまうためです。プレビューの保存期間も90日に設定し、増え続けないようにしています。
'preview_expiration_days' => 90,
この設定は、写真の閲覧頻度とSSDの空き容量を見ながら決めるのがよさそうです。
HDDはCMR方式を選ぶ
RAIDを組む、または頻繁な更新を想定するなら、HDDがCMRかどうかは確認しておきたいポイントです。
SMRはアーカイブ用途では選択肢になりますが、長時間の書き込みやRAID再同期で速度が大きく落ち込むことがあります。
Nextcloudのように、ファイル更新、同期、バックアップなどのI/Oが重なる用途では、CMRを選ぶ安心感が大きいです。
HDDのマウントオプションとread-ahead
HDDのデータ領域はnoatimeでマウントしています。
この環境ではatimeを利用する運用をしていないため、画像や動画を読むたびにアクセス時刻を書き戻す必要がありません。
また、動画のような連続読み出しに合わせ、Linuxのread-aheadも、この環境で設定されていた128 KiBから8 MiBへ拡大しました。
800 MBの同一ファイルをOSキャッシュなしで読んだ測定では、Nextcloudデータ用RAID1の読み出し速度は187 MB/sから229 MB/sへ、約22%向上しました。
これはランダムアクセス中心の用途には向きません。
先読みしすぎると、実際には使わないデータまで読み込み、ページキャッシュを圧迫する可能性があります。
このサーバーでは画像・動画の連続読み出しが多く、同時アクセスも極端に多くないため採用しています。
メモリをDBへ寄せすぎない
MariaDBにはInnoDBバッファープールを8 GiB割り当てています。
innodb_buffer_pool_size = 8G
innodb_log_file_size = 2G
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
DB全体は約4.2 GiBなので、8 GiBあればワーキングセットを十分にメモリへ載せられます。
ここでMariaDBへメモリを過剰に割り当てると、Nextcloudのファイル配信やNFSで重要になるLinuxのページキャッシュが減ってしまいます。
MariaDBだけを速くするのではなく、サーバー全体でメモリをどう使うかを考えて、この値にしています。
また、MariaDBのクエリキャッシュは明示的に無効化しています。
query_cache_type = 0
query_cache_size = 0
クエリキャッシュは一見便利ですが、更新の多いワークロードではキャッシュの無効化処理も発生します。
oc_filecacheへの更新が多いNextcloudではメリットが小さいと判断し、使用していません。
OSのページキャッシュを守る
このサーバーはNextcloudとNFSを兼ねているため、Linuxのページキャッシュが性能に直結します。
vm.swappiness = 10
vm.dirty_ratio = 10
vm.dirty_background_ratio = 3
vm.vfs_cache_pressure = 50
スワップを積極的に使わないようにし、inodeやdentryなどのキャッシュも保持しやすくしています。
また、dirty pageを大量にためてから一気にHDD RAID1へ書き戻すより、比較的早い段階からバックグラウンドで書き戻すようにしています。
これは32 GB程度のメモリがあり、ファイル配信が多いこのサーバー向けの設定です。
メモリが少ない環境へそのままコピーする設定ではありません。
大容量アップロード時の一時書き込みを減らす
Nginxは512 GiB、PHP-FPM側は100 GiBまでのアップロードを許可しています。
client_max_body_size 512G;
fastcgi_request_buffering off;
この環境ではfastcgi_request_buffering offにしています。
これにより、Nginxがクライアントからリクエストボディ全体を受信してからPHP-FPMへ渡すのではなく、受信しながらPHP-FPMへ流せます。
動画のような大きなファイルでは、Nginx側で発生する一時ファイルへの書き込みと、PHP-FPMへ渡し始めるまでの待ち時間を減らせます。
一方で、遅いクライアントとの接続をPHP-FPM側でも長時間保持することになるため、同時接続数が多い環境では逆効果になる可能性があります。
また、Nextcloud公式のNginx設定例ではfastcgi_request_buffering onになっているため、クライアントのアップロード方式によっては注意が必要です。
私の環境では、実際に利用しているクライアントで動作を確認したうえで、offのほうが高速だったため採用しています。
PHPは同時処理と再利用を調整する
PHP-FPMは次の設定で動かしています。
pm = dynamic
pm.max_children = 60
pm.start_servers = 16
pm.min_spare_servers = 12
pm.max_spare_servers = 32
pm.max_requests = 500
アクセス増加時にプロセス生成待ちが起きにくいよう、一定数のワーカーを待機させています。
さらに、500リクエストごとに子プロセスを入れ替え、長期運用時のメモリ肥大化を抑えます。
確認時点では、PHP-FPMは最大プロセス数に一度も到達していませんでした。
現状ではPHPの同時処理数を増やすより、ストレージやバックグラウンド処理を整えるほうが重要そうです。
OPcacheも標準値から拡張しています。
opcache.memory_consumption = 512
opcache.interned_strings_buffer = 64
opcache.max_accelerated_files = 32000
opcache.revalidate_freq = 60
NextcloudはPHPファイル数が多いため、毎リクエストでPHPファイルをコンパイルし直さないことは重要です。
ただし、OPcacheの容量は大きければよいわけではありません。
Nextcloudのセットアップチェックやopcache_get_status()を確認し、キャッシュが枯渇していないかを見ながら調整しています。
PHPへ来る前にNginxで終わらせる
Nginxでは静的アセットを長くキャッシュし、gzip圧縮も有効にしています。
open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
CSSやJavaScriptなど多数の静的ファイルを扱う環境では、ファイル本体の読み込みだけでなく、ファイルの存在確認やメタデータ取得も積み重なります。
そこでNginxにもファイル情報をキャッシュさせ、不要なstat()などを減らしています。
静的ファイルには約6か月のブラウザキャッシュも設定しています。
初回アクセスには効きませんが、再訪時のリクエスト数と転送量を減らせます。
アプリはできるだけ入れない
標準同梱かどうかに関わらず、アプリは必要なものだけを有効にするようにしています。
Activityについても注意が必要です。デフォルトでは1年分のアクティビティが保持されるため、イベントが頻繁に発生する環境では、oc_activityのデータ量がかなり大きくなることがあります。
私の環境では数千万件のレコードが蓄積していました。Activityを利用していない場合は無効化し、利用する場合でも保持期間を用途に合わせて調整しておくとよさそうです。
高速化を目的としたアプリについても、導入前に動作を確認するようにしています。
例えばPreview Generatorは、プレビュー表示までの待ち時間を減らせる一方で、事前生成によるCPU負荷やストレージ消費が増えます。
「高速化アプリを入れれば軽くなる」と単純には考えず、自分のボトルネックに合っているかを見る必要があります。
遅くなったときに原因を追えるようにする
最適化は、設定値を増やして終わりではありません。
このサーバーでは、Nextcloud、MariaDB、Redis、Nginx、PHP-FPMをPrometheus exporterで監視しています。
PHP-FPMでは30秒を超えた処理、MariaDBでは1秒を超えたクエリをログへ記録しています。
これにより、「なんとなく遅い」をCPU、メモリ、HDD、PHP-FPM、SQL、バックグラウンドジョブなどへ分解して考えられます。
設定を変えたあとに速くなったのか、別の場所へ負荷を移しただけなのかを判断するためにも、監視は重要です。
Nextcloudに全部やらせない
ここまで調整しても、用途によってはNextcloud自体の処理がボトルネックになります。
その場合は、すべてをNextcloudへ集約せず、用途ごとに専用のソフトウェアへ分けることも有効です。
私のサーバーでは、書籍をKavita、画像をImmichへ移動させています。
Nextcloudでは、ディレクトリのファイル一覧を取得するときに、各ファイルの情報を含んだXMLが返されます。
1つのディレクトリに大量のファイルやメタデータがあると、このファイル一覧XMLが数MBに達することもあり、サーバー側でのXML生成や転送、フロントエンド側でのXML解析に時間がかかるようになります。
そのため、1つのディレクトリに大量のファイルを置かない、不要なメタデータを減らす(つまり不要なアプリを減らす)、Webクライアントを使わない(😅)といった工夫もしています。
設定を詰めるだけでなく、Nextcloudに何を担当させるかを見直すことも、結果的には大きな高速化になります。
まとめ
Nextcloudの高速化で重要だったのは、単純にリソースを増やすことではなく、どこが遅いのかを切り分けて、その処理に合った場所へ負荷を逃がすことでした。
プレビューはSSDへ、実データはHDDへ。DBへメモリを寄せすぎず、OSのキャッシュも活かす。不要な処理やアプリは減らし、Nextcloudが苦手な用途は専用ソフトウェアへ分ける。
結局のところ、「Nextcloudが遅い」とひとまとめにせず、ボトルネックを1つずつ潰していくのが一番効きました。
GitHubで編集を提案