Neon pageserver Heatmap
2026/8/3 8:41:18 网站建设 项目流程

HeatMap 概述

HeatMap 是按租户维度生成的,每个租户拥有一个独立的 HeatMap。

1. HeatMap 包含哪些数据?

HeatMap 记录了单个租户内所有 Timeline 的层文件(Layer)元数据,用于指导次级位置的缓存预热。

单租户 HeatMap 结构

一个 HeatMap 本质上是一个 JSON 文件,其核心结构如下:

{ "generation": 42, "upload_period_ms": 60000, "timelines": [ { "timeline_id": "abc123-def456...", "layers": [ { "name": "00000001...Image_0000000160", "metadata": { "file_size": 33554432, ... }, "access_time": "2026-08-02T10:00:00Z", "cold": false }, { "name": "00000001...Delta_0000000150", "metadata": { "file_size": 4194304, ... }, "access_time": "2026-08-02T09:55:00Z", "cold": false }, ... ] }, { "timeline_id": "xyz789-uvw012...", "layers": [...] } ] }

关键数据结构

对应的 Rust 数据结构(简化)如下:

// pageserver/src/tenant/secondary/heatmap.rs:14-20 pub(crate) struct HeatMapTenant { pub(super) generation: Generation, // 主位置的 generation 号 pub(super) timelines: Vec<HeatMapTimeline>, // 该租户的所有 timeline pub(super) upload_period_ms: Option<u128>, // 上传周期提示 }

一个租户(Tenant)可能包含多个时间线(Timeline,例如主分支和多个开发分支),因此 HeatMap 会包含该租户下所有 Timeline 的层文件列表。

2. 有多少数据?

HeatMap 本身是元数据,其数据量远小于实际存储的层文件。

数据量估算

假设一个典型生产租户的配置:

参数典型值说明
Timeline 数量1-10主分支 + 几个活跃分支
每个 Timeline 的层文件数50-500取决于 WAL 写入量
单个层文件大小1MB-128MBL0 较小,L1/Image 较大
HeatMap JSON 大小50KB-500KB纯元数据,不含实际数据

举例说明

以一个名为orders-db的电商核心库租户为例:

  • Timeline 1(主分支,1TB 数据)
    • L1 Image Layer: 128MB × 3 个 = 384MB
    • L1 Delta Layer: 32MB × 10 个 = 320MB
    • L0 Delta Layer: 4MB × 20 个 = 80MB
    • HeatMap 条目:33 个层文件
  • Timeline 2(分支,100GB 数据)
    • 约 10 个层文件
  • HeatMap JSON 总大小:约 150KB

数据量总结

项目大小
HeatMap JSON 元数据50KB-500KB
层文件总大小(S3 中)几百 MB 到几 GB
次级缓存大小(热层)根据热度分数,通常 10-100GB

3. 生产时 HeatMap 生成何时结束?

HeatMap 的生成和上传是一个周期性、有条件的过程。

生成触发条件

由定时器驱动,周期由配置项heatmap_period决定(默认 300 秒)。

// pageserver/src/tenant/secondary/heatmap_uploader.rs:147-165 let period = match tenant.get_heatmap_period() { None => { // Heatmap 上传已禁用 return; } Some(period) => period, }; // 定时器触发:每隔 period 毫秒生成一次 state.next_upload = Some(now.checked_add(period_warmup(period)).unwrap_or(now));

生成完成与上传流程

生成过程会遍历租户的所有 Timeline,收集层文件信息,并最终决定是否上传。

// pageserver/src/tenant/secondary/heatmap_uploader.rs:380-400 for (timeline_id, timeline) in timelines { let heatmap_timeline = timeline.generate_heatmap().await; match heatmap_timeline { None => { // 某个 timeline 未就绪 → 整个 HeatMap 放弃本次生成 return Ok(UploadHeatmapOutcome::Skipped); } Some(heatmap_timeline) => { heatmap.timelines.push(heatmap_timeline); } } } // 序列化 let bytes = serde_json::to_vec(&heatmap)?; // 去重:如果没有变化,不上传 let digest = md5::compute(&bytes); if Some(&digest) == last_upload.as_ref().map(|d| &d.uploaded_digest) { return Ok(UploadHeatmapOutcome::NoChange); } // 上传到 S3 remote_storage.upload(...).await?;

生成结束的条件

条件结果
所有 Timeline 都就绪✅ 生成完成,上传 S3
任何一个 Timeline 未就绪❌ 放弃本次生成
HeatMap 内容无变化⚠️ 不上传(节省带宽)
Tenant 正在关闭❌ 取消生成

4. 多租户场景

在多租户环境中,HeatMap 的生成和管理是完全隔离的。

每个租户独立生成 HeatMap

┌─────────────────────────────────────────────────────────────┐ │ Pageserver(主位置) │ │ │ │ Tenant-1: HeatMap #1 ──→S3 (orders-tenant-heatmap.json) │ │ Tenant-2: HeatMap #2 ──→S3 (users-tenant-heatmap.json) │ │ Tenant-3: HeatMap #3 ──→S3 (products-tenant-heatmap.json)│ │ │ └─────────────────────────────────────────────────────────────┘ │ ▼ 对象存储 (S3) │ ┌───────────┼───────────┐ ▼ ▼ ▼ 次级 Tenant-1 次级 Tenant-2 次级 Tenant-3 下载 HeatMap #1 下载 HeatMap #2 下载 HeatMap #3

