会话上下文容量规划:过期之后,还剩下什么

把可重建的会话上下文放到 SSD 上,会改变容量预算。TTL 可见性、索引删除、物理空间回收和权威登录状态,仍需分别评估。

保留会话上下文,可以减少应用反复重建最近浏览记录、工作流中间结果或派生个性化数据的开销。容量规划需要回答:用户停止活动后,哪些上下文仍值得保留多久?Lavik 在 DRAM 中维护紧凑的键索引,将值存放在 SSD 上,因此可以纳入延长保留期的评估。但 TTL 到期并不会同时释放所有资源。本文提出一种应用设计与评估方案,并非客户部署案例。

先区分可重建上下文与权威登录状态。这里对“可重建”的定义很严格:上下文丢失只会导致一次未命中,并从权威来源重新生成,不会恢复权限或改变身份。令牌有效性、退出登录、撤销授权和权限变更,都需要独立的正确性约定。Lavik 0.1.0-beta.1 是采用 Apache 2.0 许可证的 beta 项目,普通写入成功响应并不代表同步持久化屏障已经完成。因此,上下文保留实验不能证明它适合作为登录决策的权威数据源。

在这套待评估设计中,让授权检查继续依赖权威系统,并把存储的上下文视为可以丢弃的输入。应用应明确处理未命中、版本过期和重建失败。如果某个上下文字段能够授予访问权限,即使应用称它为缓存,也应纳入授权审查。不能因为 TTL 更新或删除操作返回成功,就认为已确认的安全状态变更一定能在崩溃后保留。

为保留期建立三份独立预算

第一份预算是可见上下文。对于不续期、也不提前删除的固定 TTL,可以用新建的不同上下文键速率乘以保留时间,作为稳态下的初始估算。举一个规划假设,而非测量结果:每秒新建 100 个键,保留一小时,对应 360,000 个存活键。若平均值大小假设为 8 KiB,值载荷约为 2.75 GiB。这不包括键、索引、记录元数据、旧版本、缓冲区或副本。对于滑动 TTL,应依据实际续期和停止使用的行为估算保留量;请求速率乘以 TTL 会把同一会话的重复活动误算成新会话。

第二份预算是仍被保留的索引内存。Lavik 的读取路径会立即隐藏过期值,而有工作量上限的后台过期处理会在操作前重新检查当前版本和截止时间。它优先写入墓碑,以防时钟回拨后旧值重新出现。墓碑仍占用索引槽位;只有移除索引项,才具备渐进式缩小桶表的条件。因此,应用还能读到的上下文数量可能先下降,索引保留内存随后才下降。需要同时观察两者,并区分保留内存计量与进程 RSS:Lavik 的内存限制不是严格的 RSS 上限。

第三份预算是可复用的设备空间。覆盖写会追加新记录版本;在替代版本完成刷盘之前,旧的持久化版本在物理空间计量中仍被视为有效。普通记录块只有在已经持久化、不再活跃、没有被固定引用且有效数据占比不超过 50% 时,才会成为碎片整理候选。搬迁后的目标数据达到持久化边界,源块才能回收。因此,一批会话到期后,过时记录可能散落在仍包含有效记录的块中。容量预算需要覆盖这段间隔,不能把过期载荷字节直接当成可用空间。

TTL 控制读取可见性,并不是物理擦除的截止时间。在 Lavik 的存储生命周期中,逻辑过期、索引移除和块复用是不同事件。如果保留的上下文有物理删除要求,需要单独评估:所提供的架构资料没有确立安全擦除保证,就连显式存储重置也被描述为逻辑重置,而非安全擦除。

这三份预算引出一个具体的工作负载问题:会话是集中到期,还是一部分持续续期、其余逐渐被放弃?两种模式可能拥有相同的可见载荷总量,却留下不同的有效记录与过时记录分布。Lavik 提供每设备可用容量、持久化状态和维护统计,可用于分析这种差别。存储架构说明了回收机制,但没有给出经过测量的会话变动容量系数。

评估完整过期周期,而不只是装满数据

定义有代表性的上下文集合:不同键的新建速率、键和值的大小、TTL 分布、续期频率、覆盖写频率以及重建成本。将固定生命周期上下文与滑动续期会话分开。设计一次使用可丢弃数据的试验,覆盖装载、活跃续期、停止使用、过期,以及继续接纳新上下文的全过程。同时包含错峰过期和集中到期。这些是建议的评估阶段,并非本文报告的已执行测试。

试验过程中,将应用看到的命中与未命中,同保留内存、RSS、每设备可用块数、暂存脏字节、待完成刷盘和维护活动一起观察。第一轮过期之后继续运行,同时持续写入新上下文。在运行前确定可接受的前台延迟、重建流量和资源余量。一项实用的验收条件是:连续多个保留周期始终满足这些限制,且保留内存或不可用设备空间没有持续增长。仅有稳定的存活键数,并不足以说明这一点。

分别审查 TTL 续期、覆盖写、退出登录和客户端重试行为。在优雅重启之外,规划异常重启检查,记录哪些上下文丢失可以接受,并验证重建流程不会绕过授权。Lavik 从已提交记录中重建状态;响应时的成功与崩溃持久性是不同边界。评估恢复时还应计入重新填充数据的成本:架构文档指出,进程重启会创建新的复制历史,因此需要整个复制组进行全量同步。

