源卷中变化的 B 与 D chunk 指向有限经典 COW 异常存储里保存的原值。

LVM 快照在时间上定格一块块设备

经典 LVM 快照是在源逻辑卷上附加写时复制异常存储。lvcreate -s -L 5G -n snap vg/data 请求五 GiB 存储,实际分配还受卷组空闲容量和分配粒度约束。创建时不需要复制整份源卷,但不代表完全没有工作或必然瞬时完成。thin 快照使用另一种共享池机制,不能把经典快照的独立异常存储套到所有 LVM 快照上。 异常存储要覆盖整个保留窗口内发生变化的不同 chunk 及其元数据,包括备份复制期间的写入。突发负载可能比安静时估算更早耗尽容量。经典存储满后快照会失效,读取开始返回错误。应提前观察有效状态和增长趋势,留下扩容或结束备份的时间;只规定一个百分比告警,而没有响应能力与空闲空间,不能形成可靠恢复计划。 异常存储,不是副本 源使用 snapshot-origin 目标,snapshot 则把未变化的源 chunk 与保存的异常组合成视图。直接写快照的数据进入它自己的 COW 设备。对已保存异常的同一 chunk 反复修改,不需要为这份快照再保存一次原始内容。原文一百万次写入、十六个热块却只有十五个异常的数字,没有找回轨迹;若十六个不同 chunk 都写过,就需十六份数据异常,另加元数据。 lvs -a -o lv_name,lv_attr,origin,data_percent,lv_size 展示类型、状态和用量。swi-a-s--- 只是示意,不是所有快照固定的属性串;第五位 I 表示无效快照。告警应覆盖实际增长速率和处理时间。lvextend 需要 VG 中有可分配空间;lvchange --monitor y 只启用监控,自动扩容还依赖 dmeventd、有效扩容策略及足够容量,并不是一个开关就能保证成功。 ...

四月 10, 2028 · 1 分钟 · XB Field Notes
数据集历史线从快照分支到克隆;另一快照上的独立锁形表示用户 hold。

ZFS 快照给文件系统一个回退键

ZFS 快照通过写时复制引用保留某一时点的数据集视图,不需要把全部数据另复制一遍。只要还有引用需要旧块,它们就继续保留。但创建仍涉及元数据与同步工作,不能承诺零分配,也不能把在提示符重画之前返回当作实测保证。本次没有运行 ZFS,保留文件中也未找到原文所称实验的原始输出,所以数值与运行效果必须单独限定来源。 这样的历史适合本地恢复,但 tank/data 的快照仍位于同一个池。让池不可用的故障,也可能让全部快照不可用。这描述的是共享故障边界,不是断言每次控制器故障都会原子地毁掉所有块。独立备份或复制还需要自己的保留策略及恢复验证,不能仅因另一处存在相同名字,就宣布已经获得完整灾难恢复能力。 为一个一致的树命名 快照原子地捕获数据集的文件系统状态;递归 -r 会让后代数据集在同一时刻建立快照。不递归时,子数据集被遗漏,并不是自动晚一点另拍一份。文件系统一致不等于应用检查点:数据库缓冲、日志以及选定数据集之外的文件,仍需遵循应用支持的备份或崩溃恢复流程。必须协调所有必要部分,才能把整组状态作为可恢复对象。 tank/data@daily-2026-09-16 这样的名字可以辅助保留策略,还应结合创建时间、属性与备份记录;名字本身不是删除资格的证明。zfs list -t snapshot -o name,used,refer,creation -s creation 用于观察快照及空间统计,真正清理之前还要检查依赖和 hold。自动命名也可以被可靠管理,关键是保留规则有明确、可追溯的依据。 空间在哪里:zfs list -o space zfs list -o space tank/data 选择 NAME、AVAIL、USED、USEDSNAP、USEDDS、USEDREFRESERV 与 USEDCHILD,不包含 REFER。USED 分解为四项 used-by 类别,不能写成 REFER 加 USEDSNAP。REFER 表示数据集可访问的数据,其中也可能有共享块。单份快照的 USED 统计其独有引用,删除其他快照还会改变这个值,所以逐份相加不等于整体可回收空间。 ...

