こんにちは。ここのえです。
最近久々におうちサーバ環境を整えているのですが、その一環としてNextcloudを導入しました。NextcloudはownCloud派生のオープンソースオンラインストレージ・ファイル共有アプリケーションで、簡単に言うとオンプレミスでクラウドが作れるというヤツです。

メインマシンにはSSDを合計6TBぐらい積んでいるのですが、アホみたいなサイズのクソデカ音源データとかストレージバカ食いの極めて愉快なゲームとかに加えて、Unity・UEのプロジェクトファイルがとんでもない数あるため全体的にパンクしつつある状態で、お立ち台でHDDを繋いではデータを突っ込んだり出したりしていました。まあこれが面倒くさいんですよね。
おうちサーバのホストOSにはProxmoxを使用しているので、debianベースのLXCにNextcloudを突っ込んで、そこで運用する形に変更することにしました。しかし構築したのは良いものの、20~30MB/s まあまあ困ったレベルで速度が出ません。
小さいデータのやり取りならいいのですが、時々動画データをストレージとワークステーションで往復させることがあるため、どうしても気になってしまいます。という事で色々チューニングしてみることにしました。
ネットワーク速度の確認
そもそも論として、サーバ間との通信経路が遅かったら何の意味もありません。 iperf3
で転送速度を測っておきましょう。
# サーバ側
iperf3 -s
# クライアント側
iperf3 -c <server_ip>
# .....
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 1.09 GBytes 933 Mbits/sec 1 sender
[ 5] 0.00-10.01 sec 1.08 GBytes 925 Mbits/sec receiver
十二分すぎるぐらいにスピードが出ています。ルータ等の途中経路にはボトルネックはなさそうです。まあここに問題があったら、もはや記事にできてないんですけどね…
PHP-FPMに変える
そもそもNextcloudがサポートしているWebサーバは apache2
だけで、 nginx
は公式サポートではない旨が記載されています。公式ドキュメントのインストール手順通りにやると普通のApacheモジュール版のPHPを使うことになるので、まずここがボトルネックになります。Apache2のPHP周りの処理で php-fpm
を使用するように変更し、そのチューニングを行います。
必要なパッケージをインストールして有効化。
apt install php-fpm
a2enmod proxy_fcgi setenvif
a2enconf php8.2-fpm
apache2のnextcloudのコンフィグに以下を書き込みます。うちの環境の場合は /etc/apache2/sites-available/nextcloud.conf
です。SetHandler
で指定した .sock
のパスは任意で大丈夫ですが、忘れないようにメモしておいてください。後述のphp-fpmのコンフィグと一致させる必要があります。
<VirtualHost *:443>
<!-- もろもろの設定 -->
<Directory /var/www/nextcloud/>
<!-- いい感じの設定 -->
<FilesMatch \.(php|phar)$>
SetHandler "proxy:unix:/run/php/nextcloud.sock|fcgi://localhost"
</FilesMatch>
</Directory>
</VirtualHost>
次に php-fpm のコンフィグを作成します。これは新規ファイルなのでファイル名は何でも大丈夫です。 /etc/php/<php_version>/fpm/pool.d/
以下に配置します。
[nextcloud]
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen = /run/php/nextcloud.sock
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 64
pm.start_servers = 12
pm.min_spare_servers = 6
pm.max_spare_servers = 18
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/sessions
php_value[max_execution_time] = 3600
php_value[memory_limit] = 1G
php_value[post_max_size] = 32G
php_value[upload_max_filesize] = 32G
php_value[max_input_time] = 3600
php_value[max_input_vars] = 2000
php_value[date.timezone] = Asia/Tokyo
php_value[opcache.memory_consumption] = 512
php_value[opcache.interned_strings_buffer] = 32
php_value[opcache.max_accelerated_files] = 10000
php_value[opcache.jit] = 1255
php_value[opcache.jit_buffer_size] = 256M
php-fpmのパラメータは公式ドキュメントを読めば分かります。

……と言ってしまえば簡単なのですが、まあさすがにそれも味気ないので少し解説しておきます。
プロセスマネージャに関する設定
まず重要なのが pm.
から始まるプロセスマネージャに関する設定項目です。プロセス数が少ないと当然処理が詰まるのですが、増やしすぎても結局ディスクIOがボトルネックになるのであまり意味がなかったりします。
; dynamicにすると必要に応じて動的に子プロセス数が調整される
pm = dynamic
; プロセスの最大数
pm.max_children = 64
; --- 以下、dynamic指定時に必要なパラメータ ---
; 起動時に作成されるプロセス数
pm.start_servers = 12
; アイドル状態のサーバプロセス最小値
pm.min_spare_servers = 6
; アイドル状態のサーバプロセス最大値
pm.max_spare_servers = 18
検証時は static
にしてプロセス数を固定させ、16
と 32
で比較しましたが、最初の数秒ほどは 5~10MB/s ほど変わるものの、どちらもすぐに 30MB/s 程度に落ち着いてしまいました。
OPcache関係
OPcacheも使っておきましょう。こちらもドキュメントに記載があるので確認しておくことをお勧めします。

