# 最终会话回读拆分与定向读取(F101/F102) RedisContextSaver把同一会话的全部检查点、待处理写入及请求历史存入一个hash。F101时get_tuple调用HGETALL取回全部字段,再选中并解码最新/指定检查点及其待处理写入。F102改为单次Lua:HKEYS在服务端扫描字段名,选择对应命名空间内最新/指定检查点,仅HGET并回传该检查点及它的pending writes。历史列表接口仍全量读取。 读取原子执行,避免并发写入或过期发生在选择与取值之间。旧v1 hash直接兼容,不迁移、不删除历史、不刷新读取TTL、不跳过最终get_state。新建会话首轮也产生多个检查点,因此同样适用。服务重启后,新request_performance按原thread_id/request_id记录数值统计,旧请求无法回填。 ## 耗时(metrics.spans) 下列名称后缀`.session_result_load`表示最终get_state;`.session_load`表示开始读取。其他后缀是所在图阶段或other,不能混作最终读取。count/total_ms/max_ms/failed_count沿用原口径。 | 前缀 | 范围 | | --- | --- | | redis_hgetall | Redis客户端HGETALL调用到返回,包括连接获取、网络、服务器执行和客户端RESP解析,不能当作纯服务器执行时间 | | redis_checkpoint_read | F102状态定向读取EVAL调用到返回,同样包括网络、服务器执行及客户端解析;取代get_tuple的redis_hgetall | | redis_rows_parse | 返回字段名JSON解析、字典组装及新增计数开销;不解析历史value正文 | | checkpoint_select | 扫描字段选择最新或指定检查点 | | checkpoint_pending_select | 选取该检查点的pending writes,不含其解码 | | checkpoint_json_decode | 选中value的JSON包裹层解析 | | checkpoint_base64_decode | 选中value的Base64解码 | | checkpoint_deserialize | LangGraph serde.loads_typed,恢复选中检查点和待处理写入 | | checkpoint_cache_prepare | F169请求内正文登记及首次回读指纹计算;不含Redis内指纹计算,后者计入redis_checkpoint_read | session_result_load总区间仍包含这些子项;它与子项的差额还包含LangGraph状态/任务组装和其他本地开销,不能全部归为Redis网络。不同类别解码按实际次数累加。 ## 数据量(metrics.measurements) 每项提供count、total、max,后缀同上。count是观测次数,total是各次量值之和,max是单次最大值;例如redis_returned_fields的total才是累计字段数。 | 前缀 | 量值 | | --- | --- | | redis_scanned_fields | 定向读在Redis服务端扫描的字段名数量,不表示返回了对应正文 | | redis_returned_fields | 实际返回字段数;定向读仅为选中检查点及其pending writes,历史列表仍为全hash | | redis_returned_bytes | 所有字段名和值的编码字节数之和 | | redis_checkpoint_fields / redis_checkpoint_bytes | 检查点类字段数/字节量 | | redis_write_fields / redis_write_bytes | pending write类字段数/字节量 | | redis_request_fields / redis_request_bytes | 请求历史类字段数/字节量 | | redis_other_fields / redis_other_bytes | 其他字段数/字节量 | | checkpoint_pending_fields | 为当前选中检查点取出的pending write数量 | | checkpoint_decoded_input_bytes | 实际进入_decode的编码value字节量,count表示解码value数量 | | checkpoint_serialized_bytes | Base64解码后的序列化字节量,反序列化之前 | | redis_checkpoint_cache_hit | F169本次Redis原子核验通过并省去检查点正文回传的次数,通常单次读为0或1 | | redis_checkpoint_cache_saved_bytes | 核验命中省去的正文有效载荷字节(扣除整数确认标记),不含协议头 | 字节量不含Redis协议封装/TCP/TLS头,也不等于Python对象内存大小。真实客户端返回bytes直接len计数,替身str按UTF-8计算。仅日志数值和固定分类,不写Redis键、字段名、checkpoint ID、历史原文、模型输入输出或异常正文。空会话也记录0字段/0字节;读取失败记录耗时和失败计数,未返回的数据不伪报为0。 ## 请求内正文复用(F169) 两个聊天接口均在GraphRuntime.run内启用请求作用域。仅登记本轮成功写入、编码后至少64KiB的最后一个检查点正文;小正文沿用普通读。只保留一份不可变编码字符串,不缓存解码对象、历史列表或pending writes。图的后台检查点写线程通过同一Saver按会话键登记;结束、失败或停止时清理。同会话请求意外重叠时双方禁用复用,人才/营商的键空间继续隔离。新请求、进程重启和未启用作用域的调用均从Redis完整恢复。 最终get_state保持原流程:单次Lua仍原子选择最新/指定命名空间和ID,读取当前Redis正文,以字段、长度和随机请求前缀加盐的SHA1指纹核对候选。SHA1用于缓存等值检查,非鉴权凭证;指纹仅在实际回读时首次计算,中间检查点写入不逐个计算。核对通过返回RESP整数确认标记(不会与任何存储字符串混淆),客户端复用原编码正文并正常重新解码;不符则直接返回当前完整正文。pending writes始终在同一原子快照内完整读取,包括新interrupt/error写入;Redis不可用或数据损坏仍报错,不能用缓存掩盖。保持旧v1存储、写入和TTL契约,无迁移,不跳过任何会话恢复或最终快照重建。 命中时redis_returned_bytes及redis_checkpoint_bytes统计实际返回有效载荷,整数标记按其十进制表示计1字节,均不含RESP/TCP/TLS;checkpoint_decoded_input_bytes仍统计实际解码的完整正文。因此两者不再接近是正常现象,需结合cache_hit和cache_saved_bytes解释。 F169仅本地替身验证:合成1MB正文含8条pending writes的回读有效载荷1001233→711字节,完整检查点、来源、回答与pending逐字段相同;另有小正文不启用及3MB/32写入样例。没有运行真实Redis或Lua解释器、真实模型或部署,不能继承F102的真实Lua证据当作新分支验收。服务器仍扫描字段并计算当前正文指纹,增加O(正文长度)CPU工作;客户端仍需解码完整状态。省的是重复回传,未减少Redis往返数,不能按字节比例推算耗时。 后续获准验证时比较redis_checkpoint_read.session_result_load、session_result_load、cache_hit及有效载荷,并核查Redis执行线程开销和实际网络条件。最终next/tasks/interrupts和完整来源必须继续来自相同get_state语义,不以流中的recommendation代替。 2026-10-27用户补充运行日志:正常完成请求cache_hit=1、saved=1318958、returned=48、decoded=1318959字节,pending=0;Redis客户端读19ms、完整状态加载24ms、最终本地指纹准备1ms。证明该正常命中分支已在用户运行环境执行且仍解码完整状态,补充上述开发时仅替身证据;不是将正文裁成48字节。旧样本2153/2160ms与本次差约2.13秒,但两样本非固定输入A/B,其他模型阶段也波动。总时长19.152→15.656秒、首字14.343→12.653秒,首字发生在末尾回读之前,不能算末尾优化贡献;不据此保证稳定收益或真实恢复/并发/故障/TTL与回答语义全面验收。开发侧本轮只本地解析所给日志,没有连接真实Redis或模型。