Mac下Docker不同文件挂载方式的性能差异
众所周知 Mac 跑 Docker是有一层 VM 的,最早 Mac 的 Docker Desktop 的共享文件系统非常烂,基于 osxfs,执行一个 npm install ,Linux 下 20 秒,Mac 下可能要几分钟。而如今情况据说已经大不相同,基于 VirtioFS,性能已有大幅提升。
绑定目录(Bind Mount) 和 Docker Volume(命名卷) 这两种挂载方式是 Docker 两种完全不同的存储机制,其中在 Mac 下,bind mount 会经过 VirtioFS,而卷挂载时数据则属于 VM 内部。
测试方案
分别用 绑定路径(bind mount) 的方式与 卷命名(volume) 的方式创建 Postgres18 的 docker 容器,再加入 Mac 下 brew 安装的原生 Postgres18 作为对比。
建表:
CREATE TABLE test (
id BIGSERIAL PRIMARY KEY,
value TEXT
);执行测试,在 psql 下:
\timing然后执行:
INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,1000000);最后查看统计数据即可。
Docker Bind Mount
100 万数据插入:
postgres=# \timing
启用计时功能.
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,1000000);
INSERT 0 1000000
时间:3804.413 ms (00:03.804)
postgres=#在此基础上 1000 万数据插入:
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,10000000);
INSERT 0 10000000
时间:48126.862 ms (00:48.127)
postgres=#在此基础上 10 次 10 万数据插入:
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:396.869 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:412.232 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:382.937 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:428.471 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:426.206 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:439.906 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:416.487 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:443.733 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:453.728 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:443.960 ms
postgres=#Docker Volume
100 万数据插入:
postgres=# \timing
启用计时功能.
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,1000000);
INSERT 0 1000000
时间:2169.361 ms (00:02.169)
postgres=#在此基础上 1000 万数据插入:
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,10000000);
INSERT 0 10000000
时间:21762.814 ms (00:21.763)
postgres=#在此基础上 10 次 10 万数据插入:
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:213.439 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:233.843 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:217.458 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:219.334 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:212.566 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:216.081 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:211.386 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:206.213 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:202.603 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:211.573 ms
postgres=#Mac 原生
100 万数据插入:
postgres=# \timing
启用计时功能.
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,1000000);
INSERT 0 1000000
时间:2850.996 ms (00:02.851)
postgres=#在此基础上 1000 万数据插入:
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,10000000);
INSERT 0 10000000
时间:28794.815 ms (00:28.795)
postgres=#在此基础上 10 次 10 万数据插入:
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:302.238 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:272.471 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:264.486 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:284.120 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:287.495 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:295.402 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:272.715 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:277.457 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:298.809 ms
postgres=# INSERT INTO test(value)
SELECT md5(random()::text)
FROM generate_series(1,100000);
INSERT 0 100000
时间:290.244 ms
postgres=#数据对比汇总
以下耗时单位均为 ms,越小越好。
100 万数据单次插入
| 方案 | 耗时 | 相对 Mac 原生用时 |
|---|---|---|
| Docker Bind Mount | 3804.413 | 133.4% |
| Docker Volume | 2169.361 | 76.1% |
| Mac 原生 | 2850.996 | 100% |
继续 1000 万数据单次插入
| 方案 | 耗时 | 相对 Mac 原生用时 |
|---|---|---|
| Docker Bind Mount | 48126.862 | 167.1% |
| Docker Volume | 21762.814 | 75.6% |
| Mac 原生 | 28794.815 | 100% |
继续 10 次 10 万数据插入
| 方案 | 平均耗时 | 相对 Mac 原生平均用时 | 最快 | 最慢 |
|---|---|---|---|---|
| Docker Bind Mount | 424.453 | 149.2% | 382.937 | 453.728 |
| Docker Volume | 214.450 | 75.4% | 202.603 | 233.843 |
| Mac 原生 | 284.544 | 100% | 264.486 | 302.238 |
从这组测试结果看,Docker Volume 在 Mac 下三组数据里都是最快;Mac 原生 基本稳定排在第二;Bind Mount 始终最慢,而且在大批量写入时差距会进一步拉大。
如果容器里跑的是数据库、索引、队列这类高频写入服务,数据目录更适合放在 Docker Volume;Bind Mount 更适合代码、配置、静态资源这类需要和宿主机实时编辑同步的内容。
另外其实 Mac 和 Windows 的 Docker 场景非常类似:
| 平台 | 快 | 慢 |
|---|---|---|
| macOS + Docker Desktop 数据挂载方式 | Docker Volume | Bind Mount(共享 macOS 目录) |
| Windows + WSL2 数据目录位置 | WSL2 内部文件系统 | Windows C:\ 等目录 |
但话又说回来,Mac 作为开发机器,自然是怎么方便怎么来,哪怕是最慢的映射方式,绝大部分情况还没有到影响开发的地步,甚至很多时候作为开发端是无感的。