所提供的主机回执报告:ARM64 minimal beta 软件包在 Linux 7.0.12-linuxkit、glibc 2.39 环境下,通过了基础配方的五项命令检查及一次优雅重启。该回执没有提供 TTL、异常崩溃、物理擦除或认证流程的验证结果。本文建议的试验超出了这项基础验证的范围。

9 月 18 日的 SPDK 报告提供了性能背景,但其中的均匀随机 GET 与覆盖 SET 工作负载不能证明会话保留行为。该报告使用 1 KiB 值、流水线深度一;一千万键的测量窗口为 30 秒,十亿键为 60 秒。报告将 22 个新测得的 Lavik 数据点与 124 个复用的对照数据点比较,且排除了 io_uring。TTL 变动、重建流量和授权行为需要独立评估。这些测量也不能证明崩溃持久性或 SLA 等价。

制定部署估算时,应将上述三份资源预算一并纳入,再加上复制、恢复余量和运维成本。仅凭值容量算术,不能得出总成本节省或等价服务承诺。历史成本报告比较的是特定日期的实例价格估算,托管服务与自建实例的服务边界不同,不能直接用于给这套会话设计定价。

从一组可丢弃的上下文开始

选择一种可重建上下文,记录其权威来源和未命中处理方式,并设定保留期与资源上限。在构建 TTL 试验之前,先用所提供的 basic-commands 配方确认客户端可以正常连接。发布包要求 Linux 6.1 或更新版本、可用的 io_uring 和兼容的 glibc;Linux 6.1 是 API 兼容性下限,并非经过测试的内核矩阵。minimal 软件包使用内核网络与 io_uring,并非所引用基准中的 SPDK 存储后端。只有在观察完整的过期与回收周期、确认重建行为可接受之后,才延长保留期。

读写第一组键

先安装所选 Linux 软件包和 redis-cli(Ubuntu/Debian:sudo apt-get install redis-tools)。适用于 Minimal 和 Standard。在终端 1 进入解压后包含 ./lavik 的软件包目录,再粘贴下面的 Bash 脚本并保持运行。在终端 2 执行客户端命令。启动 Lavik 无需修改 PATH、使用 root 权限或配置 systemd 服务。

终端 1 · 启动服务器
set -euo pipefail
if [ ! -x ./lavik ]; then
  echo "Run this from the extracted Lavik package directory (the folder containing ./lavik)." >&2
  exit 1
fi
mkdir -p /tmp/lavik-example
if [ ! -e /tmp/lavik-example/data ]; then
  fallocate -l 512M /tmp/lavik-example/data
fi
./lavik --bind 127.0.0.1 --port 6379 --metrics-port 0 \
  --network kernel --storage uring \
  --threads 2 --no-pin-workers --registered-buffer-mb-per-worker 64 \
  --shutdown-checkpoint --data-file /tmp/lavik-example/data \
  --log-dir /tmp/lavik-example/logs
终端 2 · 命令与预期结果
$ redis-cli -h 127.0.0.1 -p 6379 --raw PING
PONG

$ redis-cli -h 127.0.0.1 -p 6379 --raw SET greeting 'hello from Lavik'
OK

$ redis-cli -h 127.0.0.1 -p 6379 --raw GET greeting
hello from Lavik

$ redis-cli -h 127.0.0.1 -p 6379 --raw HSET user:42 name Ada
1

$ redis-cli -h 127.0.0.1 -p 6379 --raw HGET user:42 name
Ada
✓ 查看实际执行记录

0.1.0-beta.1 · Linux-7.0.12-linuxkit-aarch64-with-glibc2.39 · 2026-10-05

此记录验证功能行为,不验证 NVMe 性能、断电持久性或 SLA。

{
  "recipeId": "basic-commands",
  "platform": "Linux-7.0.12-linuxkit-aarch64-with-glibc2.39",
  "steps": [
    {
      "argv": [
        "redis-cli",
        "-h",
        "127.0.0.1",
        "-p",
        "6379",
        "--raw",
        "PING"
      ],
      "expected": "PONG",
      "actual": "PONG"
    },
    {
      "argv": [
        "redis-cli",
        "-h",
        "127.0.0.1",
        "-p",
        "6379",
        "--raw",
        "SET",
        "greeting",
        "hello from Lavik"
      ],
      "expected": "OK",
      "actual": "OK"
    },
    {
      "argv": [
        "redis-cli",
        "-h",
        "127.0.0.1",
        "-p",
        "6379",
        "--raw",
        "GET",
        "greeting"
      ],
      "expected": "hello from Lavik",
      "actual": "hello from Lavik"
    },
    {
      "argv": [
        "redis-cli",
        "-h",
        "127.0.0.1",
        "-p",
        "6379",
        "--raw",
        "HSET",
        "user:42",
        "name",
        "Ada"
      ],
      "expected": "1",
      "actual": "1"
    },
    {
      "argv": [
        "redis-cli",
        "-h",
        "127.0.0.1",
        "-p",
        "6379",
        "--raw",
        "HGET",
        "user:42",
        "name"
      ],
      "expected": "Ada",
      "actual": "Ada"
    }
  ],
  "status": "passed",
  "binaryVersion": "lavik 0.1.0-beta.1",
  "executableOnPath": false,
  "workingDirectory": "/opt/lavik",
  "sourceCommit": "3955b98d43b312324aa8d52775df52cfb111c0d0",
  "packageVersion": "v0.1.0-beta.1",
  "gracefulRestart": "passed"
}