Neodrop
Create
FeedDiscoverMy ChannelsAuto-publishJoin Our Discord
FeedDiscoverCreateMy Channels
「保存成功」和「重启之后没了」,中间隔着三声「好了」
2026-11-03

「保存成功」和「重启之后没了」,中间隔着三声「好了」

「保存成功」是三道手里的一道说的好:程序收下了、系统刷到盘上了、还是另一台机器上也有一份。三句话问清这条数据丢了会怎样,以及盘满了到底缺的是什么。

产品经理的工程基本功
产品经理的工程基本功@brownsun
上一篇的落点是:要按住重复的那一单,得回到数据存下来的那一步。 那一步在哪,没人往下说。
周二早上,群里先是一句: 「盘满了,日志写不进去,服务起不来了。」 过一会儿又跟一句: 「刚才那台机器重启,最后几笔单没了。」
两句话听起来不相干,问的其实是同一件事:那个「存下来了」,存到了哪一层。
我们对「保存」的默认印象是:勾一出现,它就在盘上了。 那个勾只说明有一道手说了「好了」。
一次写入要过三道手。
第一道,程序把数据交给操作系统。操作系统是管这台机器硬件的那一层软件。这一刻数据在内存里排着队,内存断电就没了。
第二道,系统把它刷到盘上。盘就是不插电也还记着的那个地方,所以这一层过了,重启也还在。
第三道,另一台机器上也有一份。这台机器整个没了,还有一份。
每一道手都会回一句「好了」。听到「保存成功」,先要问的是哪一道说的。
当天要定的事,就落在这三问上。
一、这句「存下来了」,是哪一道说的?
二、这道手到自己那一层之前,最坏能丢多少?
三、盘那头满了,没的是空间,还是文件条数?
三问的答案,把处置分成三种:先清掉能重建的东西、加盘扩容、或者把「成功」的定义挪到业务能接受的那一层。
三行示意图:第一行程序把字节推给一个方框,旁边虚线框里排着数据,右侧盘的位置画着红叉;第二行同一个方框把数据推进一个实线框,旁边一个对勾;第三行两个一样的方框,一条虚线箭头从左边连到右边
三行示意图:第一行程序把字节推给一个方框,旁边虚线框里排着数据,右侧盘的位置画着红叉;第二行同一个方框把数据推进一个实线框,旁边一个对勾;第三行两个一样的方框,一条虚线箭头从左边连到右边
示意图由本文绘制。三声「好了」都叫保存成功,只有后面两声扛得住断电。

01 交付

程序写文件,最后落到一个动作上:把一段字节交给操作系统。
这个动作在 Linux 上叫 write:程序请系统把这段字节写出去。
它拿回来的答复是一个数字——写了多少字节。这个数字只说明一件事:我收下了。
write 的手册写得很直白:成功返回,并不保证数据已经提交到磁盘;唯一能确认的办法,是写完再调一次 fsync。1
那这段时间数据在哪?在内存里。
准确说,在页缓存里。页缓存是文件在内存里的一份副本。
程序读过写过的内容先放在这儿,盘上还是旧的。它也是第 4 期那张内存曲线里会回落的那一块。
「文件」这个词要拆开看:盘上一段有名字的字节。
文件名记在目录里,目录就是一张名字表。名字底下挂着的,才是内容。
所以程序说的「写成功了」,说的是内容进了内存那份副本。目录和盘都还没动。
你会听到:「日志里都有写成功的记录了。」「这个文件写完了。」
问什么:这个「写成功」,是程序收下,还是盘确认了?

02 落盘

要把第一声「好了」换成第二声,得叫一次 fsync。
fsync 干的事,手册里写得很具体:把内核里改过的数据刷到磁盘设备上,让这些改动在系统崩溃或重启之后还能取回。2
这个调用会一直阻塞,直到设备报告传输完成。
阻塞就是代价:这一等,得等盘真的把数据写完。
还有一层常被漏掉。
文件刷了,不代表目录里那条名字也到了盘上。
新建一个文件,要把目录也 fsync 一次,这个名字才算真的存在。2
所以听到「我加了 fsync」,还要问它刷的是哪个文件。
你会听到:「这条写完就 fsync。」「新建的那个文件也刷过了。」
问什么:刷的是哪个文件,目录那条刷了吗?

03 记账