またPHP8.0~はJITが使えるので、それについても有効化しています。OPcacheに関する知見があんまりないので大分大味な設定になってしまっています……。たぶん他の詳しい人の記事を読んだ方がいいと思います。
php_value[opcache.memory_consumption] = 512
php_value[opcache.interned_strings_buffer] = 32
php_value[opcache.max_accelerated_files] = 10000
php_value[opcache.jit] = 1255
php_value[opcache.jit_buffer_size] = 256M

APCu, Redisの導入
Nextcloudではmemcache関係の設定が推奨されており、管理画面でも「これがOffだと遅くなるよ~」とwarningを出してきます。公式ドキュメントのシングルサーバでの推奨構成通り、APCuとRedisを使ってキャッシュを有効化します。
必要なパッケージをインストール。
apt install redis-server php-redis php-apcu
/var/www/nextcloud/config/config.php
を修正します。
<?php
$CONFIG = array(
// もろもろの設定
'memcache.local' => '\OC\Memcache\APCu',
'memcache.locking' => '\OC\Memcache\Redis',
'memcache.distributed' => '\OC\Memcache\Redis',
'redis' => array(
'host' => 'localhost',
'port' => 6379,
),
);
デフォルトのままだと php-apcu
のメモリサイズが 32MB になっているので、これだとあまり効果がありません。再度 /etc/php/8.2/fpm/pool.d/nextcloud.conf
を修正します。
; 追加
php_value[apc.shm_size] = 256M
早くはなったけど……まだ微妙な感じです。

真剣に犯人捜しする
php-fpmに変更したことによって確かに早くはなったのですが、まだ到底納得できない速度です。そもそも一番気になるのが、最初は早いのに数秒立つと遅くなり、その後 40MB/s 前後の表示から動いたり止まったりしながら動作しているような感じで、何かで処理が詰まっているような印象です。
htop
で見てもCPU使用率/メモリにかなり余裕があり、こうなるとディスクIOが疑わしくなってきます。ZFS + lz4圧縮を設定しているため、そもそも遅い可能性があります。
iostat
で確認してみます。
iostat -xd 1
# ...
%util
sdb ... 96.60
sdc ... 97.00
Nextcloudのバックエンドにはミラーリングした /dev/sdb
と /dev/sdc
をホストOSからバインドマウントしており、そこの使用率が案の定エライことになってます。HDDなのでしょうがないのですが、それにしても結構厳しい。
非特権コンテナのrootでは iotop
を使えないので、ホストOSに戻ってプロセスの状況を見てみます。
iotop -d 1 -o
# ...
TID PRIO USER DISK READ DISK WRITE> COMMAND
63246 be/4 nextclou 91.46 M/s 90.23 M/s php-fpm: pool nextcloud
DISK WRITEの値を見ていると、30 ~ 120M/sで常に大きく変動している様子が伺えます。書き込み速度が原因でしょうか? dd
を試してみると…
# コンテナ内で4GBのデータ書き込みテスト
dd if=/dev/zero of=/mnt/nextcloud/data/test bs=1M count=4000 conv=fdatasync
4000+0 records in
4000+0 records out
4194304000 bytes (4.2 GB, 3.9 GiB) copied, 1.95236 s, 2.1 GB/s
おや?思ったより遅くない…… lz4が悪さしているとしたら scp
経由での転送で遅くなるか…?
testmovie.mkv 100% 8423MB 109.4MB/s 01:17
と思いきやこれもそんなに遅くない。となるとZFSに問題はない…という事になりそうです。←フラグです
散々悩んだ挙句、postgresqlのログを見るとそこに原因がありました。
2025-04-29 18:52:29.190 JST [4082] nextcloud_user@nextcloud_db ERROR: deadlock detected
2025-04-29 18:52:29.190 JST [4082] nextcloud_user@nextcloud_db HINT: See server log for query details.
2025-04-29 18:52:29.190 JST [4082] nextcloud_user@nextcloud_db CONTEXT: while rechecking updated tuple (27,11) in relation "oc_filecache"
そこでpostgresql周りを1日がかりで片っ端からチューニングしましたが、問題は解決しませんでした。そもそもNextcloudの公式ドキュメントの推奨通りに設定しているのに、このようなデッドロックが発生するのは妙です。
犯人はZFS
原因を探すためにインターネットを右往左往していると、Proxmoxのフォーラムに怪しい投稿を発見します。