三月 21, 2028 · 2 分钟 · XB Field Notes
十二条已存客户序列生成三行查询结果,原来的较大输入存储仍然保留。

高基数标签在查询之前就增加存储序列

Prometheus 序列由指标名与完整标签集合识别。在计数器上增加客户或请求身份,即使所有面板都会立刻把这些身份求和,也仍可能创建大量序列。查询聚合改变返回结果,不会删除已经入库的原始序列。存储和写入成本在用户打开面板之前就已经产生,因此不能只看面板返回多少行来判断仪表化是否足够节制。 查询聚合减少返回行数;在生产端累计所需的路由总量才会减少导出的身份。数字示意原文的固定总量样例;本轮未重跑 Prometheus 集成。 统计实际组合,而不是孤立取值 一个新的标签值,不是在整个数据库中自动对应一条新序列。它在指标名及其他标签形成不同组合的位置上,分别构成新身份。在明确指标与目标范围内,可能取值数的乘积给出笛卡尔组合上界;只有每种组合实际出现时,它才等于观测数量。维度之间存在约束时,上界可能远大于现实。真实数量还受到目标标签与指标族形态影响,不能把理论乘积直接宣称为已经发生的序列数量。 来自少量路由模板的标签可以有边界,包含标识符和查询参数的完整 URL 往往没有。客户身份、请求编号、任意错误文本和时间戳也需要明确审视。带多个桶序列的直方图,还会进一步放大每组标签组合的影响。预算应依据真实输出和预期工作负载,而不是只看某一个维度有多少取值,也不能因为每条样本数值很小便忽略身份成本。 查询少几行,不代表少存几行 按路由求和的表达式,可以读取十二条客户级序列后只返回三个结果,但十二条输入仍然存在。如果指标契约需要的就是路由总量,仪表化可以直接维护路由级计数器,只导出三条序列。这是在源头减少输出身份,不是要求每个消费者都重复隐藏额外维度。两种方式可能显示相同总数,却具有不同的数据保留和成本含义。 入库时删除标签不等于完成上述累计。不同样本可能获得同一个身份,而重新标记不会替它们决定如何相加。应使用明确的仪表化变更,或定义清楚的记录查询;后者会新增派生序列,原始数据仍按照存储策略保留。确实需要详细关联时,可以放入日志或追踪,但它们同样需要各自的权限、保留期限与成本约束,不是完全没有代价的替代存储。 使用正确范围的清单诊断增长 TSDB 状态接口提供头部统计,其中包括按指标名汇总的序列数量。这些值有助于定位大型指标族,但不是完整成本模型。采样间隔、标签长度、身份更替、数据块处理、保留策略和存储行为都会影响资源。不能把一个小实验中减少的序列比例,直接换算成生产内存或磁盘会减少的相同比例,更不能把该比例当成容量压测结果。 当一些头部序列已经没有近期可选择样本时,即时查询计数与头部序列统计可能不同;历史存储块又是另一种范围。比较报告时应记录测量时刻和统计总体。某次发布改变标签值后,旧身份仍可能留在存储中,因此查询当前返回更少结果,不代表之前的全部成本都已经消失。需要分别观察新增速度、当前可见序列和保留历史。 每次抓取的样本上限是保护措施,不是选择性聚合。指标重新标记之后仍超过该上限时,Prometheus 会把整个抓取视为失败,并不是悄悄保留一个便宜的子集。因此,保护资源的同时也可能失去有用观测。应根据真实输出制定预算,并对抓取失败设置清楚的信号,不能把限制触发后的缺失误解成业务没有流量。 坏掉的样子 新增无边界标签可能持续增加写入与内存压力,直到抓取或查询受到影响。应先定位指标族与仪表化改动,再从源头或经过审阅的样本过滤策略阻止不必要的新身份。立即删除历史数据不是所有场景都必须采用的恢复步骤,改变保留时间也不会瞬间清除全部资源成本。处理方式应依据实际瓶颈,而不是套用统一删除命令。 更隐蔽的失败是查询聚合很成功,面板看起来很小,但原始指标仍持续增长。应同时审阅导出身份和查询输出,为新增维度说明预期取值范围、有用消费者与负责人。生产计数器速率查询还应保留正确的重置处理;本实验用于比较固定总量的即时求和,不能代替先求速率再聚合的运行监控语义。 快速验证 保存程序为 cardinality.py,执行 python3 cardinality.py。需要 Docker 与 Prometheus 3.5.1,必要时下载镜像。本地文本端点为三个路由分别导出四个合成客户身份,同时独立导出已经累计好的路由总量。真实服务器抓取这些样本,并提供查询结果与 TSDB 统计,实验不会使用任何真实客户信息。 ...