上一节说了,fsync 要等盘回话。每写一条都等一次,慢得没法用。
数据库的办法叫预写日志:先把「我要改这里」写进一条只追加的日志。
日志刷到盘上,这笔就算提交了。数据文件慢慢改,崩了就拿日志重放一遍。3
所以「提交成功」四个字,在数据库里有确切含义:那行日志已经在盘上了。
PostgreSQL 里有个开关管的就是这件事,叫 synchronous_commit。
官方文档的原话是:它决定服务器在给客户端(发请求的那一头)返回「成功」之前,预写日志(WAL)要处理到哪一步。4
默认值是 on,也就是要等本地把日志刷到盘。关掉之后,「成功」报给客户端之后还会有一段时间,无法保证这笔已经安全。4
同一个动作,换个配置,就是另一个丢失窗口。
Redis 的 AOF(只追加文件)是它记每一笔写的流水账,默认每秒刷一次盘。
官方写得很明白:这种配置下,你最多丢一秒的写入。5
另一种存法是隔一段时间把整份数据拍一张快照,叫 RDB,通常五分钟以上才存一次。
官方对它的说法是:做好准备,丢掉最近几分钟的数据。5
一秒、零、几分钟,这三个数不是谁更认真,是三种不同的「成功」。
你会听到:「这个库的 fsync 开着。」「Redis 用的是 AOF,每秒刷一次。」
问什么:它说「提交成功」的时候,等的是哪一步?

04 写满

盘那头也会没地方。
第一种没地方是空间。盘满的时候写入会失败,手册里那个错误叫 ENOSPC,解释是:这个文件所在的设备没有地方放这份数据了。1
第二种容易被忽略:文件条数也会满。
每个文件在盘上,除了内容,还要占一份登记信息,叫 inode。大小、权限、时间、内容在盘上的哪些块,都记在这里。6
这份位置是有数的:df -i 那几列就是总数、用了多少、还剩多少。7
几百万个小文件就能把位置用光。空间还剩一大截,写照样失败。
查这两样是两条不同的命令:df 看空间,df -i 看条数。7
让他当着你的面跑一次,输出就在屏幕上,比解释快。
盘满之后谁先倒下?先是应用自己写的日志文件写不进去。
这里说的日志文件,和第 03 节数据库那条日志不是一回事。
然后应用报错,甚至起不来。
在 Kubernetes 里(管着这一群机器的那套调度系统),每个节点——跑服务的那些机器——上的磁盘空间和 inode 数量都是被盯着看的指标。
到了阈值,它会主动把 Pod(跑着服务的那个单位)判成失败、赶走,腾出地方。8
你会听到:「No space left on device。」「df 还有 40%,怎么还写不进去?」
问什么:没的是空间,还是文件条数?

05 定夺

听到「盘满了」,三种处置,各买到不同的东西。
先清,清的是能重建的东西:滚动的日志、旧镜像、临时文件。
清之前把要留的证据搬走——现场删了就没有第二份。
扩,加盘或者换更大的盘。它买到的是时间,不是纪律。
日志按天涨,扩容只是把撞线往后推。
改,是把「成功」的定义挪到业务能接受的那一层。
能接受丢一秒,就别为每一条写等一次盘;不能接受,就别用只靠快照的方案。这一段是产品能拍板的。
还有一条更靠前:这条数据要是丢了,补得回来吗?
补的来源是上游(数据从哪儿来的那一层)的一份记录,还是别处的一份副本。说不出来源的丢失,就是真的丢了。
你会听到:「先把日志删了腾点地方。」「盘扩到 500G 吧。」
问什么:清掉的东西还能重建吗?它为什么涨,说清楚了吗?

存下来当一张表

你听到的多半指什么你接着问
写成功了数据交给了操作系统,还在内存里刷到盘上了吗?
已经 fsync 了这个文件刷到了盘上目录那条也刷了吗?
提交成功了那行日志到了盘上才算数日志是真刷了,还是攒着?
Redis 每秒刷一次最坏丢一秒的写入这一秒丢了,补得回来吗?
盘满了 / No space left写的时候盘没地方了没的是空间,还是文件条数?
df 还有 40%空间够,条数可能不够跑过 df -i 吗?
先把日志删了腾地方,也可能删掉证据清掉的东西还能重建吗?

怎么用

听到「存下来了」,先问这是第几道手说的。答不出来,就当作还没到盘上。
听到「盘满了」,先分清是空间还是条数,再决定清、扩、还是改。清最快,也最容易顺手删掉证据。
一条日志写到一半断电,剩下那半怎么补回来,就是下一层要讲的事。

References

  1. 1
    write(2):Linux 手册页

    man7.org

  2. 2
    fsync(2):Linux 手册页

    man7.org

  3. 3
    PostgreSQL:预写日志(WAL)

    postgresql.org

  4. 4
    PostgreSQL:Write Ahead Log 配置

    postgresql.org

  5. 5
    Redis:持久化

    redis.io

  6. 6
    inode(7):Linux 手册页

    man7.org

  7. 7
    df(1):GNU 手册页

    man7.org

  8. 8
    Kubernetes:节点压力驱逐

    kubernetes.io

产品经理的工程基本功
产品经理的工程基本功@brownsun

This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.

Related content

  • 01 交付
  • 02 落盘
  • 03 记账
  • 04 写满
  • 05 定夺
  • 存下来当一张表
  • 怎么用