swiftfuse benchmark — chunk mode, 4-node etcd cluster
Environment: Contabo lab, 4 nodes (swift1-4, 8GB RAM), OpenStack Swift
(cluster-wide throughput shared), etcd 3-node cluster (swift1-3), blocks 4 MiB,
leases on, FUSE write-back on, prefetch 8 MiB, read/write workers 8/8,
kernel_ttl = 0(正确性优先:内核每次 LOOKUP 重验;进程内 path/attr/dir 缓存仍在内存服务)。
数值口径(G11):kernel_ttl=0 下 stat 不再是“3ms 路径缓存“ 意义上的纯缓存命中—— 每次 stat 先走内核 LOOKUP(带 TTL=0 的 entry 回复),再由进程内
path_inodes缓存命中后 0 次 etcd 往返。2026-08-09 G8 重跑后本节数值以 raw JSON 为准 (/opt/detect/results/20260809-full/g8/perf.json)。
Benchmark 2.0 framework(R7,2026-08-11)
scripts/benchmark.sh 已从 v1(纯 dd 序列)重写为 Benchmark 2.0 框架(R7 交付物)。
框架只负责运行与记录;R7 先交付框架本身,不跑 live benchmark。
工作负载分类(workload taxonomy,显式 phases)
| workload | 内容 | 默认 |
|---|---|---|
seq-write | 顺序写 BENCH_SIZE_MIB MiB(bs=BENCH_FIO_BS) | 256 MiB / 1M |
seq-read | 顺序读同一文件 | 256 MiB / 1M |
rand-write | BENCH_RAND_WRITES × 4KiB 随机写(M1 object 放大探针) | 100 |
small-file | create + stat + readdir,BENCH_SMALL_FILES 个 4K 文件 | 1000 |
rename | BENCH_RENAME_FILES 次 rename | 500 |
multi-node | 跨 BENCH_NODES 并发读/写(共享 FS,每节点 BENCH_MULTI_MIB) | 128 MiB |
冷 / 温 / 混合分离(per-workload 分别记录)
- cold = 新文件 +
drop_caches(本地 + 各节点 ssh)+ dataset > cache。local_seq_read若必须 seedseq.bin,seed 之后立刻sync+drop_caches。 GeeseFS / JuiceFS 的用户态缓存不受drop_caches影响:公平列用FAIR_REMOUNT=1(写完 persist、umount、清 JuiceFS--cache-dir、再drop_caches、再 remount)。GeeseFS 在本 lab 的 s3api 上要用--single-part 80,默认 multipart 不会落地。 - warm = 数据已入 cache、100% 缓存命中路径(不 drop_caches,重复 pass);
- mixed = 交错负载:IO 用 fio
rwmixwrite/rwmixread(70/30), metadata 用 create+stat+readdir 融合单 pass;multi-node = 一半读一半写。
确定性输出
每次运行写入 $RESULTS_ROOT/bench2.0-<UTC ts>-<commit8>/(结果在本地盘;
被测数据在挂载点 $BENCH_ROOT/$RUN_ID 下,避免结果写入污染被测路径):
meta.json(运行元数据+config env)、summary.json(统一结果表,jq 友好)、
results.tsv(平表)、run.log、workloads/<backend>/<workload>/<phase>.json。
当日可测 instrumentation(框架内实现)
- fio(优先)/ dd(回退)吞吐 + 延迟(clat p50/p99;dd 总时长);
- Swift proxy 请求延迟+count:以 run 窗口为界,
tail -n窗口行数$SWIFT_PROXY_LOG(默认/var/log/swift/proxy-server.log)统计 request count / method / status / elapsed_time(默认 access log 的 float 字段); - etcd txn 延迟:窗口前后 diff
$ETCD_ENDPOINT/metrics的grpc_server_handling_seconds{grpc_method="Txn"}sum/count → 窗口均值。
对比矩阵(env-gated backend sections)
| gate | 默认 | 后端 |
|---|---|---|
COMPARE_SWIFTFUSE=1 | 1 | 本地 mount $BENCH_MOUNT |
COMPARE_RAW_SWIFT=1 | 0 | swift CLI(RAW_SWIFT_*) |
COMPARE_RCLONE=1 | 0 | rclone(RCLONE_REMOTE) |
COMPARE_GEESEFS=1 | 0 | GeeseFS(S3API) mount $GEESEFS_MNT |
COMPARE_JUICEFS=1 | 0 | JuiceFS mount $JUICEFS_MNT |
所有启用 backend 写入同一 results.tsv / summary.json,结尾打印统一对齐表格。
Metrics Contract(R7)
daemon 暴露 GET http://127.0.0.1:<stats_listen>/metrics 以及
/run/swiftfuse/<fs_name>.stats.json(每 2s)。benchmark.sh 在每个
phase 窗口前后各拍一张,写入 phase JSON。字段:
| 字段 | 含义 | 单位 |
|---|---|---|
fuse_op_latency_us | FUSE op 延迟 p50/p99 | µs |
etcd_txn_latency_us | daemon 侧 etcd txn 延迟 p50/p99 | µs |
swift_req_latency_us | daemon 侧 Swift 请求延迟 | µs |
swift_req_count | Swift 请求数(PUT/GET/DELETE/COPY 分桶) | count |
payload_bytes | 每 workload 读写字节 | bytes |
cache_hit_ratio | 内存 block cache 命中率 | ratio |
network_bytes | client↔proxy / proxy↔object 线上字节 | bytes |
Results(2026-08-07 历史值;2026-08-09 重跑值见 G8 报告;2026-08-11 R3 修正见下)
单节点网络拓扑(M3,2026-08-11):
10.0.0.1= loopback(27.9 GB/s)。 单节点 seq read/write 不跨网卡;“千兆内网” 不适用,352-406 MiB/s 物理自洽。
| Workload | Result | Limiting factor |
|---|---|---|
| seq-write 4MiB(2026-08-09 集群安静期) | 202.5 MiB/s(历史 12.4-13.2 MiB/s) | Swift cluster PUT 延迟(并行 block PUT;忙时单块 0.3-3s) |
| seq-read 4MiB / 1MiB QD32 | 352.3 / 406.3 MiB/s | Swift proxy 并发 GET 上限 |
| rand-read 64K QD8 | 129.9 MiB/s(2079 iops) | block 级随机读 |
| 4K 随机写(32MiB 全随机,close-flush) | 超时(8192 块串行 PUT) | 结构性写放大:每 4K 写 1 新块;非相邻写不合并 → close 时数千块 PUT;开 compaction 后可后台合并 |
| 4K 随机写(单次独立 flush) | ~0.9 s/次 | 1 block PUT (~0.4s) + etcd commit |
| 256 MiB warm read (block cache) | 915 MB/s | memory |
| small-file create | ~0.4–1.3 s/file | Swift PUT latency (1 block per file) |
| stat(hot path,路径缓存命中) | 进程内命中 → 0 次 etcd 往返;含内核 LOOKUP 全程为一次 fuse round-trip | 见上文口径 |
| readdir(726 文件,BUG-1 修复后) | 冷 0.75 s / 热 0.079 s;根目录(4 项)0.19 s | etcd 前缀扫描(并行化后) |
| mdtest file create / stat | 9.5 / 103.5 ops/s | Swift PUT vs etcd metadata(两条路径清晰分离) |
| rename / truncate | ~0.2 s / 0.22 s | etcd quorum commit(strong consistency cost) |
| 2-node concurrent read | — | (节点间 ssh 不可达;多节点由 Mac 驱动 multi_node.sh) |
注意:上表 small-file create 为带 4K 内容的文件(1 对象 PUT);空文件 create 是纯 metadata(0 个 Swift 对象,114ms/create,见下文 M2 口径)——两条路径不要混读。
pjdfstest(G1,2026-08-09 全量)
| 分类 | 文件数 |
|---|---|
| PASS(适用用例) | 见 REPORT-2026-08-08.md §1 |
| UNSUPPORTED(symlink/link/mkfifo/mknod → ENOSYS 声明) | 同上 |
| KNOWN DEVIATION(unlink/14 open-deleted 链式 fd) | 1 |
| FAIL | 0 |
Analysis
- Data plane: parallel block PUT/GET confirmed correct (16 blocks in one round-trip); throughput is bounded by the Swift cluster itself (single-block PUT 0.3–3 s during benchmark runs — object servers were busy with auditor passes and replication). Raw Swift API has the same ceiling; swiftfuse adds no serialization.
- Metadata plane: etcd quorum commits are the cost of strong consistency; path-resolution caching keeps hot stat at one fuse round-trip with zero etcd lookups (BUG-1 parallel list + BUG-3 umask 修复后).
- The 宗旨 properties measured:
- 4K random write into a 256 MiB file = 1 new block + CAS commit; no old-data read/rewrite (verified via container block audit).
- rename/truncate/chmod = metadata transactions only.
- Crash of a lease holder: other writers blocked (EBUSY) until etcd TTL expiry (30 s), then recovery (verified).
- Concurrent writers on different nodes: CAS conflict → reload → retry (verified with 2 mounts writing the same file).
Amplification metrics(宗旨专项,2026-08-11 容器审计定死)
更正(2026-08-11 M1 审计):此前 REPORT/BENCHMARK 的 “4K 写 → 1 × 4MiB 对象 ≈ 1024× byte amplification” 是错误声明。 实测:4K 写 + fsync → 1 × 4096B Swift 对象(字节放大 = 1×)。 真实问题是 object/request amplification(每次 4K 写 = 1 个对象 + 1 次 PUT)。
| 指标 | 值 | 说明 |
|---|---|---|
| 4K 写 + fsync → Swift | 1 × 4096B 对象,1 PUT | 字节放大 1×;491ms/写(PUT + etcd commit) |
| 100×4K 随机写 + 单 fsync | 96 个对象(94×4K + 2×8K 合并) | 非相邻 4K 写不合并 → N 对象;flush 曾串行 33s → 批量并行后 9.4s(4f063f7) |
| empty-file create → Swift | 0 个对象(纯 metadata) | 114ms/create = 串行 etcd 往返(非 Swift);stat 9ms |
| stat → etcd | 0(路径缓存命中)/ 1(miss) | BUG-1 前缀修复 + 并行化后 |
| readdir(726) → etcd | 1 次前缀扫描 + 并行 inode 拉取 | 修复前 1008 key 泄漏 + 串行 = 25s |
网络拓扑(M3 定死,与 ROADMAP 附录 M1-M3 对齐):
10.0.0.1是 loopback (dev lo,27.9 GB/s)—— 单节点 benchmark 的 client→Swift 路径不跨网卡, 352-406 MiB/s 物理自洽;“千兆内网” 表述不适用单节点。eth1(10.0.0.x 内部网)为 Contabo virtio 网卡(ethtool 无法读速,物理 ≥10Gb 量级)。真实跨节点上限需 跨节点路径测量(G10 的 5-12 MiB/s 为部分证据)。
G8 判定(R7,2026-08-11)
G8 性能 gate 判定从 PASS/FAIL 改为 MEASURED / REGRESSION / TIMEOUT / SLO-VIOLATION
(scripts/detect/run_gates.sh)。results.tsv 保留历史 PASS/FAIL 列(向后兼容),
新增 class= 列承载 R7 判定:
MEASURED:干净跑完,高于 SLO(且已知 baseline 时高于 baseline);REGRESSION:高于 SLO,但低于已记录 baseline(默认 seq-write 202.5 MiB/s,G8_BASELINE_MIB,容差G8_REGRESSION_PCT=0.8);SLO-VIOLATION:低于 SLO(G8_SLO_SEQ_WRITE_MIB=10、G8_SLO_SEQ_READ_MIB=50、G8_SLO_RANDREAD_IOPS=500,env 可覆盖);TIMEOUT:任一 fio/mdtest job 触到 time bound(exit 124)。
mdtest 同理(create ops/s SLO G8_SLO_MDTEST_CREATE_OPS=1,baseline 9.5 ops/s)。
Tuning knobs (config)
block_size(4 MiB default): trade HTTP requests vs write amplificationprefetch(8 MiB default): read window per requestread_workers/write_workers: parallel GET/PUT concurrencycache_blocks: in-memory read cache capacitywrite_back: kernel write-back cache (on by default)lease: ProxyFS-style write leases (etcd only)kernel_ttl: 0 = 正确性优先(内核每次重验);bridge patch 稳定后可回正