一月 10, 2028 · 2 分钟 · XB Field Notes
完成的检查点位于未提交值 99 与另一会话仍可见的值 10 之间。

checkpoint 把脏缓冲刷到磁盘

检查点为 PostgreSQL 崩溃恢复建立一个可用起点。它同步这个检查点所需的数据文件变更,并记录相应恢复信息。依赖某段日志的数据页写出之前,相关 WAL 必须先持久化。这样的顺序让事务提交不必每次都把全部修改过的表页立即刷到磁盘;发生崩溃时,再从恢复起点重放后续必要变更。 坏掉的样子 检查点不是事务提交。数据页可以包含尚未提交事务写入的版本,读者究竟能看到哪些行,仍然由可见性与事务状态决定。检查点完成,不会把这些写入变成已提交,也不会把随手复制一份正在变化的数据目录变成有效基础备份。并发活动还可能再次弄脏页面,所以不能把完成提示理解成缓冲池从此全部干净。 日志保留也有别的限制。最大 WAL 大小是与自动检查点调度有关的软目标,不是磁盘使用硬上限;归档失败或复制槽保留,都可能让日志超过这个值。最小 WAL 大小则涉及回收并预留文件供以后复用,不是另一条强制触发检查点的阈值。如果理解错这些参数,可能一边看到检查点间隔正常,一边忽略实际上还在持续增长的归档积压。 维护进度与事务可见性分开;最终值 11 有断言,检查点比较是观察。双会话运行仍待验证。 快速验证 准备一台安静的 PostgreSQL 18 可丢弃实例,开启甲、乙两个全新的 psql -X -v ON_ERROR_STOP=1 会话。显式检查点和控制文件信息使用具有相应权限的管理角色。保留正常开启的同步落盘与同步提交设置。乙先创建计数器,并保存当前检查点位置: SHOW fsync; SHOW synchronous_commit; CREATE SCHEMA checkpoint_demo; CREATE TABLE checkpoint_demo.items (id integer PRIMARY KEY,n integer); INSERT INTO checkpoint_demo.items VALUES (1,10); CREATE TABLE checkpoint_demo.baseline AS SELECT checkpoint_lsn FROM pg_control_checkpoint(); 显示的两个设置都应为开启,初始已经提交的值是十。甲在一个保持打开的事务中把它改成九十九: ...

一月 6, 2028 · 1 分钟 · XB Field Notes
一个逻辑父表分叉到两个物理月份分区,子表下方另有向右的行移动箭头。

分区把一张表拆成很多看起来还像一张的片段

