3.2. 架构

3.2.1. 客户端-服务器架构
3.2.2. 逻辑数据库结构
3.2.3. 数据库对象层次结构
3.2.4. 对象标识符 (OID)
3.2.5. 物理数据库结构
3.2.6. 实例
3.2.7. 读取和写入数据库数据
3.2.8. 查询处理
3.2.9. 参考文献

3.2.1. 客户端-服务器架构

  • 服务器

    • 数据库服务器:lightdb

    • 服务器应用程序:例如 lt_initdb, lt_ctl, lt_upgrade

  • 客户端

    • 客户端接口:例如 libpq, ECPG, ltJDBC, psqlODBC, Nlightdb

    • 客户端应用程序:例如 ltsql, ltbench, lt_dump, lt_restore

  • 前端/后端协议

    • 前端=客户端,后端=服务器

    • 基于消息的协议用于前端和后端之间通过 TCP/IP 和 Unix 域套接字进行通信

  • 客户端与服务器之间的兼容性

    • ltsql 最好与相同或更旧的主要版本的服务器一起使用。它也可以与更新的主要版本的服务器一起工作,尽管反斜杠命令可能会失败。

    • 驱动程序对服务器版本无感知:始终使用最新的驱动程序版本

3.2.2. 逻辑数据库结构

  • 数据库集群是一组数据库、角色和表空间的集合。

  • 数据库集群在执行 `lt_initdb` 后最初包含一些数据库:

    • template1:除非指定了其他模板数据库,否则新数据库将从这个数据库克隆。

    • template0:template1 的原始内容的纯净副本。

    • postgres:默认由工具和用户使用的数据库。

  • 每个数据库都包含存储其本地数据库对象元数据的系统目录。

  • 数据库集群包含一些存储集群范围内全局对象元数据的共享系统目录。

    • 共享系统目录可以从每个数据库内部访问。

    • 获取共享系统目录的查询: SELECT relname FROM pg_class WHERE relisshared AND relkind = 'r';

    • 例如,pg_authid, pg_database, pg_tablespace

  • 表空间

    • pg_global ($LTDATA/global/):存储共享系统目录。

    • pg_default ($LTDATA/base/):存储 template0, template1, postgres。其他数据库的默认表空间。

    • 用户表空间:使用 CREATE TABLESPACE name LOCATION 'dir_path'; 创建。

3.2.3. 数据库对象层次结构

  • 数据库

    • 访问方法

    • 类型转换

    • 事件触发器

    • 扩展

    • 外部数据包装器

    • 外部服务器

    • 过程语言

    • 发布

    • 行级安全策略(名称必须与表中的任何其他策略不同)

    • 规则(名称必须与同一表中的任何其他规则不同)

    • 架构

      • 聚合函数

      • 排序规则

      • 转换

      • 数据类型

      • 扩展统计信息

      • 外部表

      • 函数

      • 索引

      • 运算符

      • 运算符类

      • 运算符族

      • 存储过程

      • 序列

      • 文本搜索配置

      • 文本搜索词典

      • 文本搜索解析器

      • 文本搜索模板

      • 触发器(继承其表的架构)

      • 视图

    • 订阅

    • 转换

    • 用户映射

  • 角色

  • 表空间

3.2.4. 对象标识符 (OID)

  • OID 在 Lightdb 内部用作各种系统表的主键。

    • 例如, SELECT oid, relname FROM pg_class WHERE relname = 'mytable';

  • 类型 oid 代表一个 OID。

  • oid 是一个无符号的四字节整数。

  • OID 是从单个集群范围的计数器分配的,因此它不足以提供整个数据库范围内的唯一性。

    • 在 pg_depend 和 pg_shdepend 中,特定的对象通过两个 OID(classid 和 objid)来标识。

3.2.5. 物理数据库结构

目录

  • 数据目录 ($LTDATA)

    • base/: 包含每个数据库子目录的子目录

    • global/: 包含集群范围的表,如 pg_database 的子目录

    • lt_multixact/: 包含多事务状态数据(用于共享行锁)的子目录

    • lt_subtrans/: 包含子事务状态数据的子目录

    • lt_tblspc/: 包含指向表空间的符号链接的子目录

    • lt_wal/: 包含写前日志(WAL)文件的子目录

    • lt_xact/: 包含事务提交状态数据的子目录

  • 配置文件目录(可选)

  • 表空间目录(可选)

  • WAL 目录(可选)

