Data Guard 归档损坏,根因竟是磁盘写满
Data Guard 归档损坏根因竟是磁盘写满做 Data Guard 久了归档缺失其实并不可怕最怕的是归档文件明明存在控制文件中也有对应记录MRP 应用到这里时却突然告诉你日志已经损坏。缺失的归档可以从主库重新传输也可以从备份中恢复但归档损坏往往会把排查方向带到网络、存储、文件系统甚至 Oracle Bug 上如果只把损坏文件替换掉而没有继续追查根因那么同样的问题很可能还会再次发生。最近在一套 Oracle 11.2.0.4 RAC Data Guard 环境中我们就遇到了一次比较典型的归档损坏故障。最开始只是 Thread 2 的一个归档日志应用失败随后陆续发现多个不连续的归档文件存在损坏。经过 RMAN 校验、Veeam 备份恢复、文件二进制对比、XFS Extent 分析以及 Alert Log 和 RFS Trace 交叉验证最终发现这些归档并不是被存储随机写坏也不是网络传输过程中发生了普通的数据篡改而是在备库磁盘空间耗尽以后RFS 接收到的部分归档形成了逻辑大小正常、内部却存在大量空洞的稀疏文件。更值得关注的是磁盘空间在第二天中午释放以后Data Guard 自动补齐了大部分真正缺失的归档却没有重新获取这些已经登记但内容不完整的文件直到几天后 MRP 应用到对应 Sequence问题才通过 ORA-00353 和 ORA-00354 暴露出来。本文中的数据库名称、主机名、备份标识等信息均已脱敏。故障从一个损坏归档开始故障发生时备库 Alert Log 中首先出现了下面这组错误Media Recovery Log /data/arch/2_416411_1158707423.arc Incomplete read from log member /data/arch/2_416411_1158707423.arc. Trying next member. ORA-00353: log corruption near block 2056 ORA-00334: archived log: /data/arch/2_416411_1158707423.arc ORA-00354: corrupt redo log block headerMRP 检测到归档损坏以后中断了日志应用并尝试重新获取 Thread 2、Sequence 416411但 FAL 请求最终失败Media Recovery Waiting for thread 2 sequence 416411 Fetching gap sequence in thread 2, gap sequence 416411-416411 FAL[client]: Failed to request gap sequence GAP - thread 2 sequence 416411-416411 FAL[client]: All defined FAL servers have been attempted.从报错来看问题并不复杂备库上的2_416411_1158707423.arc存在损坏Oracle 尝试重新获取却没有成功。主库查询V$ARCHIVED_LOG后发现本地归档记录已经被删除但对应 Sequence 仍然保留在 Veeam RMAN 备份中因此可以直接从 SBT 备份恢复。由于环境使用的是 Veeam Plug-in for Oracle RMAN恢复时除了分配SBT_TAPE通道还需要指定插件库和 Veeam 返回的srcBackup。实际恢复命令如下RUN { ALLOCATE CHANNEL VeeamAgentChannel1 DEVICE TYPE SBT_TAPE PARMS SBT_LIBRARY/opt/veeam/VeeamPluginforOracleRMAN/libOracleRMANPlugin.so; SEND srcBackupVEEAM_BACKUP_ID; SET ARCHIVELOG DESTINATION TO /tmp/arch_restore; RESTORE ARCHIVELOG SEQUENCE 416411 THREAD 2; RELEASE CHANNEL VeeamAgentChannel1; }归档成功恢复以后将文件传输到备库替换原来的损坏文件再重新启动 MRP。原本以为问题到这里就结束了但 MRP 刚刚继续向后应用很快又在 Thread 2、Sequence 416413 上报了完全相同的错误。这说明问题不是某一个归档偶然损坏而是后面可能还存在其他同类文件。修好一个归档又发现了更多损坏文件为了避免 MRP 每遇到一个坏归档就停一次我们没有继续采用“遇到一个修一个”的方式而是直接对尚未应用的归档范围执行 RMAN VALIDATE。当时备库已经接收到的最大序列分别为Thread 1425712 Thread 2420300已应用序列分别停留在Thread 1421807 Thread 2416412也就是说两个线程合计还有接近八千个归档等待应用。为了降低一次性扫描对磁盘 I/O 的影响我们将每个线程按一百个 Sequence 一组分批校验最终发现以下八个归档存在损坏ThreadSequence14218101423155142567724164132416419241764924189492420226再加上最开始已经修复的 Thread 2、Sequence 416411这次事故实际上共确认了九个损坏归档。好在校验日志中没有发现文件缺失除这些损坏文件以外其余未应用归档均能够正常通过 RMAN 校验。其中一个典型的 RMAN 校验结果如下Thrd Seq Status Blocks Failing Blocks Examined Name ---- ------- ------ -------------- --------------- ---------------------------- 2 416413 FAILED 12281 12319 /data/arch/2_416413_1158707423.arc 2 416419 FAILED 20415 20422 /data/arch/2_416419_1158707423.arc 2 417649 FAILED 18158 20213 /data/arch/2_417649_1158707423.arc从失败块数量来看这些文件并不是只有一两个 redo block 损坏。例如 Sequence 416413 一共检查了 12319 个块其中 12281 个块失败说明文件绝大部分内容已经不可用。先把 Data Guard 恢复起来确认损坏范围以后下一步是为每个归档寻找可靠副本。大部分归档可以从 Veeam SBT 备份恢复少数较新的归档尚未进入备份但主库 ASM 中的原始文件仍然存在因此可以通过BACKUP AS COPY导出到普通文件系统。Veeam 中有备份的归档可以在同一个 RMAN RUN 块中分别恢复RUN { ALLOCATE CHANNEL VeeamAgentChannel1 DEVICE TYPE SBT_TAPE PARMS SBT_LIBRARY/opt/veeam/VeeamPluginforOracleRMAN/libOracleRMANPlugin.so; SEND srcBackupVEEAM_BACKUP_ID; SET ARCHIVELOG DESTINATION TO /tmp/arch_restore_corrupt; RESTORE ARCHIVELOG SEQUENCE 421810 THREAD 1; RESTORE ARCHIVELOG SEQUENCE 423155 THREAD 1; RESTORE ARCHIVELOG SEQUENCE 416413 THREAD 2; RESTORE ARCHIVELOG SEQUENCE 416419 THREAD 2; RESTORE ARCHIVELOG SEQUENCE 417649 THREAD 2; RELEASE CHANNEL VeeamAgentChannel1; }仍然保留在主库 ASM 中的归档则使用下面的方式导出RUN { ALLOCATE CHANNEL d1 DEVICE TYPE DISK; BACKUP AS COPY ARCHIVELOG SEQUENCE 425677 THREAD 1 FORMAT /tmp/arch_restore_corrupt/1_425677_1158707423.arc; BACKUP AS COPY ARCHIVELOG SEQUENCE 418949 THREAD 2 FORMAT /tmp/arch_restore_corrupt/2_418949_1158707423.arc; BACKUP AS COPY ARCHIVELOG SEQUENCE 420226 THREAD 2 FORMAT /tmp/arch_restore_corrupt/2_420226_1158707423.arc; RELEASE CHANNEL d1; }所有归档准备完成以后先计算 MD5再传输到备库临时目录进行二次校验。确认文件大小和 MD5 完全一致后停止 MRP将原损坏文件移入隔离目录再把恢复出来的正常文件放回/data/arch。替换完成以后对所有文件重新执行 RMAN VALIDATE结果全部变成Status: OK Failing Blocks: 0到这里文件层面的修复已经完成但重新启动 MRP 时又出现了一个很容易被忽略的问题。修复过程中的第二个坑MRP 又读到了隔离目录中的坏文件为了保留现场我们将原来的损坏文件移动到了/data/arch/corrupt_20260727新文件已经放回正式目录并通过 RMAN 校验但 MRP 启动以后Alert Log 中读取的却不是正式目录中的文件而是隔离目录中的旧文件Media Recovery Log /data/arch/corrupt_20260727/2_416413_1158707423.arc随后再次报出 ORA-00353 和 ORA-00354。查询V$ARCHIVED_LOG后发现同一个 Thread 和 Sequence 存在两条记录一条指向正式目录REGISTRARRFS另一条指向隔离目录REGISTRARSRMN。也就是说隔离目录中的坏文件后来又被 RMAN 登记到了控制文件中MRP 在选择归档副本时恰好选中了这份损坏文件。处理方式是先停止 MRP然后取消登记隔离目录中的所有归档CHANGE ARCHIVELOG LIKE /data/arch/corrupt_20260727/% UNCATALOG;RMAN 成功取消登记了八个隔离文件随后再次执行LIST ARCHIVELOG LIKE已经查询不到这些记录。接着在 SQL*Plus 中对当前最早需要应用的正常归档执行强制替换登记ALTER DATABASE REGISTER OR REPLACE PHYSICAL LOGFILE /data/arch/1_421808_1158707423.arc; ALTER DATABASE REGISTER OR REPLACE PHYSICAL LOGFILE /data/arch/2_416413_1158707423.arc;再次启动 MRP 后Alert Log 中终于开始读取正式目录中的文件Media Recovery Log /data/arch/1_421808_1158707423.arc Media Recovery Log /data/arch/2_416413_1158707423.arc Media Recovery Log /data/arch/1_421809_1158707423.arc Media Recovery Log /data/arch/1_421810_1158707423.arc Media Recovery Log /data/arch/2_416414_1158707423.arc此时 MRP 状态恢复为APPLYING_LOGGAP_STATUS变成NO GAPTransport Lag 回到 0Apply Lag 也开始持续下降。Data Guard 已经恢复但真正有价值的排查其实才刚刚开始因为我们还没有解释清楚这些归档为什么会损坏文件大小完全一致内容为什么会损坏最初排查根因时网络、VMware、XFS、PVSCSI 和 Oracle Bug 都在怀疑范围内。备库使用 XFS 文件系统底层是由三个 VMware Virtual Disk 组成的普通 LVM Linear Volume操作系统dmesg中没有发现 XFS shutdown、SCSI timeout、PVSCSI reset 或 Buffer I/O Error。网卡vmxnet3虽然存在少量接收错误和 driver drop但发送侧没有异常FCS 和接收缓冲区分配失败也都是 0。单纯依靠这些信息很难判断究竟是网络、存储还是 Oracle 本身的问题。真正的突破口来自坏文件与正常文件的二进制比较。坏文件和从主库或 Veeam 恢复出来的正常文件逻辑大小完全相同但从某些固定位置开始大量数据出现差异。将文件头后的内容按照 1MB 分块计算 MD5 后发现很多损坏区间的 MD5 都是b6d81b360a5672d80c27430f39153e2c这个值正是 1MB 全零数据的 MD5。例如Thread 2、Sequence 416413 的前五个 1MB Chunk 全部为零后面的少量数据却又是正常的Thread 2、Sequence 420226 则是前六个 Chunk 正常中间四个 Chunk 全零最后两个 Chunk 又恢复正常。这些全零区域的起始位置也非常有规律基本都满足4KB N × 1MB换算成 Oracle redo block就是Block 8 N × 2048这与 Alert Log 中报出的log corruption near block 8、near block 2056、near block 6152等位置完全对应。很显然这不是普通的随机比特翻转也不像磁盘坏块造成的零散损坏而是某些以 1MB 为单位的数据区间从一开始就没有写进去。filefrag 给出了决定性证据为了确认这些全零内容到底是被写成了零还是根本没有写入我们进一步使用filefrag、stat和du检查文件的物理 Extent。以 Thread 2、Sequence 416413 为例这个文件的逻辑大小是6307840 字节约 6.1MB但stat显示它实际只分配了 40 个 512 字节块du查看实际磁盘占用只有 20KB而du --apparent-size显示的逻辑大小仍然是 6.1MB。filefrag的结果更加直观logical 0..0 有物理 extent logical 1..1535 没有物理 extent logical 1536..1539 有物理 extent也就是说这个逻辑大小为 6.1MB 的文件真正落到磁盘上的只有文件头和末尾极少数数据中间几乎全部是文件空洞。这种文件在 Linux 中叫作 Sparse File也就是稀疏文件。稀疏文件的逻辑长度可以很大但中间没有实际分配磁盘块的区域并不占用物理空间。当应用程序读取这些区域时XFS 不会返回 I/O 错误而是直接返回全零数据。至此问题的性质已经非常清楚这些归档不是后来被磁盘“写坏了”而是在生成时就有部分数据根本没有写入。文件路径存在、逻辑大小正常控制文件中也有记录但其中某些 redo 数据区间实际上只是 Sparse Hole。7 月 24 日磁盘写满把所有证据串了起来继续检查备库 Alert Log 后终于发现了决定性的错误ORA-19502: write error on file /data/usmesdg/tbs_xxx.309.xxxxxxxxxx Linux-x86_64 Error: 28: No space left on device同一时期还出现了ORA-16038: log 11 sequence# xxxxxx cannot be archived ORACLE Instance standby - Archival ErrorRFS Trace 中则连续出现Archivelog creation failed; error 19504这说明事故发生时备库/data文件系统确实已经没有可用空间RFS 创建归档文件的过程也明确发生了失败。到这里归档损坏、Sparse Hole 和磁盘写满之间的证据链已经完整闭环。从文件表现来看RFS 接收归档时留下了完整的逻辑文件长度但部分以 1MB 为单位的数据区间因为空间不足没有真正分配物理块。之后即使还有少量数据成功写入前面没有写入的区域仍然会保持为空洞因此才会出现“前面全零、中间正常”或者“前面正常、中间全零、最后又正常”的情况。值得注意的是当时还同时出现了ORA-19815: db_recovery_file_dest_size ... 100.00% used ORA-19809FRA 配额耗尽和/data文件系统物理空间耗尽是两个不同的问题。ORA-19815表示 Oracle 设置的 FRA 配额已经达到上限而Linux Error: 28则表示底层文件系统真正没有了可分配空间。本次归档形成 Sparse Hole 的决定性触发条件是后者。为什么磁盘释放以后坏归档没有自动修复客户环境中有一个归档清理计划任务每天中午 12 点执行删除七天以前的归档日志。查询损坏归档和相邻 Sequence 的完成时间以后发现时间规律几乎与这个计划任务完全对应。以 7 月 24 日为例最初损坏的归档在当天 17:29 至 17:37 之间由 ARCH 发送、RFS 接收并登记但周围大量正常归档直到第二天中午 12:02 至 12:05 才集中完成接收。类似的情况在 7 月 25 日、26 日和 27 日也重复出现傍晚产生异常归档第二天中午清理任务释放空间以后相邻缺失日志开始批量补传。整个过程实际上是这样的/data 文件系统写满 ↓ RFS 接收归档时部分写入失败 ↓ 留下逻辑大小完整的稀疏归档 ↓ 控制文件中已经存在该 Sequence 的记录 ↓ 第二天 12 点清理旧归档释放空间 ↓ ARCH/FAL 补传真正缺失的 Sequence ↓ 已经存在并已登记的坏归档没有重新获取 ↓ 几天后 MRP 应用到该文件 ↓ 读取 Sparse Hole 得到全零 redo block ↓ ORA-00353 / ORA-00354这也解释了为什么问题没有在磁盘写满当天立即以 MRP 中断的形式暴露出来。当时备库存在较大的 Apply LagMRP 还没有应用到这些 Sequence。对于 Data Guard 来说完全不存在的归档属于 Gap可以通过 ARCH/FAL 重新获取但这些损坏归档的文件路径存在控制文件也已经登记逻辑大小看起来没有异常因此不会被当成缺失文件重新传输。只有 MRP 真正读取 redo block 时Oracle 才发现内部内容不合法。归档量并不算夸张为什么 1.4TB 还是写满了统计最近几天的归档产生量后发现正常情况下每天大约产生 26GB 至 30GB 的归档日期归档量文件数量2026-07-2126.24GB23862026-07-2226.04GB23722026-07-2325.77GB23652026-07-2527.86GB25912026-07-2627.99GB25942026-07-2729.52GB2747按照每天 30GB、保留七天计算归档本身大约需要 210GB。表面上看/data总容量达到 1.4TB似乎不应该因为七天归档就被写满。但问题在于/data并不是专门的归档文件系统。从ORA-19502的报错路径可以看出备库数据文件同样位于/data归档日志、数据文件以及其他临时文件共同使用同一个文件系统。只要数据文件发生扩展、Apply Lag 造成归档积压或者临时恢复文件没有及时清理就可能迅速压缩归档可用空间。因此本次事件不能简单归结为“归档清理任务没有执行”更准确的说法是数据文件和归档共用空间容量缺乏隔离清理任务每天只执行一次没有磁盘阈值保护磁盘在中午释放空间以后到了傍晚又可能重新接近满空间从而连续多天产生新的不完整归档。故障最终是如何恢复的整个恢复过程可以归纳为以下几个步骤。首先停止 MRP对所有未应用归档分批执行 RMAN VALIDATE准确识别损坏范围而不是每遇到一个报错再处理一个。随后根据每个归档的保存情况从 Veeam SBT 备份或主库 ASM 中恢复正常副本并通过 MD5 和 RMAN VALIDATE 双重确认文件完整性。替换备库中的损坏文件后还需要注意清理隔离目录对应的 RMAN 记录避免 MRP 再次选中旧文件。必要时使用REGISTER OR REPLACE PHYSICAL LOGFILE强制刷新正确路径。最后重新启动实时应用并通过V$MANAGED_STANDBY、V$ARCHIVE_GAP和V$DATAGUARD_STATS验证状态。恢复完成以后备库状态如下MRP0 APPLYING_LOG RECOVERY_MODE MANAGED REAL TIME APPLY GAP_STATUS NO GAP transport lag 00 00:00:00Apply Lag 仍然存在但已经开始持续下降这属于故障期间积压日志的正常追赶过程。这次故障留下的几个教训这次事故最需要整改的并不是某一条 Data Guard 参数而是整个归档容量管理方式。首先备库数据文件与归档日志不应该长期共用同一个文件系统。归档突增不应该影响数据文件写入数据文件扩展也不应该挤占归档接收空间。更合理的规划是将数据文件和归档拆分到独立文件系统或独立 LVM给归档目录保留明确的容量边界和安全余量。其次归档清理不能只依赖每天固定时间执行一次。每天中午 12 点清理意味着一旦下午或傍晚磁盘写满系统必须等到第二天中午才能自动释放空间。在高归档量环境中应该增加磁盘使用率阈值监控并根据归档备份状态、应用状态和保留要求动态清理。清理操作也不建议长期使用简单的rm而应尽量通过 RMAN 管理避免文件系统与控制文件记录长期不一致。容量规划也不能只计算“日归档量乘以保留天数”。在 Data Guard 环境中还要考虑 Apply Lag 积压、网络中断后的 FAL 补传、归档重复接收、故障处理临时文件以及至少 20% 至 30% 的安全余量。按照当前每天约 30GB 的归档量计算七天基础需求约为 210GB如果再考虑故障期间数日不能清理和补传空间独立归档文件系统至少应准备 300GB 至 400GB实际容量还应结合业务增长趋势继续评估。另外只监控磁盘使用率还不够。一旦出现Linux Error: 28、ORA-19502、ORA-19504或ORA-16038即使释放空间以后 Data Guard 表面上恢复了传输也应该立即校验故障时间段内已经接收的归档。因为磁盘满可能留下“文件存在但内部为空洞”的特殊情况单纯查询V$ARCHIVE_GAP并不能发现这种问题。可以对新接收归档增加简单的稀疏文件扫描find /data/arch -maxdepth 1 -type f -name *.arc -mmin -60 | while read f do size$(stat -c %s $f) allocated$(( $(stat -c %b $f) * 512 )) if [ $allocated -lt $size ]; then echo SPARSE ARCHIVE: $f size$size allocated$allocated fi done正常归档文件的实际分配空间应该与逻辑大小基本一致。如果某个归档逻辑大小为十几 MB实际占用却只有几十 KB就需要立即停止应用、隔离文件并重新补传。总结回顾整个过程这次故障表面上是一个普通的 ORA-00353 归档损坏问题但真正的根因并不在 MRP也不在主库 ASM、Veeam 备份、网络或者 XFS 文件系统本身而是备库/data文件系统空间耗尽以后RFS 接收的部分归档形成了稀疏文件。文件逻辑长度正常控制文件中也有有效记录但部分以 1MB 为单位的 redo 数据区间没有真正落盘读取时只能得到全零内容。每天中午的清理任务释放空间后Data Guard 自动补齐了真正缺失的归档却没有重新获取这些已经登记的损坏文件因此问题一直潜伏到 MRP 应用对应 Sequence 时才最终暴露。这次排查最有价值的地方不是掌握了如何从 Veeam 恢复归档也不是知道了如何重新注册日志而是通过 RMAN VALIDATE、二进制分块比对、filefrag、stat、Alert Log 和 RFS Trace把“归档损坏”还原成了一个完整、可解释的故障链路。很多时候磁盘写满并不一定只表现为文件创建失败它还可能留下一个看起来大小正常、实际上大部分内容根本没有写进去的文件。对于 Data Guard 来说这种文件比直接缺失更加隐蔽因为它不会立即形成可见的 Gap直到 MRP 真正读取时才会报错。故障恢复只是把备库重新拉起来能够解释它为什么发生、为什么几天后才暴露以及如何避免再次发生才是这次事故真正的价值。