强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

AgensGraph 完全指南 / 第 03 章:架构深度解析

第 03 章:架构深度解析

3.1 总体架构

AgensGraph 在 PostgreSQL 的基础上扩展了图数据库能力,其核心思路是:复用 PostgreSQL 的存储引擎、事务管理和连接管理,在 SQL 层之上增加图查询的解析和执行层

┌────────────────────────────────────────────────────────────┐
│                    客户端连接层                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │  psql    │  │  agens   │  │  JDBC    │  │  驱动程序 │   │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘   │
│       └──────────────┴──────────────┴──────────────┘        │
└────────────────────────┬───────────────────────────────────┘
                         │
┌────────────────────────▼───────────────────────────────────┐
│                    查询解析层                                │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │  SQL 解析器  │  │ Cypher 解析器│  │Gremlin 解析器│     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
│         └──────────────────┴─────────────────┘             │
└────────────────────────┬───────────────────────────────────┘
                         │
┌────────────────────────▼───────────────────────────────────┐
│                    查询计划层                                │
│  ┌──────────────┐  ┌──────────────┐                        │
│  │ SQL 优化器   │  │ 图查询优化器 │                         │
│  └──────┬───────┘  └──────┬───────┘                        │
│         └──────────┬──────┘                                │
│            ┌───────▼───────┐                               │
│            │  统一执行引擎  │                                │
│            └───────┬───────┘                               │
└────────────────────┼──────────────────────────────────────┘
                     │
┌────────────────────▼──────────────────────────────────────┐
│                    存储引擎层                                │
│  ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐    │
│  │  堆存储     │ │  B-Tree     │ │ 图存储抽象层      │    │
│  │  (Heap)     │ │  索引       │ │ (Graph Storage)  │    │
│  └─────────────┘ └─────────────┘ └──────────────────┘    │
│  ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐    │
│  │  WAL 日志   │ │  缓冲管理   │ │  事务管理         │    │
│  └─────────────┘ └─────────────┘ └──────────────────┘    │
└───────────────────────────────────────────────────────────┘

3.1.1 组件分层

层级 组件 职责
连接层 libpq / 驱动 客户端连接管理、协议处理
解析层 SQL / Cypher / Gremlin 解析器 将查询文本转为内部语法树
重写层 查询重写器 视图展开、子查询优化、语义检查
计划层 优化器 生成最优执行计划(基于代价)
执行层 执行器 按执行计划访问数据
存储层 存储引擎 数据的物理存储和访问
事务层 事务管理器 ACID 保证、并发控制

3.2 存储引擎

3.2.1 顶点和边的物理存储

AgensGraph 将图数据映射到 PostgreSQL 的关系存储结构中:

图的逻辑模型:
  Vertex ──[Edge]──▶ Vertex

图的物理存储:
  ┌─────────────────────────────────────┐
  │         ag_vertex 表(系统表)        │
  │  id (OID) | label | properties      │
  │  ---------|-------|-----------------│
  │  100001   | Person| {name: "张三"}  │
  │  100002   | Person| {name: "李四"}  │
  └─────────────────────────────────────┘

  ┌──────────────────────────────────────────────┐
  │         ag_edge 表(系统表)                    │
  │  id | start_id | end_id | label | properties │
  │  ---|----------|--------|-------|------------│
  │  1  | 100001   | 100002 | KNOWS | {since:2020}│
  └──────────────────────────────────────────────┘

3.2.2 ID 分配策略

元素类型 ID 格式 说明
顶点 64-bit 整数 由序列(Sequence)生成,全局唯一
64-bit 整数 由序列(Sequence)生成,全局唯一

3.2.3 属性存储

属性采用 JSONB 格式存储在 properties 列中:

-- 内部存储结构(概念性表示)
-- 顶点记录
┌─────────────────────────────────────────────────┐
 id: 100001                                      
 label_id: 1 (Person)                            
 properties: {"name": "张三", "age": 30}         
└─────────────────────────────────────────────────┘

-- 边记录
┌─────────────────────────────────────────────────┐
 id: 1                                           
 label_id: 5 (KNOWS)                             
 start_id: 100001                                
 end_id: 100002                                  
 properties: {"since": 2020}                     
└─────────────────────────────────────────────────┘

3.2.4 图遍历的存储层优化

AgensGraph 在存储层为图遍历做了专门优化:

遍历路径: Alice ──KNOWS──▶ Bob ──KNOWS──▶ Carol

传统 RDBMS 方式:
  1. 全表扫描找 Alice         → O(N)
  2. 索引查找 Alice 的边      → O(log N)
  3. 通过边找到 Bob            → O(1)
  4. 索引查找 Bob 的边        → O(log N)
  5. 通过边找到 Carol          → O(1)

