2025 年随着两家 PostgreSQL 生态的公司 Neon 和 CrunchyData 被收购,PostgreSQL 再一次成为热门话题。除了 PostgreSQL 本体的发展,PostgreSQL 生态里有两条路为其引入更大的生态:我称之为"自下而上"的扩展方式和"自上而下"的协议兼容方式。以和 Rust 生态的结合为例,自下而上的方式主要基于 pgrx 将各种 Rust 生态库引入 PostgreSQL,代表性的如 ParadeDB;自上而下的通过模拟其协议和接口,构建出各种"类 Postgres"数据库。
GreptimeDB 从早期版本就开始适配 Postgres 协议,属于"自上而下"类别。
什么是 Postgres 协议
狭义的 Postgres 协议指用于与 Postgres 服务端通信、运行在 TCP 之上的应用层协议。这个协议即我们通过 psql 命令行工具或者 JDBC Driver 连接数据库时,在 TCP 连接上传输的应用层报文。
概括来说,这个协议主要包含了5个部分:
- 启动:包含了连接建立阶段的握手信息交换和各类认证机制
- 简单查询:基于文本的查询和响应
- 扩展查询:俗称 PreparedStatement,支持后端缓存查询语句,只传输参数等
- 拷贝:用于导入导出
- 取消:取消一个正在执行的查询
这里我们没有把数据库逻辑同步协议和流式同步协议包含在其中,尽管他们也用类似的思路和启动机制。
但实际上要获得相对完整的 Postgres 兼容性,狭义的协议是第一层,我们还需要支持广义的 PostgreSQL 协议,包括:
- 查询语言:其 SQL 方言和关键函数
- 数据类型系统:建立与 Postgres 本身类型系统的映射关系
- pg_catalog 元信息:元信息体系的支持

广义 Postgres 协议的各个层次
兼容 Postgres 协议的好处
比起自己从头设计一套新的四层协议,使用 Postgres 协议有很多好处。
经过验证,主要功能可靠。 Postgres 当前主流 3.0 版本协议已经运行了十多年,经过了充分的验证:支持 TLS,支持多种认证方式、与客户端协商。甚至这套协议完全支持流式的数据返回,只是受限于原生 Postgres 的进程模型这个机制没有被使用。
解锁一系列的编程语言连接驱动、数据库管理工具和 BI 工具。 几乎每个生态成熟的编程语言都有 Postgres 的连接驱动,只需要实现狭义的协议支持,就可以通过使用这些客户端编写 GreptimeDB 的应用程序。另外还有很多数据库访问和管理工具,从最简单的 psql 命令行,到 DBeaver 等一众 GUI 工具,到各类 BI 报表工具,也都可以直接将兼容 Postgres 协议的数据库当作 Postgres 来管理和使用。这部分工具往往要求实现更多的 pg_catalog 兼容性来使客户端能够提取元数据。
作为原生 Postgres 的 FDW 数据源进行联邦查询。 将 GreptimeDB 挂载为原生 Postgres 的外部数据源,可以直接在原生 Postgres 上发起 SQL 查询,还可以实现两个数据库的数据 JOIN。非常适合例如通过原生 Postgres 管理设备元数据,用 GreptimeDB 管理时序数据的情况。

通过 DBeaver 访问 GreptimeDB
当然,Postgres 协议也具有一些局限性:
协议面向行数据结构设计,如果数据结构本身是列式的,要返回原始数据,需要进行转换。
取消查询的机制与 Postgres 的进程模型绑定,不好用且有安全风险。
扩展查询对数据库有类型推断能力的要求。在不实际查询数据的环节,协议要求数据库可以对语句中的变量参数进行类型推断。比如 SQLite 和 DuckDB 就无法直接从语句上推断参数的类型,因而不能完整适配扩展查询协议。
如何兼容 Postgres 协议
GreptimeDB 使用笔者开发的 pgwire 库及其 DataFusion 生态来实现 Postgres 协议的兼容性。