声明式分区让 PostgreSQL 对外提供一张逻辑表,同时把行存放在不同分区中。父表负责写入路由,并作为统一查询入口;真正持有数据的是物理分区。它的主要收益往往来自生命周期管理和选择性访问,但选择分区时,也会影响键、查询形态和运维锁,不能只把它看作拆文件。 逻辑父表与物理子表 分区父表本身没有行存储。每个已挂载分区保存符合边界的行,可以有自己的存储参数、索引和维护活动。分区索引也类似,是各物理分区索引的集合,并不是在父表上再保存一整份相同数据。理解这一点,有助于区分统一入口与实际占用磁盘的关系。 范围、列表和哈希分区使用不同划分规则。应根据真实访问和保留要求选择键:按月保存事件可能适合范围分区,其他负载可能需要不同分布。声明式分区本身不会把这些分区自动分散到独立数据库服务器。本地分区仍共享所在数据库的资源和事务环境,不能因为表被拆开,就推断它已经实现分布式分片。 保持父表名字稳定,可以简化应用入口,但不能证明应用代码完全不用改。查询需要暴露适合的分区键限制,才可能从裁剪受益;主键与外键引用也可能需要重新设计。原本按全局唯一标识操作的调用,必须保留同样的身份语义,不能在物理拆分过程中悄悄改变其含义。 分区键不只影响路由 分区表上的唯一或主键约束必须包含全部分区键列,而且这些约束要求分区键不含表达式。各分区的独立索引,不能自行证明其他分区中不存在相同键。因此,day 与 id 的复合主键,并不保证 id 在不同日期之间仍然全局唯一。 从单表全局 id 主键迁移时,这个差别尤其重要。只是把日期加入主键,可能弱化原有身份契约。更改之前,应检查调用方、引用关系和重复防护要求。分区键不是可以完全脱离公开数据模型进行调整的纯物理参数,否则原来按 id 更新一行的操作,可能开始匹配多行。 修改分区键可以把行移动到另一个分区。目标分区必须存在并接受该行,索引和约束仍然生效。这是真实数据变更,不是免费的元数据修改。如果保留策略已经移除了目标范围,迟到的修正可能失败,即使逻辑父表仍然存在。生命周期设计应同时考虑正常新增和历史更正。 坏掉的样子 DETACH 把一张表从分区层级移除,但保留该表及其数据。通过父表查询时不再看到它,脱离后的关系仍可单独检查或导出。DROP 则会连同分区数据一起删除。脱离不等于删除,这两种操作也都不会自动生成已经验证过的归档或备份,不能因为维护命令成功就认定数据已妥善保留。 分区维护需要锁。普通 detach 需要父表上的强锁,可能等待长时间运行的操作,也会影响并发访问。PostgreSQL 提供锁行为不同的并发脱离形式,但有额外限制,例如不能放在显式事务块中执行,也不能用于包含 DEFAULT 分区的父表。并发脱离若被中断,还可能需要 FINALIZE 才能完成后续维护。应按照实际契约选择操作,不能把“元数据操作”理解为“一定不会阻塞”。 分区过多也会增加规划和内存成本,没有一个普遍适用的行数阈值能证明分区一定划算。应测量有代表性的读取和维护操作,在写入需要前创建未来分区,并明确迟到数据与保留策略如何配合。对于较小且索引合理的表,大量细碎分区可能只增加管理工作,而没有带来足够收益。 路由、复合身份、行移动与脱离成员关系不同。源文使用普通事务内脱离,不证明并发锁行为或已验证归档。 快速验证 把下面内容保存为 partition-lifecycle.sql,在可丢弃的 PostgreSQL 18 数据库中执行 psql -X -v ON_ERROR_STOP=1 -f partition-lifecycle.sql。实验使用临时分区和普通事务内 detach,最后回滚移除整个测试环境。 ...

十二月 23, 2027 · 2 分钟 · XB Field Notes
清单引用 RDB 格式的 AOF 基础文件与后续增量 AOF。原文实验重写完成时计数为 4,后加一次递增,进程重启后恢复为 5。

Redis AOF 用速度换持久性

