规模与轮换

dozycat 的规模模型:每 VM 一个 volume、约 1000 并发、10 万总量轮换,靠冷热分层与每 VM 稳定路由把 10 万规模做成 serverless 式经济——只为活跃的 cat 付费。

规模模型一览

dozycat 的实际部署形态是 microVM FaaS:海量休眠、少量在线、高频换入换出。三条事实定义了这个模型:

  • 每 VM 一个 volume:每个 cat 拥有独立的 JuiceFS volume(独立元数据 namespace + 独立对象存储 prefix + 独立凭证),各自的数据和代码互不相干。
  • 约 1000 并发在线:任意时刻只有约 1000 个 cat 是活跃的(已挂载、有会话),平均约 1 个热 cat / node。
  • 10 万总量轮换:注册表里可以有 10 万个 volume,其余约 99000 个处于休眠——数据安静地躺在对象存储里,没有任何进程。100:1 的超分(overcommit)。

关键洞察:每个 VM 的持久化数据在 volume 里(对象存储持久),不在 VM 内存里。这把 VM 内存快照从「持久性必需」降级成「性能层可选」——休眠的 cat 完全不需要占用任何热资源。

为什么 10 万很便宜:冷热分层

休眠的 99000 个 volume 不能占用任何热资源。每个状态维度都独立分层,控制面用 LRU / 预测在层间搬运,对象存储是持久地板

维度热 (hot)温 (warm)深冷 (deep-cold)持久地板
VM 内存Paused,驻留宿主 RAM内存快照在本地 NVMe丢弃快照,靠「重启 + 挂 volume」激活—(内存非持久)
数据缓存NVMe 上的 per-VM 工作集随 VM 变冷而尽力保留 / 失效无,激活时按热度图 warmup对象存储(block 持久)
元数据活跃会话在元数据引擎引擎内静默存储可 dump 到对象存储,激活时 rehydrate元数据引擎 / 对象存储 dump

分层带来的省钱效果:

  • 深冷直接丢内存快照:因为数据在 volume 里持久,深冷 cat 用「全新 boot + 挂 volume + 跑代码」就能激活,省掉了 10 万 × 内存的快照存储。只有热 / 温层才用内存快照换快速恢复。
  • 元数据也分层:深冷 volume 的元数据可以 juicefs dump 到对象存储、从引擎删除,激活时 load 回来——把元数据引擎容量从「10 万 volume」压到「活跃 + 温 volume」。

激活路径与冷启延迟

激活 = 把一个 cat 从某个冷层拉回在线。延迟随起始层级递增,这是头号 SLO。一个发往 xxxxx.dozyfs 的请求会自动触发激活(详见 生命周期稳定路由):

起始层激活步骤量级
热 (Paused)ch-remote resume毫秒
温 (NVMe 快照)本地读快照 restore + 重连 virtiofsd亚秒 ~ 秒
冷 (对象存储快照)下载 memory file → restore → 重连秒级
深冷 (无快照)boot + 挂 volume(元数据可能 rehydrate)+ app 启动 + 冷缓存 warmup数秒 ~ 数十秒

控制面用三个抓手压低冷启:

  • 预测预热:对「很可能下一个被激活」的 cat 提前回温(预取快照到 NVMe、预 rehydrate 元数据),把延迟藏掉。
  • 温层 sizing:温层(NVMe 快照)越大,越少激活命中对象存储下载。
  • 数据面 warmup:激活时按该 volume 的热度图 juicefs warmup,把工作集预取进本地 NVMe,避免应用首跑被冷 miss 拖慢。

容量与调度:自动选一个有空位的就绪节点

因为每个 VM 数据各异,没有跨 VM 缓存共享——缓存是某 node 上该 cat 的工作集,cat 走了基本失效。调度逻辑因此围绕「容量」和「亲和」展开。

把一个 cat 放到节点上的入口是 schedule:它会挑一个 ready、还有空闲 capacity_cats 的节点,拒绝超容量 / 未就绪 / 未知的节点,也拒绝已被别人占用的 guest_ip。不指定节点时,自动选负载最低的就绪节点

# 自动挑一个就绪、有空位的节点(least-loaded)
dozycat registry schedule \
  --volume-id vol-abc123 \
  --cat-id cat-abc123

# 或显式指定目标节点(仍会校验 ready + 容量)
dozycat registry schedule \
  --volume-id vol-abc123 \
  --cat-id cat-abc123 \
  --node-id node-7

调度策略要点:

  • 容量门禁:每个 node 有 capacity_cats(约 1 hot VM/node);超容量直接拒绝,整个 fleet 满了会报「no ready node with free capacity」。
  • 亲和(best-effort):恢复一个 cat 时优先调度回「上次跑它、可能还留着 NVMe 缓存 / 本地快照」的 node → 命中则激活快、省对象存储下载。10 万 cat 不可能全保亲和,所以亲和是优化而非保证;miss 就付一次 warmup 成本。
  • 凭证按激活铸造:激活时为该 volume 铸一个短期、最小权限的 token(仅该对象存储 prefix + 该元数据 shard),停机后撤销 → 一个 node 任意时刻只持有当前在跑那个 cat 的凭证,轮换下凭证隔离依然成立。

控制面是 serverless 调度器:只把约 1000 个活跃(+ 温池)当作「在调度」的实时对象;休眠的 99000 个是数据库里的行,不是 K8s Pod / CRD。etcd / K8s API 扛不住 10 万高 churn 对象,所以注册表用专用可扩 DB。

成本直觉:活跃 cat-小时主导

稳态文件系统流量(git / grep)全走宿主本地 virtio-fs socket,不上网;网络上只剩该 cat 的元数据 RPC + 冷 miss / writeback 的对象存储读写。所以规模成本由三块构成,而活跃 cat-小时是绝对大头:

成本项计费维度说明
活跃 cat-小时按实际在线时长主导项。10 万休眠 volume 不产生计算成本,只有约 1000 并发在线的 cat 在烧 CPU/RAM。
存储 GB-月对象存储实际占用小文件为主,便宜;深冷丢快照后不为 10 万 × 内存付费。
出口流量egress GBFS 流量走宿主本地不计入;主要是激活突发与跨 region 流量。

所以「10 万 volume」并不意味着「10 万份计算账单」——你只为醒着的 cat 付费。这正是 dozycat Cloud 定价的依据:按活跃 cat-小时 + 存储 GB-月 + 出口 计费,带免费额度起步。也可以选 Open Source(自托管、永久免费、完整控制面 + cat-agent + dozycat)或 Enterprise(专属、SSO、SLA、支持,联系销售)。

高 churn 下不丢写

每次 hot→冷降级都是一次 drain(上传 writeback + 提交元数据)。10 万轮换意味着 drain 频率很高,所以 drain 必须快

  • --upload-delay=0 + per-VM 小工作集 → staging 通常很小,drain 快。
  • 或严格卷直接非 writeback(fsync 同步上传),降级时数据已在对象存储,drain 仅需干净卸载。
  • 深冷丢内存快照之前必须 sync + quiesce + drain,确保 volume 是一致点(内存丢失不影响 volume 数据)。

drain 的完整语义见 持久性与不丢写;轮换状态机的实现见 github.com/dozycat/dozyfsdozycat/reconcile.py(level-triggered、幂等的控制器模型)。