关键点

  • 每个租户的 HeatMap 是独立的,不存在“多租户混合 HeatMap”。
  • 次级位置按租户维度管理缓存,每个租户有独立的 HeatMap 和缓存策略。
  • Storage Controller 为每个租户单独配置 Secondary 位置。

5. 总结

问题答案
HeatMap 包含多个租户吗?❌ 否,每个租户一个独立 HeatMap
HeatMap 包含一个租户的多个 Timeline 吗?✅ 是,包含该租户所有 Timeline
HeatMap JSON 大小?50KB-500KB(元数据)
次级缓存的实际数据量?10GB-100GB(取决于热度分数和阈值)
生成何时结束?所有 Timeline 就绪 + 内容变化 + 上传成功
生成频率?默认 5 分钟一次(heatmap_period=300s)

6. 次级缓存过滤机制

次级位置不需要缓存 HeatMap 中所有时间线的所有数据。系统设计了多层过滤机制来确保只下载和缓存最可能被访问的“热”数据,从而显著减少次级位置的存储开销和网络带宽消耗。

6.1 层级别过滤:只下载“热层”

HeatMap 中的每个层文件都有一个cold布尔标志。次级位置在下载时,会通过hot_layers()方法过滤掉所有标记为冷(cold=true)的层。

// pageserver/src/tenant/secondary/heatmap.rs:86-91 impl HeatMapTimeline { // 只返回非冷层(热层) pub(crate) fn hot_layers(&self) -> impl Iterator<Item = &HeatMapLayer> { self.layers.iter().filter(|l| !l.cold) // ← 过滤掉冷层! } // 返回所有层(不常用) pub(crate) fn all_layers(&self) -> impl Iterator<Item = &HeatMapLayer> { self.layers.iter() } }

实际下载逻辑中,只遍历热层:

// pageserver/src/tenant/secondary/downloader.rs:1098 for layer in timeline.into_hot_layers() { // ← 只遍历热层 // 下载该层... }

这意味着:

  • HeatMap 中包含了冷层(cold=true)和热层(cold=false)的完整元数据。
  • 次级位置只下载热层(cold=false)。
  • 冷层被显式过滤掉,完全不占用次级缓存空间。

6.2 时间线级别过滤:deadline 超时控制

为了避免单个租户的下载过程耗时过长,影响其他租户的缓存预热,系统设置了时间线级别的超时控制。

// pageserver/src/tenant/secondary/downloader.rs:829-845 // 计算下载截止时间:2 倍上传周期 let deadline = Instant::now() + period * 2; // 遍历所有时间线 for timeline in heatmap.timelines { // 检查是否超时 if Instant::now() > deadline { return (Err(UpdateError::Restart), touched); // ← 停止处理! } // 下载该时间线的层 self.download_timeline(timeline, timeline_state, deadline, ctx).await; }

这意味着:

  • 即使 HeatMap 中包含了 10 个时间线。
  • 如果下载前 5 个时间线后已经超时。
  • 剩下的 5 个时间线将不会被处理,本次下载任务提前结束。

6.3 下载过程日志

下载过程的日志也反映了过滤机制,只统计热层数量:

// pageserver/src/tenant/secondary/downloader.rs:1236 tracing::debug!( timeline_id=%timeline_id, "Downloading layers, {} in heatmap", timeline.hot_layers().count() // ← 只打印热层数量 );

6.4 实际缓存情况示例

假设一个租户有 5 个时间线,其 HeatMap 内容如下:

┌─────────────────────────────────────────────────────────────┐ │ Tenant: orders-db │ │ Timeline 1 (主分支, 1TB 数据) │ │ ·Image Layer (LSN=800) cold=false ✓ 热层 │ │ ·L1 Delta (LSN=500-800) cold=false ✓ 热层 │ │ ·L1 Delta (LSN=200-500) cold=true ✗ 冷层 │ │ ·L0 Delta (LSN=800-1000) cold=false ✓ 热层 │ │ │ │ Timeline 2 (分支, 100GB 数据) │ │ ·Image Layer (LSN=500) cold=false ✓ 热层 │ │ ·L1 Delta (LSN=300-500) cold=true ✗ 冷层 │ │ ·L1 Delta (LSN=100-300) cold=true ✗ 冷层 │ │ │ │ Timeline 3-5 (已合并/归档) │ │ ·所有层都是 cold=true ✗ 冷层 │ └─────────────────────────────────────────────────────────────┘

次级位置实际缓存的内容为:

┌─────────────────────────────────────────────────────────────┐ │ Timeline 1: │ │ ·Image Layer (LSN=800) ←下载 │ │ ·L1 Delta (LSN=500-800) ←下载 │ │ ·L0 Delta (LSN=800-1000) ←下载 │ │ ·L1 Delta (LSN=200-500) ←不下载(冷层) │ │ │ │ Timeline 2: │ │ ·Image Layer (LSN=500) ←下载 │ │ ·L1 Delta (LSN=300-500) ←不下载(冷层) │ │ ·L1 Delta (LSN=100-300) ←不下载(冷层) │ │ │ │ Timeline 3-5: │ │ ·不下载(冷层,且可能因 deadline 跳过) │ └─────────────────────────────────────────────────────────────┘

实际缓存大小:约 200-400MB(而非全量 1TB+)。

6.5 关键设计总结

过滤层级机制效果
层级别只下载cold=false的层排除长期不访问的冷层,节省存储空间
时间线级别deadline 超时停止处理避免下载过多时间线,保证系统响应性
租户级别按租户独立处理一个租户的下载不影响其他租户

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询