AgensGraph 优化:
  1. 索引查找 Alice            → O(log N)
  2. 直接访问邻接边列表         → O(k)  (k = 度数)
  3. 沿边直接定位 Bob           → O(1)
  4. 直接访问 Bob 的邻接边     → O(k)
  5. 沿边直接定位 Carol         → O(1)

3.3 查询处理流程

3.3.1 Cypher 查询的完整处理流程

一条 Cypher 查询从输入到结果输出,经历以下阶段:

Cypher 查询文本
    │
    ▼
┌───────────────────┐
│  1. 词法分析       │  将文本拆分为 Token
│     (Lexer)       │  MATCH → Token("MATCH", KEYWORD)
└───────┬───────────┘
        ▼
┌───────────────────┐
│  2. 语法分析       │  构建抽象语法树 (AST)
│     (Parser)      │  MATCH (n:Person) → AST 节点
└───────┬───────────┘
        ▼
┌───────────────────┐
│  3. 语义分析       │  验证标签存在、类型检查
│     (Analyzer)    │  解析变量绑定、作用域
└───────┬───────────┘
        ▼
┌───────────────────┐
│  4. 逻辑计划       │  生成逻辑执行计划
│     (Planner)     │  扫描 → 过滤 → 投影
└───────┬───────────┘
        ▼
┌───────────────────┐
│  5. 优化           │  基于代价的优化
│     (Optimizer)   │  选择最优的扫描方式和连接顺序
└───────┬───────────┘
        ▼
┌───────────────────┐
│  6. 物理计划       │  转换为 PostgreSQL 可执行的
│     (Executor)    │  物理操作序列
└───────┬───────────┘
        ▼
┌───────────────────┐
│  7. 执行           │  访问存储引擎、返回结果
│     (Runtime)     │
└───────────────────┘

3.3.2 查询计划分析

使用 EXPLAIN 查看查询计划:

-- 设置图路径
SET graph_path = social_network;

-- 查看 Cypher 查询计划
EXPLAIN (VERBOSE, COSTS ON)
MATCH (p:Person)-[:KNOWS]->(friend:Person)
WHERE p.name = '张三'
RETURN friend.name;

典型的执行计划输出:

QUERY PLAN
──────────────────────────────────────────────────────
GraphScan (cost=0.00..25.00 rows=10 width=64)
  → Vertex Scan on Person p (cost=0.00..10.00 rows=1)
    → Index Scan using person_name_idx (cost=0.28..8.29 rows=1)
      Filter: (p.name = '张三')
    → Edge Scan on KNOWS (cost=0.00..5.00 rows=5)
    → Vertex Scan on Person friend (cost=0.00..5.00 rows=1)

3.3.3 执行计划中的关键操作

操作 说明 性能特征
Vertex Scan 扫描顶点 可利用标签索引
Edge Scan 扫描边 可利用起始顶点索引
GraphScan 图遍历扫描 组合顶点和边扫描
Index Scan 索引扫描 O(log N)
Seq Scan 顺序扫描 O(N),大数据集时避免
Filter 条件过滤 尽早过滤减少数据量
Sort 排序 需要额外内存

3.4 与 PostgreSQL 的兼容层

3.4.1 兼容性概览

AgensGraph 与 PostgreSQL 的兼容是深度兼容,而非简单替换:

兼容性层次:
  ┌────────────────────────────────────┐
  │  协议兼容     │ libpq 协议完全兼容  │  ✅ 100%
  ├───────────────┼────────────────────┤
  │  SQL 兼容     │ 标准 SQL + PG 扩展  │  ✅ 99%+
  ├───────────────┼────────────────────┤
  │  数据类型      │ 所有 PG 数据类型    │  ✅ 100%
  ├───────────────┼────────────────────┤
  │  索引类型      │ B-Tree / GiST 等   │  ✅ 100%
  ├───────────────┼────────────────────┤
  │  扩展兼容      │ 部分 PG 扩展可用    │  ⚠️ 需测试
  ├───────────────┼────────────────────┤
  │  系统目录      │ PG 目录 + 图专用目录 │  ✅ 扩展兼容
  └───────────────┴────────────────────┘

3.4.2 系统目录扩展

AgensGraph 在 PG 系统目录之上添加了图专用的系统表:

系统表 用途 示例查询
ag_graph 存储所有图的元数据 SELECT * FROM ag_graph;
ag_label 存储标签(顶点/边类型) SELECT * FROM ag_label;
ag_vertex 顶点存储表(每图一张) 内部使用
ag_edge 边存储表(每图一张) 内部使用
-- 查看所有图
SELECT graphname, graphid FROM ag_graph;

-- 查看某图的所有标签
SELECT labname, labkind
FROM ag_label
WHERE labname NOT LIKE 'ag_%'
ORDER BY labkind;

3.4.3 SQL 与 Cypher 的执行引擎集成

-- SQL 中嵌入 Cypher 查询(概念示意)
-- AgensGraph 提供了将 Cypher 查询包装为 SQL 子查询的能力

-- 方式 1: 使用 Cypher 查询函数
SELECT * FROM cypher('social_network', $$
  MATCH (p:Person)-[:KNOWS]->(f:Person)
  RETURN p.name AS person, f.name AS friend
$$) AS (person text, friend text);

-- 方式 2: 直接在图路径下使用 Cypher
SET graph_path = social_network;
MATCH (p:Person) RETURN p.name, p.age;

3.4.4 事务共享

图操作和关系操作共享同一个事务上下文:

BEGIN;

-- SQL 操作
INSERT INTO audit_log (action, timestamp)
VALUES ('graph_update', now());

-- Cypher 操作(在同一事务中)
SET graph_path = social_network;
CREATE (:Person {name: '新用户', age: 25});

-- 两者要么同时提交,要么同时回滚
COMMIT;

3.5 连接管理

3.5.1 连接模型

AgensGraph 继承了 PostgreSQL 的进程模型(Process-based Model):

客户端连接模型:

  客户端 1 ──┐
  客户端 2 ──┼──▶ Postmaster (主进程)
  客户端 3 ──┘        │
                      ├── Backend Process 1 (服务客户端 1)
                      ├── Backend Process 2 (服务客户端 2)
                      └── Backend Process 3 (服务客户端 3)

  每个客户端连接分配一个独立的后端进程
  每个后端进程约占用 5-10 MB 内存

3.5.2 连接池配置

在生产环境中,建议使用连接池:

# 使用 PgBouncer 作为连接池
# pgbouncer.ini
[databases]
agens = host=127.0.0.1 port=5432 dbname=agens

[pgbouncer]
listen_port = 6432
listen_addr = *
auth_type = md5
auth_file = userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 50

3.6 内存架构

AgensGraph 内存布局:

┌─────────────────────────────────────────┐
│            共享内存 (Shared Memory)       │
│  ┌───────────────┐  ┌────────────────┐  │
│  │ Shared Buffer │  │    WAL Buffer  │  │
│  │   (数据缓存)   │  │   (日志缓存)   │  │
│  └───────────────┘  └────────────────┘  │
│  ┌───────────────┐  ┌────────────────┐  │
│  │  Lock Table   │  │  CLOG Buffer  │  │
│  │   (锁表)      │  │  (事务状态)    │  │
│  └───────────────┘  └────────────────┘  │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│          后端进程私有内存                  │
│  ┌───────────────┐  ┌────────────────┐  │
│  │   work_mem    │  │ maintenance   │  │
│  │  (排序/哈希)   │  │  _work_mem    │  │
│  └───────────────┘  └────────────────┘  │
│  ┌───────────────┐                      │
│  │ 图遍历上下文   │                      │
│  │ (遍历状态)    │                       │
│  └───────────────┘                      │
└─────────────────────────────────────────┘
内存区域 配置参数 作用
共享缓冲区 shared_buffers 缓存数据页,减少磁盘 I/O
WAL 缓冲区 wal_buffers WAL 日志写入缓冲
工作内存 work_mem 排序、哈希操作的内存
维护内存 maintenance_work_mem VACUUM、CREATE INDEX 等

3.7 本章小结

架构要点 说明
存储模型 图数据映射为 PG 关系表(ag_vertex / ag_edge)
属性存储 JSONB 格式
查询处理 Cypher → AST → 逻辑计划 → 物理计划 → 执行
兼容性 深度兼容 PostgreSQL(协议/SQL/数据类型/索引)
事务模型 图操作与 SQL 操作共享事务
连接模型 PG 进程模型,建议使用连接池

3.8 练习

  1. 使用 EXPLAIN 分析以下查询的执行计划,并解释各步骤:
    MATCH (p:Person)-[:KNOWS*1..3]->(fof:Person)
    WHERE p.name = '张三'
    RETURN fof.name;
    
  2. 查询 ag_graphag_label 系统表,列出所有图和标签。
  3. 测试在一个事务中混合使用 SQL INSERT 和 Cypher CREATE。

3.9 扩展阅读