计算节点(compute)执行 getpage 请求的完整调用栈,从上到下:
1. 从 Postgres 内核入口
text
PostgreSQL buffer manager
└─ bufmgr.c: ReadBufferExtended() / InitBufferPage()
└─ smgrread() / smgrreadv() ← 分发到 Neon 的 smgr hook
2. Neon smgr 层
pgxn/neon/pagestore_smgr.c
neon_read() (PG≤16) // 单块读取
neon_readv() (PG≥17) // 向量化读取(批量)
│
├─ 1 communicator_prefetch_pump_state()
│ └─ communicator.c:468
│ 从 TCP 接收缓冲中 drain 已到达的 prefetch 响应
│
├─ 2 communicator_prefetch_lookupv()
│ └─ communicator.c:1049
│ 在 prefetch ring buffer (hash table) 中查找是否已有缓存的页
│ 命中 → memcpy 到 buffer,返回;未命中则继续
│
├─ 3 lfc_read / lfc_readv_select()
│ └─ file_cache.c
│ 查 Local File Cache(本地磁盘缓存)
│ 命中 → memcpy,返回;未命中则继续
│
└─ 4 neon_read_at_lsn() / communicator_read_at_lsnv()
└─ communicator.c:2093
└─ 见下方详细链路
核心请求发送/接收链路
pgxn/neon/communicator.c
communicator_read_at_lsnv()
│
├─ prefetch_register_bufferv() // 注册到 ring buffer
│ └─ prefetch_do_request() // 填充 LSN、构建 NeonGetPageRequest
│ └─ page_server->send() // 发送请求
│ └─ libpagestore.c:1135
│ nm_pack_request() // 序列化(pq_send...)
│ PQputCopyData() // 发到 Pageserver TCP
│
├─ prefetch_wait_for(ring_index) // 阻塞等待响应
│ └─ prefetch_flush_requests() // 如果有未 flush 的请求先 flush
│ └─ prefetch_read(slot) // 从 Pageserver 接收响应
│ └─ page_server->receive()
│ └─ libpagestore.c:1219
│ call_PQgetCopyData() // 异步 read,含超时/断连逻辑
│ nm_unpack_response() // 反序列化
│
└─ 处理响应
├─ T_NeonGetPageResponse → memcpy(page, buffer, BLCKSZ)
├─ T_NeonErrorResponse → ereport(ERROR)
└─ prefetch_set_unused() // 释放 ring slot
Pageserver 端处理
pageserver/src/page_service.rs
PageStream handler (每连接一个线程)
└─ handle_getpage_request()
└─ tenant/timeline::get_page_at_lsn()
├─ 查 in-memory delta / image layer
├─ 查 s3 remote storage(需拉取时)
└─ WAL redo(pageserver 侧 walredo,非 compute 侧)
└─ 对旧版本 page image 应用 WAL diff
关键设计要点
-
Prefetch Ring Buffer:compute 端维护一个最多 readahead_buffer_size(默认 128)个 slot 的环形缓冲区,支持异步 pipelining。
-
LSN 语义:请求携带 (request_lsn, not_modified_since) 两个 LSN,primary 端 request_lsn=UINT64_MAX(最新),replica
端=replay_lsn。
-
三重缓存:prefetch ring → LFC(Local File Cache,本地磁盘)→ 远程 Pageserver。
-
协议:基于 libpq COPY 协议的 pagestream_v3,每个 request 带 reqid 用于匹配响应。
-
分片:get_shard_number() 按 relNode + block/stripe_size 哈希到不同 shard(Pageserver)。