news 2026/9/21 7:41:13

FoundationDB 存储基准测试上 RAM Disk:mako_storage_bench.sh 在 okteto 开发 Pod 上的 tmpfs 实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FoundationDB 存储基准测试上 RAM Disk:mako_storage_bench.sh 在 okteto 开发 Pod 上的 tmpfs 实践指南
  • 分布式数据库
  • KV存储
  • 数据库
  • 后端

【免费下载链接】foundationdb

FoundationDB - the open source, distributed, transactional key-value store

项目地址:https://gitcode.com/gh_mirrors/fo/foundationdb
点击查看免费下载

mako_storage_bench.sh是 FoundationDB 仓库中用于捕获存储服务器 CPU 回归的单机吞吐基准脚本,其设计前提是"存储进程的 CPU 成为瓶颈"。本文以 contrib/mako_storage_bench-ramdisk-okteto.md 为主体,完整讲解如何在 okteto 开发 Pod 上把集群数据目录放到 tmpfs(内存盘),使基准真正跑成 CPU-bound;并深入 contrib/mako_storage_bench.sh 及 fdbserver/flow 源码,解释脚本自动注入的 tmpfs knob、redwood 与 rocksdb 引擎的差异、okteto 拓扑坑与容量规划。读完本文,你将能够独立完成从"给 Pod 挂载 /mnt/ram"到"跑出可信的 TPS 对比数据"的全部步骤。

为什么 mako 存储基准需要一块 RAM Disk

mako_storage_bench.sh的设计目标是让存储服务器 CPU成为瓶颈,从而检测存储引擎乃至存储服务器热路径(flow、fdbrpc、网络、序列化等)上的 CPU 回归。这在数据目录位于快速本地存储时成立;但在 okteto 开发 Pod 上并不成立:Pod 的文件系统是容器 overlay,底层是网络挂载的 EBS,因此基准被 EBS 的 fsync/IO 延迟所束缚,而不是存储 CPU。

症状非常典型(文档记录的实测):

  • 吞吐量停留在很低的平台(在全新 m7a Pod 上约1.5k TPS);
  • 存储进程 CPU 远低于 85%;
  • commit 延迟达到数十毫秒。

解决办法是把集群数据放到tmpfs(内存支持的目录)上,使 commit 不再承担磁盘延迟,存储进程重新变为 CPU-bound。脚本已经内置了自动检测逻辑:当/mnt/ram存在且可写时,它会自动使用它(并自动附加下述 tmpfs knob),因此一旦开发 Pod 拥有了/mnt/ram,直接运行contrib/mako_storage_bench.sh build_output4就能以 CPU-bound 方式跑起来。剩下的实际工作只是给 Pod 提供这块内存目录——而这在 okteto 上需要一些技巧。

补充说明:build_output4只是示例构建目录(作者本机~/src/fdb4构建树,即开发 Pod 上的/root/build_output4),请替换为你自己的构建根目录。注意mako 默认不参与构建:需要以-D BUILD_MAKO=ON配置构建,否则build_output4/bin/mako不存在,脚本会在入口处直接报错退出(contrib/mako_storage_bench.sh 会对三个二进制逐一检查可执行性)。

TL;DR

# 一次性 Pod 配置(详见下文):给开发 Pod 一个 /mnt/ram tmpfs + 内存余量。 # 然后直接运行——脚本自动检测 /mnt/ram 并附加 tmpfs knob: contrib/mako_storage_bench.sh build_output4

脚本自动做了什么(以及如何覆盖)

WORKDIR:数据目录与输出目录

集群数据目录和 mako 输出都位于WORKDIR之下。脚本的判定逻辑在 contrib/mako_storage_bench.sh:

if [[ -z "${WORKDIR:-}" && -w /mnt/ram ]]; then WORKDIR=/mnt/ram/mako_storage_bench fi readonly WORKDIR="${WORKDIR:-${PWD}/mako_storage_bench}"

即:当/mnt/ram是可写目录时默认使用/mnt/ram/mako_storage_bench,否则回退到$PWD/mako_storage_bench。可以通过导出WORKDIR覆盖(例如指向非 tmpfs 路径,强制使用 EBS 数据目录,用于对照实验)。