Downside of cause would be that all workloads doing reads/writes smaller than 16K (like PostgreSQL) will be terrible, as ZFS could only work with 16K blocks, so any 4K or 8K IO would cause 16K reads/writes.
オイオイオイ
zfsのプロパティは zfs get
コマンドで取得できるので、チェックしてみます。
zfs get recordsize,volblocksize,compression,dedup,sync,atime,sharenfs,sharesmb -r cloud
NAME PROPERTY VALUE SOURCE
cloud recordsize 128K default
cloud volblocksize - -
cloud compression lz4 local
cloud dedup off default
cloud sync standard default
cloud atime on default
cloud sharenfs off default
cloud sharesmb off default
cloud/nextcloud recordsize 128K default
cloud/nextcloud volblocksize - -
cloud/nextcloud compression lz4 inherited from cloud
cloud/nextcloud dedup off default
cloud/nextcloud sync standard default
cloud/nextcloud atime on default
cloud/nextcloud sharenfs off default
cloud/nextcloud sharesmb off default
recordsize = 128K
16Kどころの騒ぎではないです。WebUIからZFSをMirroring, ashift=12で設定していたのですが、恐るべきことに128Kとかいうとんでもない数字になっています。横転しました。
postgresql v15のドキュメントを読んでみると、データのブロックサイズ BLCKSZ
は 8192 byteであると書かれています。
すなわちPostgreSQLは8Kを一つの単位として書き込みを行おうとしますが、そのたびに余分に+120Kの書き込みを行ってしまいます。読み取りも同様です。これを俗に padding overhead とか言うみたいです。そりゃ遅くなるわけですね。試しにZFSでない lvm のドライブにpostgresqlのデータを移して動かしたところ、デッドロックは発生しなくなりました。
これについてはPerforceの公式ブログでも触れられており、ZFSのRAIDZで運用する場合はrecordsizeを設定することが推奨されていました。ZFSでデータベースを使用する際の一般的な推奨事項のようです。

解決策:ZFSのデータストア作成時にrecordsizeを指定する
解決策はZFSのデータストア作成時にrecordsizeを指定することです。zpoolはWebUIから操作する関係で変更することは難しいですが、プールとデータストアのrecordsizeは一致している必要はないので、これで解決できます。Nextcloudに実際に保存するデータはサイズがかなり大きいので、こちらはrecordsizeを小さくするとシーケンシャルI/Oのスループットが低下してしまい、またこれも問題になります。DB用のデータストアとデータ用のデータストアを別で切って対応することにします。
ホストOSで以下の作業をします。
# DB用はrecordsizeを8Kにする
zfs create -o recordsize=8K cloud/nextcloud_db
# データ用はデフォ設定を使う
zfs create cloud/nextcloud_data
# 非特権コンテナ使用時、ホストOSからコンテナに合わせて
# uid/gidを設定しないと nobody:nogroup になりアクセスできなくなります。
# デフォ設定の場合以下のUID/GIDになります。
# ホストOS上のUID/GID = 100000 + コンテナ上のUID/GID
# 以下の設定はdebian12での
# postgres:postgres = 103:112
# www-data:www-data = 33:33
# の場合です
# chown 100103:100112 /cloud/nextcloud_db
# chown 100033:100033 /cloud/nextcloud_data
# コンテナにマウント
pct set <コンテナのvmid> -mp0 /cloud/nextcloud_db,mp=/mnt/nextcloud_db
pct set <コンテナのvmid> -mp1 /cloud/nextcloud_data,mp=/mnt/nextcloud_data
# コンテナを再起動して適用
pct stop <コンテナのvmid> && pct start <コンテナのvmid>
ディレクトリを変更することになるので、 /var/www/nextcloud/config/config.php
のディレクトリ設定や、postgresの /etc/postgresql/15/main/postgresql.conf
の設定も変更する必要があるためご注意を。
これにより速度が +30MB/s ぐらい上昇。劇的な改善です。転送中にデータが止まることもなくなりました!

まとめ
今回は Proxmox + Nextcloud で問題が起きていましたが、Proxmoxに限らずZFSを使う場合は同様の問題が発生する可能性があるので参考にして頂ければ幸いです。ちなみにmariadbのブロックサイズも8KBのようなので、同様の解決ができそうです。
まさかデータベースのチューニングにファイルシステムの問題が関わってくると夢にも思っていなかったので、原因に気づくまで無駄にpostgresqlのコンフィグをいじくって時間を無駄にしてしまいました……。
注意喚起のためにBruno Marsに 128K Magic とか曲作って “Check your recordsize” とか歌詞に入れてもらいましょう。無理あるって?それはそう。
コメント