折腾家里她的几个晚上
0. 起因
起因特别无聊。
就是无聊了,想整点活,优化一下Nextcloud吧。
几个晚上,前后改了 20 项配置。
然后发现真正管用的就一项。
1. 差点就去抄清单了
最开始的想法很朴素:找一份“Nextcloud 性能优化最佳实践”,一条条照着改完,收工。
临抄前想了想,还是先摸排下现状吧。
一测磁盘 IO:0.07 MB/s。
🌿。提前准备好的一整套虚拟磁盘调优方案就完犊子吧。
2. 然后挖出来一个 69 毫秒
接着往下挖,就挖到 innodb_flush_log_at_trx_commit 了。
这参数管的是事务提交时多久把日志刷到盘上。= 1 是每提交一笔刷一次,= 2 是每秒刷一次,平时只写页缓存。= 1 就是每记一笔账跑一趟银行柜台盖章,= 2 是先记本子上、下班前统一去盖。
差多少,看柜台多远。
我这台的柜台——虚拟机嘛,底下那块盘是宿主模拟出来的——单次 fsync 实测 69 毫秒。
我压测跑 1000 次单行事务,花了 69.156 秒,TPS 14。十四。我第一反应是脚本写错了,又跑了一遍,还是 14。
它不是算不过来。它是每笔都在排队等那 69 毫秒。
改成 2,同样的脚本:
| 改前 | 改后 | |
|---|---|---|
| 1000 次单行事务 | 69.156 s | 0.842 s |
| TPS | 14 | 1188 |
🌿,🐮🍺。
同步、上传、照片索引,全都跟着快了。代价是有的:整机断电或宿主崩了,最多丢最后 1 秒已提交的事务。kill 进程、正常关机、restart 都不丢。官方 my.cnf 示例里就这么写的,单机没做复制本来就这个取舍,我 sync_binlog 本来就是 0。
家用网盘,认了。存交易流水的话我不会碰它。
不过这个 82 倍是沾了虚拟机的光——物理 SSD 上同样的改动,可能也就两三倍。
3. 剩下的活儿,基本是打扫卫生
性能收益约等于零,但满足强迫症。
- 日志:2.8G → 24M。顺手给 journald 加了 500M 封顶,之前它压根没设上限,随便长。用的
truncate不是rm——rm掉文件,服务手里那个句柄还在往已经消失的 inode 里写,空间一 KB 都释放不出来。这个我以前栽过。 - Collabora:1094MB → 354MB。这服务加载过文档就不还内存了,跟仓鼠似的。跑了十天攒下十个空闲进程、1G 内存,而最后一次真用 Office 是四天前。合着在我这儿养了一窝闲进程。
- PHP-FPM:常驻 14 → 6,swap 从 1601MB 掉到 282MB。
4. 几个没兑现的
key_buffer_size128M → 8M。我特意记下了改前的数,等着看内存掉 120MB。掉了 2MB。
全库 269 张表零 MyISAM,那 128M 从头到尾就没真正占过物理页,调小配置值当然释放不出东西来。行吧,就当清了个不合理配置。
opcache.memory_consumption512 → 256,明确不省内存,共享内存是惰性分配的。纯属给配置文件做美容。preview_max_memory2048 → 256。这是天花板不是油门,挡的是“单张预览图允许吃 2G”那条路。全库最大的图才需要 61MB,不会有任何文件因为降配拿不到预览。- FPM 进程数省了大概 100MB。不是我一开始以为的 700MB——为什么,往下看。
5. 数字
5.1 性能
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 数据库写入(1000 次事务) | 69.156 s | 0.842 s(82×) |
| 事务吞吐 | 14 TPS | 1188 TPS |
| 登录页 | 0.159 s | 0.083 s(p95 0.088) |
| 文件列表页 | — | 0.044 s |
| 并发 | — | 约 27 req/s |
| MySQL 慢查询 | 有 | 0 |
| 页面延迟(第四批改前 vs 改后) | 0.082 s | 0.081 s(持平) |
5.2 内存与磁盘
| 指标 | 优化前 | 优化后 |
|---|---|---|
| Swap | 1601 MB | 282 MB |
| Collabora | 1094 MB | 354 MB |
| php-fpm 常驻 worker | 14 | 6–7 |
| 可用内存 | 4273 MB | 4452–4558 MB |
/var/log | 2.8 GB | 24 MB(封顶 500 MB) |
| mariadbd | 942 MB | 942 MB(纹丝不动) |
| 缓冲池命中率 | 66.4%(冷启动) | 99.894% |
登录页那 0.076 秒不能全算配置的功劳,里面有一部分只是缓冲池从冷到热。别把冷启动到热起来的便宜都记我头上。能拍胸脯的就数据库写入那个 82 倍,同一脚本改前改后各跑一次。
三次重启数据库,每次 9 到 12 秒,Nginx 一条 5xx 都没多出来。
6. 改了啥
| 参数 / 动作 | 改前 → 改后 | 位置 |
|---|---|---|
innodb_flush_log_at_trx_commit ⭐ | 1 → 2 | my.cnf |
apc.shm_size | 32M → 256M | php.ini + php-cli.ini |
key_buffer_size | 128M → 8M | my.cnf(两处) |
apc.enable_cli | 0 → 1 | php-cli.ini + php.ini |
pm.max_requests | 缺失 → 500 | php-fpm.conf |
pm.min_spare_servers | 10 → 5 | php-fpm.conf |
pm.start_servers | 10 → 5 | php-fpm.conf |
opcache.memory_consumption | 512 → 256 | php.ini + php-cli.ini |
request_slowlog_timeout | 30 → 5 | php-fpm.conf |
preview_max_memory | 2048 → 256 | NC config.php |
innodb_flush_neighbors | 1 → 0 | my.cnf |
innodb_io_capacity / _max | 默认 → 1000 / 4000 | my.cnf |
innodb_read/write_io_threads | 4/4 → 8/8 | my.cnf |
| 缓冲池预热三件套 | 关 → ON/ON/75 | my.cnf |
| 根分区挂载 | relatime → noatime | /etc/fstab |
| Collabora 瘦身(8 项) | 见注 | coolwsd.xml |
| 清空全部日志(truncate) | 2.8 GB → 24 MB | /var/log 等 |
| journald 限容(6 项) | 无上限 → 500M / 两周 | journald.conf |
| mysqld 重启 ×3 | 每次 9–12 秒 | — |
| php-fpm reload ×4 | 先 -t 校验 | — |
Collabora 那 8 项:空闲子进程 4→1、内存占比、语言只留中英、脏内存上限 3072→200、虚拟内存 0→1024、空闲超时 3600→600、关更新检查、日志级别 trace→warning。
每项改之前都备份了带时间戳的副本,随时能单独回退。
7. 我搞错的几个地方
- 先说最蠢的一个。 我一看 RSS:php-fpm 占 1937MB,好家伙,内存第一大户,比数据库还狠。我袖子都撸起来了,准备大干一场省它 700MB。
错了。
RSS 这个口径会把进程之间共享的内存在每个进程头上都重复算一遍。我这台 OPcache 共享内存 512MB,14 个 worker 每人算了一遍,凭空多出好几千 MB。
换成 PSS 再看,14 个 worker 合计 324MB。
700MB 的收益,缩水到 100MB。 - 然后是差点照抄的。 Redis 配
maxmemory-policy allkeys-lru,一百篇教程一百篇都这么说。我差点就复制粘贴了,后来一想不对:我用 Redis 存事务文件锁,这策略内存一满,连锁一起扔。锁扔了,门还在,那我锁谁呢。
官方文档选 Redis 做锁的前提写得清清楚楚——保证缓存对象在需要期间一直可用。这跟我的用法直接打架。维持noeviction。 - 类似的还有
opcache.enable_cli=1,听着特别有道理,实测慢一倍。CLI 脚本跑一次就退出,缓存根本留不住,每次启动还多一遍校验开销。有道理的事不一定对,今年已经遇到第三回了。
8. 收尾
就这些。下次再攒一堆毛病出来,继续记。