tmpfs knob:--knob_disable_posix_kernel_aio=1

这是脚本在 tmpfs 上能跑起来的关键。fdbserver 默认以O_DIRECT+ 内核 AIO 打开数据文件,而tmpfs 拒绝O_DIRECT,因此如果没有回退机制,集群根本启动不起来。脚本在检测到WORKDIR位于 tmpfs 上时自动附加该 knob(contrib/mako_storage_bench.sh):

if [[ "$(stat -f -c %T "$(dirname "${WORKDIR}")" 2>/dev/null)" == tmpfs \ && "${KNOBS}" != *disable_posix_kernel_aio* ]]; then KNOBS="${KNOBS:+${KNOBS} }--knob_disable_posix_kernel_aio=1" fi

对应源码在 fdbrpc/Net2FileSystem.cpp:当文件以OPEN_UNBUFFERED打开、未禁用 AIO、且FLOW_KNOBS->DISABLE_POSIX_KERNEL_AIO为假时走AsyncFileKAIO(内核 AIO,要求O_DIRECT);否则回退到Net2AsyncFile(EIO 缓冲路径,tmpfs 可接受)。该 knob 的默认值在 flow/Knobs.cpp 中为0,即默认启用内核 AIO。因此:

  • 你可以用KNOBS='...'传额外的 knobs;
  • 如果你没有自己包含 tmpfs knob,脚本会替你追加;
  • 反过来,若你想强制 EBS 数据目录(非 tmpfs),脚本不会注入该 knob,保持默认的内核 AIO/O_DIRECT 行为。

有界时长:SECONDS_RUN / WARMUP_SECONDS

脚本的默认值是WARMUP_SECONDS=300SECONDS_RUN=600(见 contrib/mako_storage_bench.sh),均可通过环境变量覆盖;缩短测量窗口(例如SECONDS_RUN=120 WARMUP_SECONDS=60)是控制 tmpfs 占用、加快迭代的有效手段,详见下文"容量规划"。

存储引擎:redwood(默认)vs rocksdb on tmpfs

redwood:开箱即用

默认引擎是redwood(映射为ssd-redwood-1)。redwood 走 flow 的文件 IO 路径,脚本自动附加的 tmpfs knob 已足够,无需任何KNOBS

引擎到存储类型的映射在 contrib/mako_storage_bench.sh:

引擎名configure new存储类型备注
redwoodssd-redwood-1默认引擎
rocksdbssd-rocksdb-v1需要 RocksDB-enabled 构建
sharded-rocksdbssd-sharded-rocksdb需要 RocksDB-enabled 构建,须显式指定
allredwood + rocksdb便捷别名,不含 sharded-rocksdb

集群由 tests/loopback_cluster/run_custom_cluster.sh 以1 stateless / 1 log / 1 storage(复制因子 1)的方式拉起,脚本通过--storage_type把引擎传入configure new(contrib/mako_storage_bench.sh)。

rocksdb:还需要两个额外 knobs

rocksdb引擎(... build_output4 rocksdb)需要两个额外的 knobs。RocksDB 自行管理文件 IO(不经过 flow),在ssd-rocksdb-v1上默认以O_DIRECT打开数据库:ROCKSDB_USE_DIRECT_READSROCKSDB_USE_DIRECT_IO_FLUSH_COMPACTION在 fdbserver/core/ServerKnobs.cpp 中默认值均为true,并在 fdbserver/kvstore/KeyValueStoreRocksDB.cpp 中直接映射到 RocksDB 的Options::use_direct_readsuse_direct_io_for_flush_and_compaction

tmpfs 拒绝O_DIRECT,因此存储服务器会在rocksdb::DB::Open处失败(fdbserver/kvstore/KeyValueStoreRocksDB.cpp 记录RocksDBError: "Invalid argument: Direct I/O is not supported by the specified DB.")并崩溃循环——而且由于这发生在任何数据目录创建之前,基准会卡在configure new(脚本的BUILD_TIMEOUT只保护 mako 构建阶段,不覆盖集群启动,因此不会自我中止,见 contrib/mako_storage_bench.sh 的注释)。

自动的 tmpfs knob 对 RocksDB 无效,必须单独禁用 RocksDB 的直接 IO。这两个是运行时 knobs,无需重新编译。脚本仍会自动附加 flow 的 knob,所以你只需提供两个rocksdb_*knobs:

KNOBS='--knob_rocksdb_use_direct_reads=false --knob_rocksdb_use_direct_io_flush_compaction=false' \ contrib/mako_storage_bench.sh build_output4 rocksdb

这两个rocksdb_*knobs 对 redwood 无害,因此同样的KNOBS也可用于... build_output4 redwood rocksdb的一次性双引擎跑法。

文档记录了一次 24Gi tmpfs(m7a Pod)上完整 300s/600s 跑的观测:加上 knobs 后 rocksdb 与 redwood 一样跑成存储 CPU-bound(约4.6k vs 4.8k TPS),且峰值占用更小(约3.3 GB vs redwood 的 5.5 GB)——即 rocksdb 并没有撑爆内存盘。不过它的单次 GET 延迟更高、冲突率也更高,这是正常现象。

你无法直接在 Pod 内mount一个 tmpfs

开发容器没有CAP_SYS_ADMIN,所以mount -t tmpfs ...会以permission denied失败。/dev/shm存在但被限制为 64 MB;你在系统里看到的那些大 tmpfs 挂载(如/var/okteto/secret等)都是只读的。因此,这块内存目录只能来自Pod 规格(spec)中的 memory-backedemptyDir

okteto 拓扑坑:要 patch BASE deployment,而不是 clone

okteto up并不是直接运行你的 deployment。对于name: <dev>的 okteto manifest,会存在两个 deployment:

  • <dev>—— base deployment,缩容到0/0。这是 okteto 克隆的来源(source of truth)。
  • <dev>-okteto—— okteto 生成并**主动调谐(reconcile)**的活动克隆,真正运行你的 Pod。

关键行为:

  • okteto 会剥掉它不拥有的 volumes:对<dev>-okteto执行kubectl patch deployment <dev>-okteto ...添加 volume 会静默消失
  • 但 okteto 克隆时会保留 base deployment 的 volumes(这正是efs-ccache、fdb cluster 文件等能到达开发 Pod 的方式)。

所以 volume 必须加到base<dev>deployment 上,okteto up随后会把它带进 Pod。(下文示例使用 deployment 名gglass-dev,请替换为你 okteto manifest 的name:。)

设置步骤

1. 提高开发容器内存上限(okteto.yml)

memory-backedemptyDir的内容会计入容器的 memory cgroup,即占用dev容器的内存limit(而不是节点的空闲 RAM)。因此 tmpfs 内容加上所有 fdbserver/mako 的 RSS 必须落在 limit 之内。在okteto.yml中提高它:

resources: requests: cpu: "4000m" memory: "24Gi" # was 16Gi limits: cpu: "8000m" memory: "48Gi" # was 32Gi -- room for a 24Gi tmpfs + fdbserver/mako RSS

这部分确实会okteto up持久保存在okteto.yml中。

2. 给 base deployment 添加 tmpfs volume

okteto.yml(这种 manifest 格式)无法声明emptyDir,因此以 strategic-merge patch 的方式应用到basedeployment。保存为fdb-ramdisk.yaml

spec: template: spec: volumes: - name: ramdisk emptyDir: medium: Memory sizeLimit: 24Gi containers: - name: dev volumeMounts: - name: ramdisk mountPath: /mnt/ram

应用到 base(它处于 0 副本,不影响正在运行的东西):

kubectl patch deployment gglass-dev --patch-file fdb-ramdisk.yaml

注意:该 patch 修改的是活着的 base deployment。任何供应gglass-dev的机制(你的 dev-pod 基础设施/manifest)在重新应用时都会覆盖它。若要永久化,请把同样的volumes/volumeMounts块加到那个源 manifest 中。

3. 重建 Pod

okteto up

okteto 会把(已 patch 的)base 克隆成<dev>-okteto,因此新 Pod 会拥有/mnt/ram和 48Gi 上限。

4. 验证

kubectl exec <pod> -c dev -- df -hT /mnt/ram # 期望:tmpfs, 24G kubectl exec <pod> -c dev -- sh -c 'touch /mnt/ram/.w && rm /mnt/ram/.w && echo ok'

第二条命令验证/mnt/ram可写。