AOF 记录 Redis 的数据变更,供启动时恢复,但选择 AOF 并不等于承诺任何已确认写入都绝不会丢失。持久性取决于 fsync 策略、实际生效的配置、底层存储,以及讨论的是进程退出、操作系统崩溃还是整块磁盘损坏。应该先明确恢复目标,再决定日志策略,不能仅凭“追加日志”这个名称推导保证。 执行、记录与确认不是同一步 Redis 先在内存数据集上执行命令,再把需要持久化的效果传播到 AOF。它不是“每条命令必须先落盘才能修改内存”的执行模型。使用 appendfsync always 时,Redis 在发送回复前同步追加的数据;一起处理的命令可以共用一次写入和 fsync,并不必然每个客户端命令都对应一个独立系统调用。 执行成功、完成同步、客户端收到回复仍是不同的时刻。连接在回复到达前断开,客户端不能据此判断操作没有发生,也不能因为配置了 always 就放心重复执行扣减或计数操作。持久化协议并没有替业务提供重试去重。 AOF 是用于重建状态的表示,不是永久保存所有原始请求的审计流水。Redis 可以转换用于持久化的命令,重写也只保留重建当前数据所需的信息,而非完整历史。因此,已经删除的值并不保证还能从重写后的文件中找回来。 按确认语义选择同步策略 appendfsync everysec 大约每秒安排同步,通常以约一秒的写入暴露窗口换取性能,但不能把它当成任意存储阻塞下都成立的严格丢失上界。appendfsync no 则把刷盘时机交给操作系统;它表示 Redis 不主动请求 fsync,不表示数据永远不会落到磁盘。这两种策略下,一条成功回复都不等于该写入已经进入稳定存储。 appendfsync always 给出更强的本机写入确认约定,同时增加同步开销,影响延迟与吞吐。它仍不能保护磁盘整体丢失、存储栈失信或应用主动误删。还要检查 no-appendfsync-on-rewrite:如果将其打开,后台持久化工作期间会暂停通常的 fsync,即使主要策略写着 always,实际保证也会变弱。检查有效配置比只检查某一个配置项更重要。 重写产物是一组文件 Redis 7 起,AOF 使用 manifest 清单、基础文件和增量文件。启用 aof-use-rdb-preamble 时,基础文件采用 RDB 格式,其后的修改放在增量 AOF 中。这个基础文件属于 AOF 自己的文件集合,不是另外定期生成的 dump.rdb,二者不能因为扩展名相似就混为一谈。 ...

十一月 17, 2027 · 3 分钟 · XB Field Notes
同一文件系统内,快照根与源根指向共享未改 extent,并在分岔后分别指向旧数据与新数据。

Btrfs 快照是写时复制,不是复制

Btrfs 快照是带有源初始内容的新子卷,通过共享已有文件 extent 保留视图,而不是逐块复制全部数据。创建会复制根部元数据,也可能等待源中的脏数据刷出,因此不能承诺无论大小都在毫秒内完成。同一 extent 还可能同时被多个快照或 reflink 引用,不存在固定只能引用两次的规则。共享数据与完全没有创建成本,是不同的判断。 快照让本地恢复点较为经济,但成本包含元数据以及后续分岔保留下来的旧块。它与源共用整个文件系统的存储池,而文件系统可能跨多块设备,并不一定只在一块磁盘上。快照数量持续增加时,已删除或覆盖的数据仍可能被保留。即使设置为只读,同一文件系统内的快照也不是独立备份,不能把版本数量当作故障隔离程度。 共享 extent,而不是复制数据块 快照是有独立根的子卷。示例 btrfs subvolume snapshot -r "$src" "$snap" 请求只读快照,普通快照则可以写入。只读标志限制通过这个子卷进行的普通修改,不防管理员删除或底层损坏。快照不是递归操作:嵌套子卷在其中只是空的占位目录,其内部文件不会自动包含进来。恢复计划必须另行处理这些子卷,不能只检查顶层目录是否存在。 共享 extent 被修改时,写时复制为其余引用保留旧内容。实际分配受到 extent 布局、压缩、元数据和后续快照影响,不能简单等同于应用写了多少字节。原文中的 APFS 克隆数字不是 Btrfs 测量,本次也没有找到其原始轨迹;不能用它们证明创建零成本,或同一范围第二次覆盖时 Btrfs 一定不再分配。跨文件系统类比只能解释思路,不能替代本系统验证。 空间到底去了哪里 btrfs filesystem du 通过 FIEMAP 报告文件 extent 的 Total、Exclusive 和 Set shared。每个参数各自定义递归扫描的文件集合,Set shared 统计该集合引用的共享范围并处理重叠,并非只统计与其他命令行参数之间的共享。compsize 显示面向压缩的 Disk Usage、Uncompressed 与 Referenced,不是相同三列。qgroup 则要求先启用配额并保持统计一致,不能把缺失统计解释成零占用。 ...