GreptimeDB 的 Postgres 兼容性技术栈
传输协议支持
如果我们把 Postgres 协议对比为 HTTP 协议(同是四层协议),pgwire 相当于 Rust 生态中的 hyper 甚至 axum 框架,帮助用户构建兼容 Postgres 协议的服务端。用户可以选择性地实现认证、简单查询、扩展查询、拷贝等实现不同程度的 Postgres 协议兼容性。
以简单查询为例,在这个子协议中,我们甚至可以不必假设用户输入的是 SQL 查询。pgwire 提供了两个层次的 API:
- 低层 API:
SimpleQueryHandler::on_query关注消息层面的处理和语义。 - 高层 API:
SimpleQueryHandler::do_query关注数据层面的处理和语义。
对简单查询来说,协议甚至没有要求输入的查询是 SQL。
#[async_trait]
impl SimpleQueryHandler for EchoHandler {
async fn do_query<C>(&self, _client: &mut C, query: &str) ->
PgWireResult<Vec<Response>>
where
C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send + Sync,
C::Error: Debug,
PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>
{
let query = query.to_string();
let f1 = FieldInfo::new("input".into(), None, None, Type::VARCHAR,
FieldFormat::Text);
let schema = Arc::new(vec![f1]);
let data = vec![Some(query)];
let mut encoder = DataRowEncoder::new(schema.clone());
let data_row_stream = stream::iter(data).map(move |r| {
encoder.encode_field(&r)?;
Ok(encoder.take_row())
});
Ok(vec![Response::Query(QueryResponse::new(
schema,
data_row_stream,
))])
}
}通过 psql 连接:
❯ psql -h 127.0.0.1 -p 5432 -U postgres
psql (18.1, server 16.6-pgwire-0.38.2)
Type "help" for help.
postgres=# hello world;
input
--------------
hello world;
(1 row)pg_catalog 支持
pgwire 提供了网络协议层面的支持,如前文所述,进一步的兼容性还需要元信息的支持,我们就需要引入 pg-catalog。由于 pg-catalog 是一系列数据表或视图,因此需要构建在某个查询引擎上。由于 GreptimeDB 使用了 DataFusion 开源查询引擎,我们维护了 datafusion-postgres 项目,作为 pgwire 在 DataFusion 查询引擎、arrow 数据格式上的适配器。其中也包含了对 pg-catalog 的支持和 arrow 数据向 Postgres 数据转换的支持 arrow-pg。不过,由于 pg-catalog 本身功能复杂,很多表的数据和原生 Postgres 的机制深度关联,我们更关注其中主要的几个表:
pg_database: catalog 信息pg_namespace: schema 信息pg_tables: 表信息pg_class: 表信息pg_attributes: 列信息
一些数据库管理工具,如 DataGrip,在启动阶段会发送非常复杂的 pg_catalog 查询,其中还会涉及 DataFusion 并不支持的查询或 UDF,这部分适配工作需要专门进行,欢迎参与到 datafusion-postgres 的开发中来。

(笔者在 DataFusion 旧金山 meetup 分享 datafusion-postgres)
总结
我们把 Postgres 兼容性的能力实现为可复用的库,这样意味不仅 GreptimeDB 可以实现 Postgres 的协议兼容,其他任何现代的数据基础设施都可以快速适配这套协议,从而构建一个"类 Postgres"的新生态,这何尝不是一种新的"Postgres"。
除了 GreptimeDB,pgwire 还被用在 Clickhouse 收购的 PeerDB,为实时在线游戏设计的 SpacetimeDB,fly.io 的开源项目 corrosion 以及最近发布的 db9.ai 等等。在 DataFusion 上,我们也可以融合 geoarrow 和 geodatafusion 去构建 PostGIS 的兼容生态。
如果你也对构建这样一个新的 Postgres 生态感兴趣,欢迎加入相关项目的开发,或者直接用这些工具来实现自己的想法。