数据目录中的文件

  • 配置文件(lightdb.conf, lt_hba.conf, lt_ident.conf):可以存储在其他目录中

  • 控制文件(global/lt_control):存储控制信息,如集群状态、检查点日志位置、下一个 OID、下一个 XID

  • 常规关系数据文件

    • 关系是一组元组:表、索引、序列等。

    • 每个关系都有自己的文件集。

    • 每个文件由 8 KB 块组成。

    • 懒惰分配:一个新的堆表文件包含 0 个块,而一个新的 B 树索引文件包含 1 个块。

    • 有一些类型的文件(分支):主文件、FSM、VM、初始化文件

    • 主分支 (base/<database_OID>/<relation_filenode_no>)

      • 例如, "SELECT pg_relation_filepath('mytable');" 返回 base/17354/32185,其中 17354 是数据库的 OID,32185 是 mytable 的 filenode 编号

      • 存储元组数据。

    • FSM(自由空间映射)分支 (base/<database_OID>/<relation_filenode_no>_fsm)

      • 记录关系中的空闲空间。

      • 条目按树状组织,每个叶节点条目存储一个关系块中的空闲空间。

      • 可以使用 lt_freespacemappageinspect查看其内容。

    • VM(可见性映射)分支 (base/<database_OID>/<relation_filenode_no>_vm)

      • 记录:

        • 哪些页面只包含对所有活动事务都可见的元组

        • 哪些页面只包含冻结的元组

      • 每个堆关系有一个可见性映射;索引没有。

      • 每个堆页面存储两位:

        • 全部可见位:如果设置,则该页面不包含任何需要进行清理的元组。也被索引扫描用来仅使用索引元组回答查询。

        • 全部冻结位:如果设置,则页面上的所有元组已被冻结,因此清理可以跳过该页面。

      • 可以使用 lt_visibility查看其内容。

    • 初始化分支 (base/<database_OID>/<relation_filenode_no>_init)

      • 每个未记录的日志表和索引都有一个初始化分支。

      • 内容为空:表是 0 块,索引是 1 块。

      • 未记录的关系在恢复期间会被重置:初始化分支会被复制到主分支上,并且其它分支会被擦除。

  • 临时关系数据文件

    • base/<database_OID>/tBBB_FFF

      • BBB 是创建该文件的后端的后端 ID,FFF 是 filenode 编号

      • 例如:base/5/t3_16450

    • 有主分支、FSM 和 VM 分支,但没有初始化分支。

  • 大关系被分割为 1 GB 的段文件。

    • 例如:12345, 12345.1, 12345.2, ...

页面(= 块)

  • 每个页面大小为 8 KB。在构建 Lightdb 时可配置。

  • 关系具有相同的格式。

  • 内存中的内容与存储上的内容相同。

  • 每个页面存储多个称为项的数据值。在表中,一项是一个行;在索引中,一项是一个索引条目。

  • 可以使用 pageinspect查看其内容。

  • 页面布局:

    1. 页面头:24 字节

    2. 项标识符数组指向实际项:每个条目是一个(偏移量,长度)对。每项 4 字节。

    3. 空闲空间

    4. 特殊空间:对于表是 0 字节,对于索引类型(B 树、GIN 等)是不同的字节数。

表行

  • 可以使用 pageinspect查看其内容。

  • 行布局:

    1. 头:23 字节

    2. 空值位图(可选):每列 1 位

    3. 用户数据:行的各列

3.2.6. 实例

实例是一组服务器端进程、它们的本地内存以及共享内存。

