规模与轮换
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 GB | FS 流量走宿主本地不计入;主要是激活突发与跨 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/dozyfs 的 dozycat/reconcile.py(level-triggered、幂等的控制器模型)。
dozycat