强曰为道

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

第 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_buffersWAL 日志写入缓冲
工作内存work_mem排序、哈希操作的内存
维护内存maintenance_work_memVACUUM、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 扩展阅读