SonnetDB Graph Beta:加权路径、离线算法与结果版本
SonnetDB Graph Beta:加权路径、离线算法与结果版本
SonnetDB 的 Graph Beta 提供有界加权最短路径与可恢复离线图算法;本文说明如何选择 Dijkstra、A* 或双向 Dijkstra,怎样设置预算并只在完整结果版本发布后消费 degree、PageRank 和 community 输出。

AI 主题示意图,并非实测截图。
本文按本地源码提交 dbc8178988a0293ed67d24e6f70b78b970465aa0 和 Graph 合同核查。GitHub 当前正式 Release 是 v4.0.0,但当前 Graph Beta 源码合同不是所有已发行包、远程 Server、目标硬件或长期运行的替代证据;接入前应逐项核对目标版本和发布物。
1. 加权路径先确认权重与预算
GraphReadSession.WeightedShortestPath 在创建读会话时固定一个 statement snapshot。搜索过程中发生的并发提交只会被后续 session 看见,因此同一次路径结果不会拼接两个 sequence 的邻接数据。
边权只能来自非负且有限的 Int64 或 Float64 属性;嵌入式调用也可提供 Func<GraphEdge, double>,但它不能同时与 WeightPropertyId 设置。缺失、错误类型、NaN、Infinity、负值和累加溢出都会稳定失败,而不是静默跳过坏边或捏造一条路径。
以下片段假设 graphStore 已指向含顶点 101、808 及边权属性 7 的示例图。先对目标图调用 BeginRead() 创建固定读会话,并为调用设置 30 秒取消上限:
using SonnetDB.Graphs;
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
CancellationToken cancellationToken = cancellation.Token;
using GraphReadSession readSession = graphStore.BeginRead();
GraphWeightedShortestPathOptions options =
GraphWeightedShortestPathOptions.ForProperty(7) with
{
Algorithm = GraphWeightedShortestPathAlgorithm.Dijkstra,
MaxDepth = 12,
MaxFrontier = 4_000,
MaxVisitedVertices = 50_000,
MaxExpandedEdges = 200_000,
MaxTotalWeight = 1_000,
PageSize = 128,
MaxPageBytes = 4 * 1024 * 1024,
};
GraphWeightedPath? path = readSession.WeightedShortestPath(
new GraphElementId(101),
new GraphElementId(808),
options,
cancellationToken);
if (path is not null)
{
Console.WriteLine($"cost={path.TotalWeight}, hops={path.Depth}, seq={path.SnapshotSequence}");
}
MaxDepth 是路径 hop 的语义边界,不只是循环保险丝。实现以 (vertex, depth) 保存状态,避免“成本更低但 hop 已耗尽”的状态遮蔽另一个仍可能到达目标的状态。嵌入式 null 表示在给定方向、hop 与成本约束下未找到路径,也包括端点不存在;frontier、访问顶点或展开边预算超限会抛出异常,取消不会返回半条路径。
2. 三种算法各自负责什么
| 算法 | 何时选择 | 必须保持的事实 |
|---|---|---|
| Dijkstra | 默认选择,所有权重非负 | 返回按总权重最优的路径 |
| A* | 调用方能提供可采纳且一致的启发式 | 未提供 heuristic 时退化为零启发式 Dijkstra;Core 只校验数值边界 |
| 双向 Dijkstra | 起点和目标都明确,且想减少两侧搜索 | 反向侧按实际方向读取 adjacency,并受两侧总预算约束 |
当前本地 topology quick matrix 在固定的缩小 256 vertex、960 directed edge、16x16 网格上记录了 A* 与双向 Dijkstra 的 expanded-edge/P95 局部准入。这只是该输入、机器与五次样本的局部证据;远程零启发式 A*、Couplet 真实语料、1m/10m 规模、固定目标硬件和七天 mixed workload 仍是 NOT_RUN,不应把表中的单次数字外推为产品承诺。
3. 一次路径调用应记录哪些结果
成功的 GraphWeightedPath 包含路径、TotalWeight、实际使用的算法、ExpandedVertices、ExpandedEdges 和 SnapshotSequence。这些字段比只保存“找到/没找到”更可审阅:
- 用
SnapshotSequence将路径结果与输入版本绑定; - 用 expanded counters 判断是否接近此次明确设置的资源边界;
- 对 A* 保存 heuristic 的定义、版本和调用方假设,而不是只写算法名;
- 对不可达、取消、权重异常和预算超限分别记录,避免将未知结果误标为没有路径。
远程合同还提供 POST /v1/db/{db}/graphs/{graph}/weighted-shortest-path 与 SndbGraphClient.WeightedShortestPathAsync。端点不存在时沿用既有 shortest-path 的 404 vertex_not_found 语义;网络请求在发送后结果不明时,应先只读核对 request/response 记录,不盲目将同一副作用重放。
4. 离线算法是可恢复任务,不是一次全图原子写
GraphStore.RunOfflineAlgorithms 在一个固定 source sequence 上收集输入,计算 in/out/total degree、weakly connected components、directed PageRank 和确定性 label-propagation community,并发布版本化结果。以下片段假设已从同一数据库取得 tables = db.Tables 和 graphStore。首次运行先创建结果表;续作时只打开原表,并复用原任务 ID 与选项,不重新创建表。
using SonnetDB.Graphs;
using SonnetDB.Tables;
TableSchema schema = GraphOfflineAlgorithmTable.CreateSchema("graph_algorithm_results");
tables.Create(schema); // 仅首次;恢复时跳过这一行。
TableStore table = tables.Open(schema.Name);
var request = new GraphOfflineAlgorithmRequest(
Guid.Parse("3f9a9ff9-5033-4bc6-ae67-e902401f0f09"),
new GraphOfflineAlgorithmTableOutput(table));
var options = new GraphOfflineAlgorithmOptions
{
PageSize = 256,
MaxPageBytes = 4 * 1024 * 1024,
MaxWorkUnits = 64,
MaxMemoryBytes = 64L * 1024 * 1024,
MaxPageRankIterations = 50,
MaxCommunityIterations = 50,
OutputBatchSize = 128,
};
GraphOfflineAlgorithmResult result = graphStore.RunOfflineAlgorithms(request, options);
Console.WriteLine($"phase={result.Phase}, complete={result.IsComplete}, version={result.ResultVersion}");
MaxWorkUnits 是每次调用的工作预算。若 IsComplete=false,调度器可以持久保存任务标识、等待下一次调度后用同一 request 和 options 继续调用;每轮仍须设置取消时间及最大续作次数,不能用无界忙循环。此处只展示单次调用,不声称一次调用必然处理完任意规模的图。
结果表的主键是 (operation_id, vertex_id),因此同一源图可以保留多个可追溯版本。ResultVersion 是 operationId@sequence,消费者应仅在 IsComplete=true 后把这个版本当作完整结果;中途暂停、取消或 source sequence 漂移不能被当作一套完成的 PageRank 或社区划分。
5. 恢复、spill 和发布的真实边界
任务 workspace 的 manifest.sdbgraph 使用固定二进制 header、版本、CRC32、原子替换和目录 fsync。durable checkpoint 发生在 vertex/edge 采集页、degree/components 阶段、每个完整 PageRank/community iteration 和每个 Graph/Table 发布批次之后。
取消会停在最近的 durable 边界;未完成的阶段或单次迭代会从该阶段起点重新计算。若续作时 source sequence 与 sidecar 不一致,任务抛出 GraphOfflineAlgorithmSourceChangedException,拒绝把不同图版本拼接为一个输入。输入顺序 spill,不把完整 adjacency 常驻内存;community vote 使用受预算约束的排序 run 与最多 32 路多轮 merge,而不是无界 dictionary 或无界文件句柄。
输出到 Graph properties 时,调用方必须显式分配七个互不重复的 property ID,并提供完整 unique 声明;如果多标签顶点的 unique 组合无法表达,runner 会拒绝,要求改用 Table output。Graph 和 Table 发布都是有界批次,不能宣称全图结果跨 keyspace 原子提交;外部并发更新还会由乐观 element version 阻止静默覆盖。
6. 上线前的最小检查表
- 明确选择权重 property、算法、方向、hop 与每项预算;不要用不满足可采纳/一致性的 heuristic 宣称 A* 仍然最优。
- 为每个离线任务创建稳定
operationId,保存 source sequence、result version、预算、阶段、收敛状态和 spill 信息。 - 只在
IsComplete=true后让下游读取结果;对PageRankConverged=false或CommunityConverged=false保留有界近似标记。 - 对 source changed、CRC 损坏、取消、发布冲突和未知远程结果分别交接,不自动把失败当作“可以从头重跑”。
源码测试覆盖路径正确性、方向、深度状态、异常权重、取消、预算、small randomized graph oracle、离线续作、CRC、spill 和版本化发布。本文片段按这些 API 和测试源码静态复核,未在本次写作中实际执行;这些证据不替代 Graphalytics/LDBC、固定硬件、百万/千万规模、生产恢复或七天长期负载验收。Graph 仍是 Beta。
参考
关注我们,扫码进群沟通
欢迎关注我们,扫码加入微信或 QQ 交流群,一起交流技术实践,分享使用经验和问题反馈。
微信交流二维码

QQ 交流群:IoTSharp,群号 63631741

请扫码并关注我们,进群沟通。若二维码暂时无法使用,可通过 QQ 群号 63631741 搜索加入。

浙公网安备 33010602011771号