fio压测打穿数据库主备与备份:一次信创存储验收事故复盘
上线前做信创硬件验收是很多单位必经的一道流程。就在上个季度我经手的一个国产化项目差点因为一次常规的fio压测直接翻车主备库两节点先后告警核心业务表空间出现坏块当晚的备份任务也一并失败而且备份集直接无法用于恢复。事后复盘整个链路问题的源头并不是硬件、不是数据库参数、也不是备份脚本而是一条干干净净的fio写测试命令。这篇就把整个故障从发生、定位到恢复的完整过程写出来重点拆解为什么一个磁盘压测工具能同时干掉主库、备库和备份以及遇到类似场景时应该怎么止血、怎么重建、怎么做后续防护。1. 故障初现一个验收测试把主备库和备份一起打穿1.1 第一波告警备库日志应用中断主库出现IO异常那天下午先是收到了zabbix的告警备库的日志应用进程状态异常主备同步延迟从0一路飙到了几千。因为当时正好在做信创设备的存储性能验收现场有厂商的人正在跑压测第一反应是磁盘阵列的I/O能力被打满导致日志传输跟不上。然后我们登录数据库主机查看状态备库的MRP进程已经退出尝试手动recover也起不来主库侧开始间歇性报出物理读错误错误指向的正是核心业务表空间的几个数据文件。到这里才意识到问题不是简单的压测导致性能下降。1.2 第二波打击备份任务失败备份集文件字节数异常紧接着是当晚备份作业的告警。因为主备同步已经中断我们原计划先做一次全量备份保住数据底线结果备份日志显示某个数据文件读取失败备份集被标记为不可用。更麻烦的是检查备份目录时发现归档日志备份目录里面混入了一个超大文件字节大小和fio的测试文件完全吻合直接覆盖了原有的归档备份目录结构和部分历史备份文件。那一刻就明白了这次压测不只是把磁盘I/O打满那么简单它是把备份目录所在的存储空间当成测试目标直接写了一遍。1.3 初步定性不是普通性能事故是物理层面的数据文件损坏当数据库层面的坏块提示、主备同步中断、备份目录文件被覆盖这三件事同时出现在一个时间点基本上可以定性为物理介质层面的人为破坏。此时需要立刻停止所有对存储的写操作禁止继续压测禁止做任何可能触发数据库写入的运维操作。先把现场保护住再把时间线梳理清楚这是后面所有恢复工作的前提。2. 定位过程顺着时间线把fio的作案痕迹一条条找出来2.1 数据库告警日志与fio运行时段的精确对照登录数据库主机后第一件事不是看参数、不是启进程而是把数据库告警日志、系统dmesg、以及压测任务的开始结束时间拉到一起做时间线比对。我们这边的情况是fio任务从下午14:37开始14:52的时候主库的归档日志开始写不进去15:08备库日志应用中断15:47备份任务启动后立即报错。时间线一出几乎可以锁定压测是源头——因为14:37之前数据库各项状态完全正常所有异常都发生在fio运行的窗口内。这个步骤强烈建议用表格记录后面写事故报告和追责时也用得上。2.2 现场证据fio任务残留的job文件与被覆盖的备份目录压测的fio进程虽然已经被厂商的人结束但是工作目录里面留下了job文件。打开一看参数是常见的4k随机写测试direct1ioenginelibaiorwrandwritebs4ksize500Gfilename没有指向普通测试文件而是直接指向了整个块设备的裸设备路径。这意味着fio的每次写I/O都是绕过文件系统、绕过数据库的块缓存直接落到磁盘块上数据库压根不知道哪些块已经被覆盖了。再看备份目录归档日志目录下多出了一个大小4.8G的测试文件是fio的另一个测试任务把输出路径误写到了备份挂载点下面。所以主库、备库、备份三个方面同时出问题其实是两个变体的fio命令分别干了坏事。2.3 数据库坏块检测哪些数据文件被物理覆盖了在锁定方向之后需要确认损坏范围才能决定是直接做介质恢复还是从备库抢救数据还是只能走不完全恢复。我们用数据库自带的坏块检测工具对全库数据文件做了一遍物理一致性校验表空间级别的dbv或者类似工具同时系统层面用blktrace回放了fio写入时段的I/O轨迹把所有被写入的磁盘块和数据库文件占用块做了交集比对。最终确认主库有3个数据文件出现了物理坏块位置集中在核心业务表所在的表空间备库的损坏范围略轻但也有2个数据文件受损。备份目录那边因为整个归档备份路径被覆盖只能靠磁盘阵列的快照和之前的本地备份补救。2.4 结论为什么正常的压测命令会造成这么大的破坏从厂商角度看fio是存储性能测试的标准工具命令参数也完全符合测试规范甚至direct和libaio的组合是压测时应有的做法目的就是压出真实I/O能力。但问题恰恰出在这里压测目标是数据库主机上挂载的存储盘而这个盘同时承载了数据文件、归档日志和备份目录fio完全有能力在毫秒级别内把文件系统元数据和数据库数据块覆盖掉。加上direct1之后写入内容不会先落到page cache而是直接写块设备数据库和文件系统连缓冲一下的机会都没有。最后的结果就是一次标准操作直接打穿了三层防线。3. 原理拆解fio写盘为什么会精准命中数据库数据块3.1 direct与libaio组合的破坏力来源fio压测中direct1表示绕过操作系统page cachelibaio表示使用Linux原生异步I/O。持久化层来看每次写请求都会以块设备为目标等价于在应用层直接对磁盘做写操作。对于数据库来说这是一个很严重的问题数据库依赖文件系统和块设备驱动来保证数据块的完整性和一致性但在裸设备路径下fio写入了随机的4k数据块数据库完全不知道这些块会被改写成什么。数据库进程如果此时恰好需要读取被覆盖的块轻则报错重则实例崩溃。3.2 随机写4k块为什么能破坏表空间结构数据库表空间内部有统一的数据块大小通常为8k或者16k如果fio以4k粒度对表空间数据文件所在的裸设备做随机写会产生一个极其恶劣的效果一个数据库块可能被拆成两个半块一半是fio写入的随机内容另一半还是原来的数据内容整个块的校验直接失败。这种半块覆盖在文件系统层看大小没问题在数据库层就是生成了坏块。更麻烦的是这种损坏往往不是连续的坏块散落在多个数据文件中恢复的时候很难通过跳过坏块的方式完美还原。3.3 主备同步中断的连锁反应主库数据文件损坏之后正常的运行过程可能没有立刻宕机但日志传输和归档操作会陆续踩到损坏区域。redo日志的归档写不进去传输到备库的日志序列就产生了缺口备库在应用日志时发现需要的日志文件有问题只能挂起MRP进程等待人工介入。此时如果DBA没有及时发现问题主库的日志堆积越来越大备库gap越来越大主备切换的可行性随之归零。等到想切备库顶上业务的时候才发现备库的数据文件也被坏了——等于两条路都断了。3.4 备份为何成为牺牲品而非救命稻草正常情况下备份是最后一道防线。但这次备份目录和数据库主机共享了同一个挂载点fio任务又把输出文件名直接写在了备份目录下面。备份文件是顺序写的fio是随机写覆盖两者共存于同一个目录时先被覆盖的就是某个历史归档备份集。当晚备份任务启动后由于备份集所在路径下部分文件被替换成fio生成的随机内容备份校验直接失败已经生成的备份集也被标记为不可用。等到我们想用前几天做的全量备份来恢复时发现那个全量备份在今天的压测中已经被覆盖得残缺不全了。4. 恢复实战没有完好全备时的主备重建方案4.1 先盘点还能用的资源磁盘快照和未损坏的归档恢复的第一原则是不要一上来就重启数据库或重做系统先盘点所有还完好的数据资源。当时我们手里有的东西包括故障发生前几小时存储阵列上的LUN快照刚好能覆盖到故障点之前的数据文件状态、备库主机上未受影响的归档日志、主库本地的在线日志和部分残留归档以及一份7天前的全量备份放在外部存储上没有被这次的压测波及。这里面最关键的是阵列快照它能最大程度还原故障前的数据文件现场。相比之下7天前的备份太旧丢失的业务数据过多尽量不作为首选恢复源。4.2 用存储快照恢复主库并做一致性校验接下来操作顺序是先将阵列上的快照克隆出来挂载到一台备用主机上对主库数据文件做一致性校验确认这个快照点之后没有产生新的写损坏。校验通过之后把快照卷切换给主库主机启动数据库到mount状态应用故障点之后的归档日志进行前滚恢复。恢复过程中遇到个别坏块或者归档缺失的情况选择跳过并记录因为我们的目标是让数据库能够重新对外提供服务至于个别游离块可以通过后续的应用层校验和业务逻辑核对来兜底。4.3 重建备库的两种方式和取舍主库恢复拉起之后备库的重建有两条路一是直接从主库做新的全量备份再恢复到备库优点是数据完全一致、过程简单缺点是需要较长的备份和恢复窗口期间会在恢复主机上产生较大的I/O压力而且此时主库还在承接业务备份任务可能再次触发性能瓶颈二是利用备库主机上残存的未损坏数据文件加上主库的归档日志做一个增量恢复把备库回退到故障前的主备一致性点附近然后从那个点开始补齐归档。经过评估我们选的第二种因为备库主机上的数据文件损坏范围可控归档日志保存得也相对完整增量恢复能够在半小时内把备库拉起来业务侧可以尽快恢复容灾能力。4.4 备份系统的重建与验证备份这个环节不能再抱侥幸心理。备份目录移到独立的存储空间之后我们重新做了全量备份并且增加了备份集字节数校验。更重要的是把备份文件的存储路径和数据库数据文件路径、压测工具的测试路径彻底物理隔离不能再出现同一个挂载点既放数据文件又放备份还被fio当测试目标的情况。备份验证测试也做了两次一次恢复到临时实例上做逻辑校验一次只做物理校验确认备份集可读、可用、可恢复才算真正过关。5. 备份故障的深挖为什么连历史全备都不能用5.1 备份文件被覆盖后的表现和损坏原理备份文件的格式通常是逻辑上连续的二进制流内部有校验头、块校验信息、文件尾标记。如果随机写入部分字节备份集在做校验时会在第一个被覆盖的块就报错整个备份集就不可用了。这次压测对备份目录造成的影响是全方位的部分文件被直接重写成0字节或随机内容部分文件被截断还有一部分文件被覆盖后文件依然保存了原有文件名和部分头信息看起来像正常文件一读全是乱码。这种情况下如果备份软件只做文件存在性检查不会报错只有真正restore的时候才会暴露。5.2 一个典型误判介质完好≠备份可恢复故障处理中容易出现的误判是文件系统上备份文件存在、大小也差不多就认为备份可以用于恢复。实际上fio这样的工具直接写块设备后文件系统本身可能都不会感知到数据变化ls看到的大小完全正常但是文件的内容早已不可用。所以恢复前一定要用备份校验工具进行内容级校验而不是简单的文件属性检查。这个教训在信创环境下尤为重要因为很多单位的备份策略还是按文件copy式的没有块级校验机制遇到物理覆盖根本无从察觉。5.3 备份策略调整异地、外部存储、多版本快照这次事故之后我们把备份策略调整成了本地磁盘一份、独立外部存储一份、存储阵列快照一份三层结构并且每份备份都做内容校验校验结果记录在备份管理库中。主备库的备份目录与数据库数据文件目录、fio等压测工具的测试目录彻底隔离从源头上杜绝了备份和测试目标混用的场景。同时归档日志的保留策略也做了延长因为归档是增量恢复的关键材料宁可多占存储也不能在备库重建时因为缺归档而走回头路。6. 亡羊补牢信创环境下如何管住压测工具和备份安全6.1 压测任务执行前的三查三确认现在我们的压测任务上线前必须确认三件事其一测试目标路径是否在允许清单内数据文件盘、归档日志盘、备份目录盘绝对不允许作为fio的直接写入目标其二fio的filename参数是否指向具体测试文件而不是裸设备任何裸设备级别的测试都必须经过存储负责人和DBA的双重审批而且只能选在业务低峰和备份窗口之外执行其三测试有没有设置runtime限制和终止条件不能出现跑到一半人走了、fio还在持续覆盖的情况。经过这次事故我有一条经验压测工具放在任何一个环境里跑都要先看它的filename指向哪儿这个参数比bs、iodepth、numjobs这些性能参数重要得多。6.2 数据库主机上的存储盘规划规范运维层面我们强行规定数据库主机上的存储盘必须按用途拆分数据文件、在线日志、归档日志、备份目录、测试目录分别使用不同的逻辑卷或子目录并且设置独立的挂载点。任何压测工具只能使用测试专用目录该目录不允许与其他生产目录共享存储池。同时数据库主机上的系统盘和应用盘不允许被压测脚本直接选中。规范落地之后即使有人再次误操作损坏范围会被限制在测试目录内部不会波及生产数据。6.3 变更管理与监控联动在信创环境中硬件和软件验证、性能压测是常态运维动作。这类操作必须在变更流程中明确标注影响范围履行审批后才能执行不能当作随手一跑的脚本。同时监控侧增加了几类规则数据库日志中出现物理读错误立即告警备份目录出现非备份软件生成的大文件立即告警主机块设备写入量与数据库进程写量差异超过阈值时自动通知DBA。这些规则并不是复杂的技术但在关键时刻能帮我们提前发现类似风险。6.4 应急恢复手册的更新事故之后我们把这次处理过程整理成一份应急恢复手册核心内容就是出现数据文件坏块时第一步停压测、停备份、停一切写操作第二步盘活所有可用的恢复源包括存储快照、外部备份、备库残留文件第三步按先恢复主库→再重建备库→最后重建备份的顺序推进。手册里同时记录了本次事故中用到的每一条命令、每一个校验步骤、每一条绕过坏块的准则确保下一次遇到类似场景时不用临时翻文档、靠记忆决策。7. 复盘后的几个真实建议7.1 关于fio在数据库主机上的使用原则不是不能用fio而是必须在隔离环境或测试专用存储上使用。如果确实需要在生产存储上验证I/O能力尽量避免direct和裸设备组合可以先在文件系统层面创建测试文件并控制文件大小使用ioenginepsync等相对温和的引擎设置较短的runtime和较低的队列深度先跑一个5分钟的小规模探针测试确认不会影响生产后再放大规模。这个过程我们叫先探后压可以大幅降低误伤概率。7.2 关于备份健康度的日常检查备份不能只看任务成功这个状态每个月至少做一次实际恢复演练把备份集恢复到临时实例上跑一下业务逻辑的抽样校验。只有真正restore过一次才能确认这条备份链路是通的。而且备份文件所在的存储空间一定不能和数据库数据文件、压测工具目录混用这个红线不能碰。我们这次如果提前做过一次恢复演练就能在坏块发生前发现备份目录被覆盖的风险不至于等到需要恢复时才被动。7.3 关于主备库架构在信创场景下的额外保障信创环境下很多单位的主备库部署在同一个存储阵列上甚至共用同一个LUN组。这种情况下一次存储层面的误写入就可能同时干掉主备两个节点本次事故就是活生生的例子。有条件的情况下主备库的数据文件应该放在不同的存储池中备库最好支持独立的快照能力和独立的备份链路否则主备架构的保护效果会大打折扣。我们后续在主备库之间额外引入了一层准实时同步机制一旦主库异常备库即使不能完全接管也能保留一份较新的数据副本供恢复使用。8. 随手记给同行的检查清单如果你也在信创环境里做数据库运维建议把下面几条当成硬性规定业务数据盘不允许被任何压测工具以裸设备方式写入备份目录与测试目录物理隔离并且定期做内容级校验一次压测任务至少要有开始时间、持续时间、测试路径、终止条件四要素并且提前通知DBA执行任何可能产生大规模I/O的操作前确认数据库的归档日志、备份任务、同步状态都处于正常状态出现数据文件坏块时第一时间保护现场然后从存储快照、备库残留文件、外部备份三个方向同时做恢复评估不要只依赖单一的备份源。这次故障最后恢复耗时大约5个小时业务影响窗口接近3个小时数据损失通过存储快照和归档日志的回放控制在分钟级别。整个过程踩了很多坑但如果当时的存储规划再合理一点、压测路径检查再严格一点、备份链路再独立一点这5个小时完全是可以避免的。把这些细节写出来也是希望同行的环境不要重蹈覆辙。