ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Neon 拆分脑防护:基于 Generation Number 的 pageserver 远程存储安全机制解析

Neon 拆分脑防护:基于 Generation Number 的 pageserver 远程存储安全机制解析 Neon 拆分脑防护基于 Generation Number 的 pageserver 远程存储安全机制解析【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon导读本文以 Neon 仓库中的 RFC 文档 docs/rfcs/025-generation-numbers.md 为主体系统讲解 Neon 如何在不引入 pageserver 内置共识算法、不依赖 EC2 实例节点隔离fencing的前提下用一套由控制面control plane签发的代数号generation number机制从根本上解决 pageserver 远程存储S3的拆分脑split-brain安全问题。读完本文你将掌握generation number 如何解决节点替换、租户迁移、自动故障切换failover场景下的并发写入冲突对象键S3 key为何采用 generation 后缀而非前缀删除操作与remote_consistent_lsn推进为何必须经过 generation 校验以及该方案在当前仓库源码中的落地形态如Generation类型、持久化删除队列、/re-attach与/validate接口。背景拆分脑为什么是 pageserver 远程存储的致命问题在 Neon 的存算分离架构中pageserver 负责把 Postgres 的 WAL 落盘为 immutable 的 layer 对象并上传到 S3。问题在于S3 这类对象存储不具备原子操作与客户端隔离fencing能力——没有任何办法物理上杀死一个还在运行的旧节点。RFC 在 Motivation 一节给出了拆分脑的精确定义从远程存储视角看只要两个节点都认为同一个租户tenant挂在它们身上、并且都能向 S3 写入拆分脑就发生了。这可能是网络分区、病态的长延迟例如被挂起的 VM或者软件 bug 造成的。在 RFC 成稿时的部署模型中控制面靠一个租户同一时刻只 attach 到一个 pageserver这一保证来排除双 attach 导致的拆分脑——但这意味着如果某个 pageserver 失联系统就不敢把它上面的租户迁走因为无法安全地 detach从而阻碍了对故障的鲁棒响应。这一缺失进一步阻塞了两个把偶发拆分脑当作设计前提的重要能力无缝租户迁移seamless tenant migration自动的 pageserver 实例故障处理failover。generation number 方案正是为了让在旧节点可能仍在运行的情况下把租户 attach 到一个新节点这件事变得安全。其前置研究包括 docs/rfcs/020-pageserver-s3-coordination.md、docs/rfcs/023-the-state-of-pageserver-tenant-relocation.md 与 docs/rfcs/026-pageserver-s3-mvcc.md。需求与设计准则RFC 明确了硬性需求兼容不具备原子/隔离能力的存储后端即接受 S3 无原子操作、客户端无法被 fencing 的限制不依赖计算层的 STONITH 或节点隔离不假设可靠地杀掉一个 EC2 实例且它一定死透粒度按租户而非按pageserver划分——无缝迁移需要租户级粒度failover 也更倾向于把故障节点的负载分散给多个 peer而不是整体搬走。设计准则Design Tenets则强调不要再造一个共识系统控制面已有一致性数据库可做原子操作safekeeper 侧已有 Paxos 实现严格保证数据正确性——偶尔的可用性损失可以容忍偶发的数据丢失绝不允许。非目标本 RFC 刻意不覆盖故障检测、failover 的编排、用于快速迁移的 standby 模式、租户的有意多写者操作多写只被视为瞬态拆分脑、分片sharding。这些特性与本 RFC 的交互见 Appendix B。方案总览generation number 核心机制RFC 的 Implementation Part 1: Correctness 摘要 把整套机制浓缩为六条要点引入每租户的 generation number唯一标识租户到 pageserver 进程的一次 attachment每当控制面修改某个Project所分配的 pageserver或所分配的 pageserver 重启时generation 递增只有控制面有权递增对象键object key追加 generation 后缀——这是对抗拆分脑、支持安全多 attach 的主要机制同 node ID 多进程拆分脑安全pageserver 启动时调用控制面重新 attachre-attach从而递增所有挂载租户的 generation删除安全把 S3 上的 DELETE 推迟到删除节点已向控制面验证不存在更高 generation 的 attachment 还引用该 key之后用控制面签发 generation避免在 pageserver 内内置共识系统存储格式本身并不绑定这一选择未来可替换。一个 u32 为何够用generation 以 u32 存储在 S3 键中表现为 8 字节十六进制字符串。RFC 的估算很有意思即使按 5 年系统生命周期计算u32 的范围也允许每秒 27 次重启——比现实情况高出若干个数量级。也就是说用尽 generation 几乎不可能成为运维问题。源码中的 Generation 类型这套设计在仓库中已经落地为真实的 Rust 类型位于 libs/utils/src/generation.rs/// Tenant generations are used to provide split-brain safety and allow /// multiple pageservers to attach the same tenant concurrently. /// /// See docs/rfcs/025-generation-numbers.md for detail on how generation /// numbers are used. #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum Generation { // The None Generation is used in the metadata of layers written before generations were // introduced. A running Tenant always has a valid generation, but the layer metadata may // include None generations. None, Valid(u32), }关键设计点Generation::None专门用于向前兼容generation 机制引入之前写入的 layer 元数据没有代数后缀读取侧用None表示Generation::new(v)构造有效代数get_suffix()返回-{g:08x}形式的 8 位十六进制后缀GenerationFileSuffix的格式化实现见同文件 第 95-101 行next()从None递增到Valid(1)previous()把Valid(0)的上一代解释为None对应升级自无 generation 时代的租户——这一点直接服务于 RFC 中加载 index 时假设旧 layer 无后缀的向后兼容规则特意不实现Display只允许通过get_suffix()把代数写进文件名避免在format!()中误用代数本身见 第 131-133 行注释。layer 文件名解析也印证了这一格式在 pageserver/src/tenant/storage_layer/layer_name.rs 中layer 名形如key start-key end__LSN start-LSN end-generation8 位十六进制并且省略 generation 后缀被明确视为合法对应老数据。对象键变更为什么必须用 generation 后缀后缀 vs 前缀由对象列表演变而来RFC 的 FAQ Why a generation suffix rather than prefix 给出了简洁答案S3 只能按前缀prefix列出对象不能按后缀。在寻找远程 index的流程中pageserver 需要做tenant/timeline/index_part.json*这样的前缀列举来兜底如果 generation 放在前缀位置这类列举就无法工作。而按 generation 列举以便对某一代做 scrub的场景并不属于常规操作因此不值得为此牺牲前缀列举能力。键结构层对象与 index 对象都带后缀所有对象键layer 对象与 index 对象都以 attachment generation 作为后缀。两个 pageserver 即使因故障残留而持有相同 node ID由于启动时 re-attach 会递增 generation它们也不会写到相同对象同一租户的多 attachment如迁移期间同理每次 attachment 拥有独立代数。实际对象形态在 pageserver/src/tenant/remote_timeline_client.rs 中有完整定义index 对象的键通过generation.get_suffix()拼接见该文件 2651-2760 行附近的IndexPart相关代码测试辅助函数assert_remote_files也按{}{} suffix 的方式构造期望文件名。Index 变更与可见性语义由于键带了后缀IndexPart也必须记录 generation目前它存储键与 LSN 以重建键名扩展为同时存储 generation。RFC 估算这会让 index 文件增大每 layer 约 10 字节layer 已以字符串形式编码若未来把 JSON index 迁移为二进制格式开销会更小。RFC 的 Visibility 一节是理解整套语义的关键它区分了两类可见性对 pageserver 的对象可见性pageserver 的实际可见集合由其 LayerMap 决定而 LayerMap 从它加载的index_part.json-gen初始化。它从最近的前一代index 起步这些对象在旧代被超越前一直保持可见因为旧代避免删除。对客户端的 LSN 可见性由于 index 文件带 generation 后缀读到什么数据取决于读者处于哪一代。两个同时 attach 同一租户的 pageserver 可能各自回放 WAL、维护独立的 LayerMap、写独立的 index 文件因此可见数据不必已远程提交。一个新 attach 的 pageserver 的最高可见 LSN 甚至可能相对旧 attachment倒退——如果旧 attachment 在交接前没来得及把全部数据上传到 S3。RFC 给出了一个关键示例序列说明最近的前一代不等于墙上时间最近0001 - 0002 | - 0003PS1 在代 1 写入index_part.json-0001PS2 在代 2 读取它PS3 在代 3 读取它随后 PS2 才完成工作写出index_part.json-0002PS3 再写出index_part.json-0003。这不构成安全问题若 0002 引用了 0001 中不存在的对象0003 只是看不见它必要时重做对应工作如重新摄取 WAL 或压缩仅被 0002 引用的对象不会被未来代读取最终由 scrub 清理。删除与 remote_consistent_lsn必须经过 generation 校验三步骤删除协议写操作靠写者总是把自家 generation 写进键天然去冲突但删除更棘手如果 pageserver A 被隔离、真正活跃的是 pageserver B那么 A连删除自己写过的对象都危险——因为 B 的元数据可能仍引用这些对象。RFC 给出的解决方法是把把对象从 index 解链与真正删除对象之间插入一步generation 校验删除严格遵循顺序写出index_part.json保证后续任何元数据读者都不会再读取被解链的对象调用控制面校验我们使用的 attachment generation 仍然是最新的校验通过才删除对象——因为结合可见性规则任何更晚的 generation 都会使用第 1 步上传的index_part.json或其后继而二者都不再引用该键故该键对任何后续 attachment 都不可见。注意第 2 步只确认第 1 步写出的那个 index_part 不再引用的对象可以安全删除并发进行的其他删除需要各自的校验。若第 2 步失败对象可能泄漏——这安全但有成本见下文 scrub在正常关机与干净迁移时可通过妥善 flush 删除来避免。为避免海量控制面请求多个租户的校验合并为单次请求删除先排队再统一校验见 持久化删除队列。remote_consistent_lsn 的双值设计与推进顺序对象删除之外pageserver 还通过向 safekeeper 回传remote_consistent_lsn间接删除 WAL 数据safekeeper 据此丢弃该 LSN 以下的数据。它遵循同样约束把覆盖到 LSNL0的 index_part 上传到 S3调用控制面校验 generation 仍最新把对外通告的remote_consistent_lsn推进到L0。关键点是第 3 步通告的不是最新值而是第 1 步上传的 index_part 中所包含的值这提供了强顺序保证。为此每个 timeline 内部维护两个remote_consistent_lsn一个反映最近一次写远程存储一个反映最近一次通过 generation 校验只有后者才能对外对 safekeeper通告。控制面完全不知道remote_consistent_lsn本身它只负责校验 generation 新鲜度从而授权 pageserver 把该信息分享给 safekeeper。在源码中这一设计体现为 pageserver/src/tenant/timeline.rs 的get_remote_consistent_lsn_projected()尚未校验、可能后退的值与get_remote_consistent_lsn_visible()已校验、保证不后退的值两个接口。pageserver 的 attach 与启动流程变更Attachmentattach 请求携带 generation/v1/tenant/{tenant_id}/attach请求体新增generation字段。pageserver不持久化它——一个 generation 只在进程存活期内有效这正好呼应 RFC 开头generation 的生命周期等于 pageserver 进程或租户 attachment 中较短者的定位。寻找远程 indexGET 优先、ListObjects 兜底因为 index 文件带 generation 后缀pageserver 无法总是单次 GET 拿到最新 index事先不知道最新是哪一代。典型情况下最近写过 index 的一代是自己这一代减 1但前一个节点可能拿到代数后还没来得及写 index 就崩溃了。因此启动一个 timeline 时先 GETindex_part.json-my generation - 1若失败发一次ListObjectsv2请求列举index_part.json*按 generation 排序取最高、且自己当前代的那一个。两条规则保证了后一代写的对象永远不会对前一代可见租户绝不能加载比自己 attachment 更新的 index。RFC 也提示了可选优化让控制面记录最近一次写 index 的是哪一代以提高单次 GET 命中率或让 pageserver 拿到新代数后主动先写一版 index。启动时 re-attach扫描磁盘 删除未授权内容pageserver 启动时会调用新的控制面/re-attachAPI见 Generation API返回应挂载的租户列表及各自代数控制面在返回前递增。pageserver 仍会扫描本地磁盘但对不在 re-attach 响应中的租户直接删除本地内容——它们的缺席等价于一次隐式 detach。RFC 注明这一行为未来会随secondary/standby location概念而改变拥有本地内容的节点届时可降级为该状态并保留部分数据。这一流程在 pageserver/src/tenant/mgr.rs 中有对应实现TenantStartupMode::from_reattach_tenant把 re-attach 响应转换为启动模式响应中缺失的租户会被 detach 并删除本地文件日志信息为Detaching tenant, control plane omitted it in re-attach response若控制面返回的代数低于本地记录Control plane gave decreasing generation ...则该租户被降级为 secondary。旧 index 的清理删除旧 index 对正确性非必需但若不清理ListObjects 兜底会越来越贵。新 attachment 写出自己的 index_part 后可以异步清理发现的旧index_part.json——既可以作为 pageserver 生命周期内首次写 index 后的显式步骤也可以简单并入后台 scrub 周期执行。控制面变更与 Generation API存储与递增 generationProject表新增 generation 字段。/v1/tenant/:tenant_id/attach所需代数由控制面在把租户分配到新服务器时递增提供改变 assigned pageserver 的同一数据库事务里同时更新 generation保证原子性。在 storage controller 中对应实现位于 storage_controller/src/tenant_shard.rs第 69-74 行注释Latest generation number: next time we attach, increment this。它还包含一条防御性断言Attempted to enter attached state without a generation——试图在没有代数的情况下进入 attached 状态被视为严重 bug。/re-attach与/validate两个端点RFC 指出该 API 可直接由控制面提供也可做成独立微服务实践中为做 E2E 测试确实需要写一个迷你版。两个端点都很简单只依赖持久化、可线性化的存储如数据库/re-attach请求{node_id: u32}响应200 {tenants: [{id: TenantId, gen: u32}]}404表示未知 node_id未来可用429表示检测到抖动多个节点争夺同一 node_id 或进入重试循环未知租户直接省略。服务端查询数据库确定该 pageserver 应挂载的租户对每个租户递增 attachment generation 并返回新值。客户端响应中的租户以新代数激活本地存在但响应中未引用的内容按 detach 处理删除本地文件。RFC 还前瞻性地说明未来若改用临时 node ID此请求中的node_id会替换为某种关联 ID如 EC 实例 ID 或首次使用时写入磁盘的 UUID帮助控制面识别是否同一个存储上的同一进程。/validate请求{tenants: [{tenant: tenant id, attach_gen: gen}, ...]}响应200 {tenants: [{tenant: tenant id, status: bool}...]}未知租户省略用途让 pageserver 发现自己的 attachment 是否仍是最新。服务端只读操作比较请求中的代数与已知代数相同则status: true。客户端未收到当前代有效的响应前不得对该租户的远程数据执行任何删除。/load与/ignore的归宿由于 pageserver 改为只在启动时依据/re-attach响应挂载租户/load与/ignore原语义不再成立/load功能上等同于 attach将被移除凡用/load之处改为 attach/ignore等价于不删本地文件的 detach。Timeline 创建与删除的特殊处理timeline 的创建/销毁与普通读写不同写入方是控制面而非 safekeeper/endpoint因此需要防范两类场景租户已 attach 到 pageserver B而 pageserver A 正在处理控制面发来的 create/delete RPCpageserver A 收到创建请求后失联租户被改挂到 B创建请求重发给 B。创建靠 generation 后缀天然防错由于 timeline 远程路径内所有对象都带 generation 后缀一个慢节点在更新的代已经建好 timeline 并写入数据之后才尝试创建只会写出一个带旧代数后缀的index_part.json不会造成破坏。Timeline ID 永不重用因此无需担心 create/delete/create 循环若未来灾难恢复场景要反删除重用 ID则需向所有 pageserver 确认对该 timeline 返回 404并 flush 各自的删除队列。控制面可以安全地在创建进行中改换租户归属新 attachment 用新代数但必须检查timeline 创建成功响应对应的 pageserver generation 是否仍最新若已过期该响应不构成成功应丢弃scrubber 会清理 S3 状态或更优——向旧 attachment 发一个 timeline 删除请求。删除控制面持久化意图并无限重试租户/timeline 删除豁免generation 校验也不必走 GC/压缩 layer 删除那条队列一旦控制面发出删除命令就承诺持续重试直到完成因此允许过期 pageserver 继续删除。控制面的相应义务是删除期间必须等待真正完成状态 404pageserver 不可用时要么等待同 node_id 的替代者要么把租户 re-attach 到别处删除意图必须在发出任何 RPC 前持久化这正是仓库中持久化Operation记录、无限重试的既有机制。RFC 给出的两个示例删除中途节点重启只要返回过 202远程已写入删除标记后续任何同 node_id 的化身都会看到标记并继续删除若原节点永久丢失且无同 node_id 替代控制面必须把租户 re-attach 到别的节点。创建中途节点失联控制面看到 HTTP 超时后向租户最新 attachment 点持续重发请求直到成功过期节点若仍在执行创建写出的旧代数 index 会由清理旧 index 的同一机制最终清除。一种特殊泄漏最坏情况下最新代已完成删除含清空 timeline 路径但某个慢速/被分区的节点仍以旧代数向该路径写入——这不会被 per-timeline scrub 捕获timeline 已删除无处 attach。该场景很罕见控制面可通过迁移期间等多 attach 状态结束再处理删除进一步压缩其概率未来也可用外部工具做顶层 scrub识别控制面数据库中不存在的 tenant/timeline 路径。不安全场景坏行为基础设施下的边界RFC 明确划出安全边界上述保证只适用于 EC2 临时磁盘这类基础设施。若基础设施可能透明地重启 pageserver 而旧进程仍存活如 VM 被重新调度而未隔离旧实例存在风险控制面向节点 A 发/attachA 死亡并被替换控制面在不递增代数的情况下重试同一请求就可能出现两个物理节点用同一代数。EC2 临时存储下只要控制面不复用 node_id 就不会发生但换基础设施必须重新审视。若要彻底防护可引入node generation区分持有同一 node_id 的不同进程或干脆放弃静态 node_id、给每个 pageserver 进程签发临时 ID。持久化删除队列与 scrub优化的两翼持久化删除队列Persistent Deletion Queue写出新 index_part 到真正执行删除之间存在一个对象只在内存中被引用的窗口pageserver 非正常停止会导致对象泄漏。这带来一对矛盾——延迟/批量删除可以摊薄控制面校验与 DeleteObjects 请求的成本而立即删除可以最小化泄漏。持久化删除队列正是两全方案。RFC 强调该队列的存在理由是优化而非正确性只要遵守执行删除前必须校验 generation这一规则细节有大量灵活度。其要点作用域按 pageserver 全局而非按租户原因有四作为合并向控制面校验请求的中央点避免每个Timeline对象直接触碰控制面 API、也不必了解校验规则把队列与租户 attachment 生命周期解耦休眠不活跃租户时无需等待删除完成摊薄持久化 I/O 成本把删除合并成更少的、更大的 DeleteObjects 调用。附带好处是停用租户时无需排空其队列。删除流程识别删除需求 → 从所有引用处解链写index_part.json→ 入队每条记录tenant_id, attachment_generation, S3 key→ 批量校验并执行对一批条目调用控制面通过校验的子集执行DeleteObjects。可接受的泄漏在第 2、3 步之间崩溃会丢失对无引用对象的追踪入队条目也可能因摊薄 flush 成本而不立即持久化。这些都可接受由 scrubber 兜底。未来改进手段包括为入队条目设置持久化时限、优雅停机时主动 flush、租户 detach 时主动 flush、为每条目记录是否已过解链阶段intent/commit 双条目。可绕过队列的操作整个 timeline 的删除豁免 generation 校验任何时刻都可删该路径下所有对象但由于小 timeline 的对象数凑不满一次 DeleteObjects仍建议走删除管线最后一段与其他删除合并因此队列应暴露两个输入通道generation 感知的常规删除与可跳过校验和持久化队列的 timeline 删除快速通道。上述设计在 pageserver/src/deletion_queue.rs 及其子模块 validator.rs、list_writer.rs 中完整落地删除被先写入持久化DeletionListvalidator定期把待删条目按租户聚合成批量/validate请求只有当前代或紧邻上一代的列表才被接受执行上一代被允许是因为本地磁盘保存的删除列表能证明该盘连续服务于两代、中间没有其他节点介入——见 validator.rs 第 196-219 行注释校验失败则丢弃并记日志Dropping stale deletions for tenant ... objects may be leaked。对remote_consistent_lsn的更新同样进入该管线接受校验过期代的更新会被丢弃Dropped remote consistent LSN updates for tenant ... in stale generation。清理孤儿对象scrubbing孤儿对象 不再被运行节点或元数据引用的对象产生途径包括节点 PUT 完 layer 后、写引用它的 index_part 前崩溃过期节点相信自己仍是合法写者而持续写出一批对象pageserver 在解链与入队之间崩溃。孤儿对象功能上无害只是占用 S3 容量。清理方式做一次ListObjectsv2并与最新元数据交叉比对找出未引用对象。scrub 只由 attached pageserver 执行而非第三方进程其删除请求与普通删除一样必须通过 generation 校验——这防止过期 pageserver 误把新一代写入的对象当作陈旧对象删掉。外部 scrubber 理论可行但同样必须经过与删除队列一致的校验流程。运维影响可用性依赖引入控制面协调后三类操作产生新的依赖启动新 pageserver或重启后激活原先即使控制面不可用也能就地重启恢复服务的场景将不再成立直到控制面可用。RFC 讨论过一种折中——与控制面通信超时后以旧代数恢复服务若已持久化到磁盘但这样做会破坏代数唯一标识租户到 pageserver进程的 attachment这一不变量退化为标识机器或磁盘状态且控制面本就是必要且高可用的组件因此大概率不需要。执行已入队的删除运维上无碍——延迟删除无害唯一代价是 S3 容量。推进remote_consistent_lsn以触发 WAL 裁剪若 safekeeper 磁盘紧张且控制面长期不可用可能成为问题届时可改为让 safekeeper 一上传到 S3 就尽早删除本地段而不是等remote_consistent_lsn推进。此外控制面目前单区域运行本 RFC 新增的请求要么低频、要么不在数据路径上跨区域延迟不是大问题但确实失去了这些操作的区域隔离console 与控制面拆分的工作完成后每个区域将拥有自己的控制面本 RFC 的所有操作都可交给区域级控制面处理。RFC 还提出一个逃生舱口灾难场景下允许人工以手工选定的 generation 启动 pageserver使其独立于控制面恢复上线。分阶段滚动Rollout组件间虽有耦合但数据面大部分组件可独立于控制面先行部署初始使用静态 generationPhase 1pageserver 带特殊配置上线——一切按 generation 1 处理、attach 时不等待控制面代数、跳过删除与 remote_consistent_lsn 更新中的控制面调用点。Phase 2部署控制面变更——跟踪并递增 generation。Phase 3启用 pageserver 中依赖控制面的逻辑——启动时依赖/re-attach、处理 generation 校验请求。向后/向前兼容向后兼容很直接读 index 时元数据不含 generation 的 layer 视为无后缀路径定位 index 文件时用列举兜底路径若只有一个无后缀 index 就加载它。无需重写既有 layer——新 index 格式本就能表示无代数 layer。向前兼容需要两阶段很可能跨多个 release因为读侧代码通常会先就绪第一步部署能读懂新 index 格式与 generation 后缀、但写键时不带 generation的 pageserver第二步才部署写键带 generation的版本。因为旧 pageserver 对 generation 一无所知、读不了带代数的对象名所以必须先有读的能力、再开始写。高可用/failover 场景示例Appendix Ageneration 机制对多种 failover 模型均适用就地重启 pageserver重启后向控制面 re-attach拿到所有挂载租户的新代数并按之激活若某 attachment 其实已过期离线期间被改派他人re-attach 响应会通过不为该 attachment 递增代数来告知——这会隐式阻断该租户的删除pageserver 还应主动停止 S3 上传控制面最终会 detach 该租户。若控制面干脆未在响应中包含某租户而本地仍有其数据pageserver 删除本地数据且不加载激活。控制面可借此机制清理停机过久、租户全被迁走的节点。节点故障Case A租户改挂其他节点节点 0 失联后外部机制把其租户按分发规则迁往节点 1、2每个租户因 assigned pageserver 变化而递增代数。节点 0 仍以旧代数写 S3index_part.json-00000001与index_part.json-00000002并存旧后缀下新写的对象对系统其余部分无关紧要endpoint 已从新位置读取只是可随时清理的垃圾节点 0 无法删除任何东西——它要么连不上控制面要么删除队列处理时校验请求报错。节点故障Case B同 node_id 网络盘直接替换这是动态 VM/容器环境以网络块设备提供存储、失联即自动替换 node_id的典型场景。节点 0a 失败后生成物理上独立的新节点 00b而 0a 可能仍在运行环境不保证隔离。0b 启动时 re-attach 拿到所有租户的更高代数0a、0b 继续并行写 S3 但互不冲突后缀不同且 0a 的写入对系统不可见endpoint 只从 0b 读。Appendix B与其他特性的互通性分片键空间Sharded Keyspace本设计与分片天然契合——attachment 的工作单元从 Tenant 变为 TenantShardTenantShard 与 Tenant 一样拥有 generation租户的写入负载摄取、压缩经分片分散到多个 pageserver但每个分片仍保证同一时刻恰好一个合法写者。读副本Read Replicas此处指 S3 pageserver 状态的被动读者非 Postgres 读副本。对低于 remote persistent LSN 的历史读任何节点随时可读远程数据逻辑上不可变本 RFC 的延迟删除缓解了物理上不可变压缩导致页面实际位置移动的问题。读副本需感知代数以读取最新元数据找到后缀最大的 index_part.json可通过控制面查询或 ListObjectsv2 发现。无缝迁移Seamless Migration为做到完全无缝会短暂地有意双 attach——旧节点继续服务读等待新节点就绪。本 RFC 使双 attach 成为可能两节点可同时 attach迁移目标端代数更高旧节点能摄取与服务读但不能删除新节点的 attachment 也必须避免删除旧节点仍在使用的 layer。为此控制面的 attachment 定义需要新增状态。热备位置Warm Secondary Locations为加速节点丢失后的租户移动可投入磁盘容量让 standby 位置保留本地数据。与本 RFC 无冲突standby 不写 S3无需代数租户 re-attach 到 standby 时照常递增 attachment generation只是多了一个热缓存。临时节点 IDEphemeral Node IDs本 RFC 刻意不改变 pageserver 的标识/注册方式避免拆分脑防护与更根本的管理变更耦合。改用临时 ID 可带来额外韧性——即便出现两个同 node_id 的物理节点控制面也不可能误以同一代 attach 到两者当前依赖 EC2 保证消除该场景。由于node_id在本设计中几乎不被使用pageserver 侧几乎无需改动/re-attachAPI 届时扩展为同时下发临时 ID并接受关联标识符如 EC 实例 ID以便控制面把租户重新挂回原物理服务器。总结generation number 方案用控制面签发、随 attachment 生命周期递增、写入 S3 键后缀、删除与 LSN 推进前必须校验四个环节把拆分脑防护从依赖控制面单 attach 保证 节点隔离升级为存储格式层自洽的安全模型。它不引入新共识、不依赖节点 fencing、按租户粒度工作并且以Generation类型libs/utils/src/generation.rs、持久化删除队列pageserver/src/deletion_queue.rs与/re-attach、/validate接口storage_controller/src/http.rs 中注册于/upcall/v1/re-attach与/upcall/v1/validate等形式在仓库中完整落地。对读者而言理解这套机制的关键在于记住两条主线写入靠键后缀天然隔离删除靠控制面校验确保安全——前者解决并发写冲突后者保证旧节点永远删不掉新节点还在引用的数据。【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表