tmpfs 容量规划

磁盘占用随总插入量 = TPS × 墙钟时间增长(默认g18ui工作负载每次事务插入一个新 key)。内存盘提高了 TPS,因此同样的墙钟时间会比 EBS 写出更多数据。作为参考:EBS 上 ~1.5k TPS 跑 600 s 大约写了 ~4 GB;redwood 文件是主体,考虑 COW/extent 开销后约~2 KB/次插入

两个事实让容量规划变得简单:

  • tmpfs 的sizeLimit硬上限(超出写入会得到 ENOSPC),并且其用量计入容器内存上限(超过即 OOMKill);
  • 内存只在实际写入字节时消耗,而不是预先保留。

建议:

  • 默认的完整 300s/600s 跑在24Gi tmpfs上对 redwood 绰绰有余(峰值约 5.5 GB)。若引擎更快或跑得更久,想控制占用,可以用SECONDS_RUN=120 WARMUP_SECONDS=60缩短(一旦 CPU-bound,2 分钟的测量窗口足够)。
  • sizeLimit: 24Gi+limits.memory: 48Gi可从容覆盖上述场景。对于极高 TPS 或长时间运行,两者一起上调(例如 40Gi tmpfs / 64Gi limit),并确认节点确实有这么多 RAM。

禁用 / 回退

  • 从 base 移除 volume 并重新克隆:

    kubectl patch deployment gglass-dev --type=json \ -p '[{"op":"remove","path":"/spec/template/spec/volumes/-"}]' okteto up

    (或重新应用你的 basegglass-devmanifest。)

  • 如需恢复原来的内存上限,还原okteto.yml中的 memory limits。

  • 脚本现在在/mnt/ram存在时自动优先使用它,所以普通运行就会用内存盘。要不删除 volume 而强制使用 EBS 数据目录,可对该次运行导出WORKDIR=$PWD/mako_storage_bench(任何非 tmpfs 路径均可)。

其他坑

  • 你会丢失build_output4它位于易失的容器 overlay 上,而内存上限的修改和 volume patch 都会触发 Pod 滚动更新(rollout)。之后用run-ccmk4(或你自己的构建包装脚本)重建——ccache 在持久 EFS volume 上,因此热重建只需几分钟,而不是冷构建。
  • mako链接libfdb_c.so,且以SKIP_BUILD_RPATH构建。mako_storage_bench.sh已经设置LD_LIBRARY_PATH=<build>/lib(以及LD_BIND_NOW=1),使 mako 加载匹配的客户端(见 contrib/mako_storage_bench.sh)。这与内存盘无关,但正是 mako 能从构建树直接运行的原因。
  • mako 退出时会在_dl_fini中 abort(libfdb_c 作为共享库的 teardown 问题),但发生在写出全部输出之后。脚本以"已完成的报告"(文本中检索Overall TPS,见 contrib/mako_storage_bench.sh)判断成功,并禁用了 core dump(ulimit -c 0),所以这是无害的——看到 abort 信息不必惊慌。

附:一次跑完后的产物

每次引擎跑完成后,输出位于${WORKDIR}/${label}/下:

  • mako-run.txt—— mako run 阶段的 tee 报告,含Overall TPS等汇总;
  • mako.json—— 逐秒采样与最终统计(--json_report);
  • mako-sketch.json—— 原始延迟 sketch(--stats_export_path);
  • cluster-start.logmako-build.txt—— 集群启动日志与 mako 构建阶段输出。

A/B 对比时请务必在同一台机器/实例上背靠背运行基线构建与实验构建(结果仅在相同硬件与条件下可比),并重点观察 TPS 差值——任何在存储服务器热路径上新增的 CPU 开销都会与单核存储进程争抢 CPU,通常表现为吞吐下降而非明显延迟。

  • 分布式数据库
  • KV存储
  • 数据库
  • 后端

【免费下载链接】foundationdb

FoundationDB - the open source, distributed, transactional key-value store

项目地址:https://gitcode.com/gh_mirrors/fo/foundationdb
点击查看免费下载

相关推荐

上一篇:彻底清理!Owncast API文档优化实战指南
下一篇:MTKClient项目中Preloader区域访问问题的分析与解决

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!