- 分布式数据库
- KV存储
- 数据库
- 后端
【免费下载链接】foundationdb
FoundationDB - the open source, distributed, transactional key-value store
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=300、SECONDS_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存储类型 | 备注 |
|---|---|---|
redwood | ssd-redwood-1 | 默认引擎 |
rocksdb | ssd-rocksdb-v1 | 需要 RocksDB-enabled 构建 |
sharded-rocksdb | ssd-sharded-rocksdb | 需要 RocksDB-enabled 构建,须显式指定 |
all | redwood + 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_READS与ROCKSDB_USE_DIRECT_IO_FLUSH_COMPACTION在 fdbserver/core/ServerKnobs.cpp 中默认值均为true,并在 fdbserver/kvstore/KeyValueStoreRocksDB.cpp 中直接映射到 RocksDB 的Options::use_direct_reads与use_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 upokteto 会把(已 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(或重新应用你的 base
gglass-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.log、mako-build.txt—— 集群启动日志与 mako 构建阶段输出。
A/B 对比时请务必在同一台机器/实例上背靠背运行基线构建与实验构建(结果仅在相同硬件与条件下可比),并重点观察 TPS 差值——任何在存储服务器热路径上新增的 CPU 开销都会与单核存储进程争抢 CPU,通常表现为吞吐下降而非明显延迟。
- 分布式数据库
- KV存储
- 数据库
- 后端
【免费下载链接】foundationdb
FoundationDB - the open source, distributed, transactional key-value store
相关推荐
探索未来开发:Okteto——在Kubernetes上的应用开发利器
探索未来开发:Okteto——在Kubernetes上的应用开发利器 项目简介 在云原生时代,Kubernetes的出现让应用程序部署达到了前所未有的高度,但与
云原生开发工具Delta Lake 基准测试框架实战指南:在 AWS EMR 与 GCP Dataproc 上运行 TPC-DS 基准测试
Delta Lake 基准测试框架实战指南:在 AWS EMR 与 GCP Dataproc 上运行 TPC DS 基准测试 本指南基于 Delta Lake
湖仓一体数据工程数据湖30分钟上手Kubernetes架构可视化:从Pod到存储的完整实践指南
30分钟上手Kubernetes架构可视化:从Pod到存储的完整实践指南 diagrams是一个功能强大的架构可视化工具,它允许你使用代码来创建清晰、专业的云系
数据可视化开发工具文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考