十一月 14, 2027 · 1 分钟 · XB Field Notes
从旧到新的三行依次为 ABC、访问 A 后的 BCA,以及加入 D 后的 CAD;被淘汰的是 B。

LRU 缓存淘汰最久未用的条目

LRU 按最近使用顺序选择淘汰对象,而不是按历史热度、存放年龄或业务重要程度选择。有界缓存需要腾出空间时,精确的最近最少使用策略会移除最后一次有效访问发生得最早的条目。它利用的是时间局部性,既不保证命中有价值的数据,也不保证留下来的数据仍然新鲜。 沿访问顺序找出淘汰对象 假设缓存只能存三个条目,并按从旧到新的方向列出。依次加载 A、B、C 后,顺序为 A、B、C;再次读取 A,顺序变成 B、C、A;此时加载 D,就淘汰 B,留下 C、A、D。普通字典即使保留插入顺序,也不会因为某个条目被读取就自动调整它的位置。 实现必须明确哪些操作算作一次访问。常见精确方案把查找表与双向链表结合起来,以较低成本完成查找、移到最近端和从最旧端移除。维护顺序需要额外元数据,也让缓存命中变成状态更新;并发实现还必须协调这些更新,不能把读取缓存理解成永远没有写入开销。 精确顺序与采样近似不是同一契约 Redis 使用近似 LRU:抽取候选并维护较好的淘汰候选池,而不是为每个键保存完整的精确访问顺序。采样数量可以配置,更多采样用额外计算换取更接近精确 LRU 的结果。它不承诺下面小模型的逐个淘汰顺序,也不能由此推导所有淘汰操作都有固定耗时。 候选资格同样重要。Redis 的 allkeys-lru 可以淘汰任意键,volatile-lru 则只考虑带过期信息的键。设置了有效期,并不保证键在截止时间之前不会被淘汰;如果没有符合条件的带过期时间的键,volatile 策略在内存压力处理上会表现得像 noeviction。 容量约束不是新鲜度约束 限制三个条目,不等于限制三千字节。不同键、值和维护结构占用不同空间,按数量限制的缓存仍可能持有很大的对象。Python 的 lru_cache 会持有参数与返回值的引用,直到条目被淘汰或缓存被清空。选择容量单位时,应对应真正需要控制的资源,而不是只看条目数是否稳定。 最近访问也不能说明源数据有没有变化。频繁读取一个过时答案,反而可能让它一直留在缓存中。有效期、显式失效和版本化键分别处理新鲜度问题;淘汰规则既不是一致性协议,也不是备份策略,不能因为缓存命中就跳过业务对数据有效性的判断。 坏掉的样子 一次扫描大量只用一遍的条目,可能挤掉本来有用的工作集。容量为三时循环访问四个不同键,即使这些键都在被反复请求,也可能每次都未命中。LRU 不会因为过去访问次数很多就永久保留一个键。LFU、插入前的准入策略或调整容量可能适合某些负载,但不能仅凭“读多”就自动选出正确方案。 应在有代表性的时间段一起观察命中、未命中、淘汰、保留字节数以及未命中的代价。很高的命中率也可能掩盖少量特别昂贵的回源;单看淘汰数量,不知道被移除的数据是否有价值。修改策略前,先还原实际访问轨迹,再解释缓存为什么做出了这些选择。 缓存命中还必须允许安全复用。带副作用的函数,或每次调用都必须创建独立可变对象的函数,不适合直接做记忆化。Python 的 lru_cache 能保持多线程下内部结构一致,但同时发生的未命中仍可能让被包装函数执行多次。线程安全不等于抑制同一项工作被重复执行。 访问 A 后,淘汰对象变成 B。依据原文绘制机制;模型值与原文报告的观察不代表本次运行测试。 快速验证 将代码保存为 lru_check.py,用 Python 3.10 以上版本执行 python3 lru_check.py。实验已在 Python 3.14.4 上运行,只使用标准库:OrderedDict 展示精确访问顺序,真正的 functools 装饰器另行验证记忆化行为。不启动 Redis,不发送网络请求,也不写文件。 ...

十一月 12, 2027 · 2 分钟 · XB Field Notes