本文档介绍了在 Spanner Graph 上运行算法的最佳实践。它涵盖以下主题:
算法的图表架构最佳实践
如果您在 Spanner Graph 中使用自定义 标签和
属性,我们建议您始终将 元素
键 公开为属性。这样,您就可以在算法查询的 RETURN 子句中访问这些键属性,并使用它们来标识算法为其生成输出的图表元素。
处理算法输出
本部分分享了有关从算法输出返回哪些内容以及将返回的结果保留在何处的建议。
返回哪些内容
当算法的输出签名包含图表元素时,Spanner Graph 可让您返回这些元素的任何属性。为了降低处理费用,我们建议您尽量缩短要返回的属性列表。例如,仅返回作为元素键的属性,因为键是标识元素所必需的。
保留在何处
Spanner Graph 支持将算法查询结果保留到 Cloud Storage 或保留回您发起查询的同一 Spanner Graph 实例。
保留回 Spanner 可让所有 Spanner 操作以原生方式访问算法输出。例如,您可以运行 WeaklyConnectedComponent,将 cluster_id 保留回图表。
随后,针对具有特定 cluster_id 的输入图表运行 PageRank。
Spanner 的 变更
流等功能会将新的算法
输出传播到下游系统。如果您想在 Spanner 中使用算法输出,我们建议您保留到 Spanner。
虽然 Spanner Graph 使用高效的方式将算法输出批量
保留到 Spanner,但所有写入都必须
通过同一 主 副本。如果您已有占用主副本容量的关键工作负载,不妨错开算法运行时间,以避免与关键工作负载竞争。
如果您不打算在 Spanner 中使用算法输出,或者想在将算法输出提取到主数据源之前对其进行评估,不妨考虑保留到 Cloud Storage 。请参阅支持的文件 格式。
用于扩充图表的数据建模注意事项
本部分讨论了将算法输出保留到 Spanner 时的一些数据建模注意事项。
属性与边
算法可以生成各种洞见,包括:
- 节点级指标(例如中心性得分)。这些指标可以作为节点的新属性保留。
- 成对洞见(例如两个节点之间的相似性)。这些洞见可以作为两个节点之间的新边保留,输出值作为边属性。
- 社群检测算法可识别图表中的逻辑社群,并可将一个或多个社群分配给给定节点。您可以通过为每个成员节点添加其分配的社群的 ID,将社群成员资格建模为节点的新属性。您还可以将社群成员资格建模为新的子图,并将逻辑社群存储为图中的一种新节点类型,并使用边将它们连接到成员节点。如果您想在访问节点时了解节点所属的社群,社群属性可能就足够了。 如果您想按社群导航(例如,查找与起始节点位于同一社群中的节点),社群子图可能更合适。您可以根据计划使用社群信息的方式,选择其中一种或两种方式。
预定义架构与灵活架构
在将算法输出保留回 Spanner 之前,请通过以下方式之一在架构中定义目标列或表:
- 如果您的算法用例稳定且已知,您可以提前添加其他列或表。
- 如果您正在迭代并尝试使用不同的方式提取洞见(例如,尝试不同的算法、参数、输入子图),您可能需要一种灵活的方式来保留和区分多次实验的输出,而无需为每次运行更新 Spanner 架构。在这种情况下,您可以考虑将算法生成的新属性和边存储在通用子表中。
如需将算法生成的新属性和边存储在通用子表中,请按以下步骤操作:
第 1 步:创建通用子表
-- Create `AccountAlgoProperty` as a child table of `Account`, to store all
-- properties produced by algorithms for `Account`.
-- `algo_run_id`: a unique ID for a given algorithm run.
-- `int_val`: column to store integer algorithm output.
-- `float_val`: column to store float algorithm output.
CREATE TABLE AccountAlgoProperty (
id INT64 NOT NULL,
algo_run_id STRING(200) NOT NULL,
int_val INT64,
float_val FLOAT64
) PRIMARY KEY(id, algo_run_id),
INTERLEAVE IN PARENT Account;
-- Create `AccountAlgoEdge` as a child table of `Account`, to store all
-- outgoing edges produced by algorithms for `Account`.
-- `algo_run_id`: a unique ID for a given algorithm run.
-- `to_id`: destination `Account` id.
-- `int_val`: column to store integer algorithm output.
-- `float_val`: column to store float algorithm output.
CREATE TABLE AccountAlgoEdge (
id INT64 NOT NULL,
algo_run_id STRING(200) NOT NULL,
to_id INT64 NOT NULL,
int_val INT64,
float_val FLOAT64,
CONSTRAINT FK_AccountId FOREIGN KEY (to_id) REFERENCES Account (id) NOT ENFORCED,
) PRIMARY KEY(id, algo_run_id, to_id),
INTERLEAVE IN PARENT Account;
第 2 步:将算法生成的属性和边存储为子表行,并存储生成它们的唯一 algo_run_id。
-- Store PageRank score as property in child table.
EXPORT DATA
OPTIONS (format = "CLOUD_SPANNER",
table = "AccountAlgoProperty",
write_mode = 'upsert_ignore_all' ) AS
GRAPH FinGraph
CALL PageRank(node_labels => ['Account'], edge_labels => ['Transfers'])
RETURN node.id, "page_rank_123" AS algo_run_id, score As float_val;
-- Store ShortestPath output as edge in child table.
EXPORT DATA
OPTIONS (format = "CLOUD_SPANNER",
table = "AccountAlgoEdge",
write_mode = 'upsert_ignore_all' ) AS
GRAPH FinGraph
CALL ShortestPath(
source_nodes => ARRAY {
MATCH (n:Account {id: 7})
RETURN n
},
target_nodes => ARRAY {
MATCH (n:Account {id: 16})
RETURN n
}
) YIELD source_node, target_node, path, cost
RETURN source_node.id AS id, "shortest_path_456" AS algo_run_id,
target_node.id AS to_id, PATH_LENGTH(path) AS int_val, cost AS float_val;
第 3 步:使用
GRAPH_TABLE访问算法输出
SELECT acct.id, prop.float_val AS page_rank_score
FROM GRAPH_TABLE(
FinGraph
MATCH (n:Account)
RETURN n.id
) acct JOIN AccountAlgoProperty prop ON acct.id = prop.id
AND prop.algo_run_id = 'page_rank_123';
SELECT acct.id, edge.to_id, edge.int_val AS path_len, edge.float_val AS cost
FROM GRAPH_TABLE(
FinGraph
MATCH (n:Account)
RETURN n.id
) acct JOIN AccountAlgoEdge edge ON acct.id = edge.id
AND edge.algo_run_id = 'shortest_path_456';
第 4 步:您可以选择创建使用算法生成的属性和边扩充的逻辑图表,以便更轻松地访问算法输出。
-- Create a view that aggregates non-null algorithm properties per node into
-- name-value pairs in JSON.
CREATE OR REPLACE VIEW AccountWithAlgoProperties SQL SECURITY INVOKER AS
SELECT
n.id, ANY_VALUE(n.create_time) AS create_time,
ANY_VALUE(n.is_blocked) AS is_blocked, ANY_VALUE(n.nick_name) AS nick_name,
JSON_OBJECT(
IF(COUNT(c.algo_run_id) = 0, [], ARRAY_AGG(c.algo_run_id)),
IF(COUNT(c.algo_run_id) = 0, [], ARRAY_AGG(
COALESCE(
IF (c.int_val IS NULL, NULL, TO_JSON(c.int_val)),
IF (c.float_val IS NULL, NULL, TO_JSON(c.float_val))
)))) AS algo_props
FROM Account AS n LEFT JOIN AccountAlgoProperty AS c ON n.id = c.id
GROUP BY n.id;
-- Create FinGraphAugmented with algorithm generated properties and edges.
CREATE OR REPLACE PROPERTY GRAPH FinGraphAugmented
NODE TABLES (
AccountWithAlgoProperties AS Account
KEY(id)
LABEL Account PROPERTIES(
create_time,
id,
is_blocked,
nick_name,
algo_props),
Person
)
EDGE TABLES (
PersonOwnAccount
SOURCE KEY (id) REFERENCES Person (id)
DESTINATION KEY (account_id) REFERENCES Account (id)
LABEL Owns,
AccountTransferAccount
SOURCE KEY (id) REFERENCES Account (id)
DESTINATION KEY (to_id) REFERENCES Account (id)
LABEL Transfers,
AccountAlgoEdge
SOURCE KEY (id) REFERENCES Account (id)
DESTINATION KEY (to_id) REFERENCES Account (id)
);
然后,您可以直接访问算法属性,并在图表查询中浏览算法生成的边:
-- Retrieve PageRank score property in graph query.
GRAPH FinGraphAugmented
MATCH (a:Account)
RETURN a.id, a.algo_props.page_rank_123 AS page_rank
ORDER BY page_rank DESC
LIMIT 5;
-- Navigate through ShortestPath edge in graph query.
GRAPH FinGraphAugmented
MATCH (s:Account {id: 7})-[e:AccountAlgoEdge {algo_run_id : 'shortest_path_456'}]->(d:Account)
RETURN s.id AS src, d.id AS dst, e.int_val AS path_len, e.float_val AS cost
为大型工作负载指定 machine_category
对于大多数工作负载,default machine_category 是合适的。如果您的输入
图表包含超过 10 亿个节点和超过 100 亿条边,我们建议您
为 machine_category 明确选择 large。
重复使用计算资源以实现快速迭代
如果您正在对小型数据集尝试不同的算法或不同的输入参数
以实现快速迭代,我们建议您使用
max_idle_time。
较大的 max_idle_time Spanner Graph 可让您重复使用已为更多算法工作负载预配的计算资源。这可以减少按需冷启动计算资源的开销,并降低因暂时容量不足而无法预配计算资源的风险。请注意,算法计算资源在闲置时会产生费用。
区分输出中的元素类型
如果算法输出包含多种元素类型,您可能需要在输出中添加 ELEMENT_DEFINITION_NAME 以区分它们。例如:
EXPORT DATA OPTIONS (
uri = "gs://bucket-name/page_rank.csv",
format = "csv",
overwrite = TRUE) AS
GRAPH FinGraph
CALL PageRank()
YIELD node, score
RETURN ELEMENT_DEFINITION_NAME(node) AS node_type, node.id, score;