进程

  • 单线程:postmaster 为每个客户端连接启动一个单独的后端进程。因此,每次 SQL 执行只使用一个 CPU 核心。并行查询、索引构建、VACUUM 等可以通过运行多个服务器进程来利用多个 CPU 核心。

  • postmaster:所有服务器进程的父进程。控制服务器启动和关闭。创建共享内存和信号量。启动其他服务器进程并回收已结束的进程。打开并监听 TCP 端口和/或 Unix 域套接字,接受连接请求,并生成客户端后端来传递这些连接请求。

  • (客户端)后端:代表客户端会话执行其请求,即执行 SQL 命令。

  • 后台进程

    • 日志记录器:通过管道捕获其他进程的所有标准错误输出,并将其写入日志文件。

    • 检查点:处理所有检查点。

    • 后台写入器:定期唤醒并将脏的共享缓冲区写入磁盘,以便其他进程在需要释放共享缓冲区读取其他页面时不必写入。

    • 启动:执行崩溃恢复和特定时间点恢复。一旦恢复完成即结束。

    • 统计信息收集器:通过 UDP 套接字接收来自其他进程的消息,累积关于服务器活动的统计信息,并将它们写入文件。这些统计信息可以通过 pg_stat... 视图查看。

    • WAL 写入器:定期唤醒并将 WAL 缓冲区写入磁盘,以减少其他进程需要写的 WAL 数据量。还确保异步提交的事务的提交记录被写入。

    • 归档器:归档 WAL 文件。

    • 自动清理启动器:当启用自动清理时始终运行。调度自动清理工作者来执行任务。

    • 自动清理工作者:根据启动器确定的数据库连接,检查系统目录选择表,并清理它们。

    • 并行工作者:执行并行查询计划的一部分。

    • WAL 接收器:在备用服务器上运行。从 WAL 发送器接收 WAL,将其存储在磁盘上,并告诉启动进程继续基于此进行恢复。

    • WAL 发送器:在主服务器上运行。向单个 WAL 接收器发送 WAL。

    • 逻辑复制启动器:在订阅者上运行。协调逻辑复制工作者的执行。

    • 逻辑复制工作者:在订阅者上运行。每个订阅的 apply 工作者从发布者的 WAL 发送器接收逻辑更改并应用它们。一个或多个表同步工作者为每个表执行初始表复制。

  • 后台工作者:运行系统提供或用户提供的代码。例如,用于并行查询和逻辑复制。

内存

  • 共享内存

    • 共享缓冲区:存储数据文件(主、FSM 和 VM 分支)的缓存副本。

    • WAL 缓冲区:事务在此处放置 WAL 记录,然后将其写入磁盘。

    • 其他各种区域:一个大的共享内存段被划分为特定用途的区域。

    • 区域的分配情况可以通过 pg_shmem_allocations 查看。

  • 本地内存

    • 工作内存:为查询操作如排序和散列分配。通过 work_mem 和 hash_mem_multiplier 参数配置。

    • 维护工作内存:为维护操作分配,如 VACUUM、CREATE INDEX 和 ALTER TABLE。通过 maintenance_work_mem 参数配置。

    • 临时缓冲区:为缓存临时表块分配。通过 temp_buffers 参数配置。

    • 其他各种区域:为特定用途分配一个内存上下文(例如,来自客户端的消息、事务、查询计划、执行状态)。每个会话可能有数百个内存上下文。

3.2.7. 读取和写入数据库数据

  • 读取:

    • 首先,在共享缓冲区中查找包含目标块的缓冲区。如果找到,则返回给请求者。

    • 如果没有找到,则从空闲缓冲区列表中分配一个缓冲区,并将目标块从数据文件读入缓冲区。

    • 如果没有空闲缓冲区,则驱逐并使用一个已用缓冲区。如果该缓冲区是脏的,则将其写入磁盘。

  • 写入

    • 找到目标共享缓冲区,修改其内容,并将更改写入WAL缓冲区。

    • 修改事务将其WAL记录从WAL缓冲区写入磁盘,包括提交的WAL记录。

    • 被修改的脏共享缓冲区由后台写入器、检查点器或其他进程刷新到磁盘。这与事务完成是异步的。

  • 任何后端都可以读取和写入共享缓冲区、WAL缓冲区、数据文件和WAL文件。与某些其他DBMS不同,写入操作不是由某个特定的后台进程独家执行的。

  • 数据库数据文件一次读取和写入一个块。没有多块I/O。

  • 某些操作绕过共享缓冲区:索引创建期间的索引写入、创建数据库、ALTER TABLE ... SET TABLESPACE

3.2.8. 查询处理

  1. 客户端连接到数据库,向服务器发送查询(SQL命令),并接收结果。

  2. 解析器首先检查查询的语法是否正确。然后,它解释查询的语义以了解引用了哪些表、视图、函数、数据类型等。

  3. 重写系统(重写器)根据系统目录 `pg_rewrite` 中存储的规则转换查询。一个例子是视图:访问视图的查询会被重写为使用基表。

  4. 查询规划器/优化器创建查询计划。

  5. 执行器执行查询计划并将结果集返回给客户端。

注释

  • 每个会话运行在一个连接到单一数据库的连接上。因此,它不能访问另一个数据库中的表。但是,一个会话可以通过像 `postgres_fdw` 这样的外部数据包装器连接到另一个数据库并创建另一个会话,从而访问那里的表。例如,一个SQL命令可以连接本地数据库的一个表和远程数据库上的另一个表。

  • 每个SQL命令基本上只使用一个CPU核心。并行查询和一些实用命令,如 `CREATE INDEX` 和 `VACUUM`,可以通过运行后台工作者来使用多个CPU核心。

3.2.9. 参考文献

Lightdb 文档