这两天, 关于腾讯云因为静默错误, 把创业公司的数据彻底搞丢了的事件已经传遍了整个互联网, 引发了广泛的热议和讨论.
终极故障回放
腾讯云已经于 8 月 7 日公布了最近这次事故的根本原因:
故障过程复盘 当天上午 11:57, 运维人员收到仓库 空间使用率过高告警, 准备发起搬迁扩容; 在 14:05 时, 从仓库 选择了一批云盘搬迁至新仓库 , 为了加速搬迁, 手动关闭了迁移过程中的数据校验; 在 20:27 搬迁完成之后, 将客户的云盘访问切至仓库 , 同时为了释放空间, 对仓库 中的源数据发起了回收操作; 到 20:30 监控发现仓库 部分云盘出现 IO 异常.
故障原因复盘 本次事故起源自因磁盘静默错误导致的单副本数据错误, 再由于数据迁移过程中的不规范操作, 导致异常数据扩散至三副本, 进而导致客户数据完整性受损.
数据搬迁过程中的违规操作主要如下两点:
第一是正常数据搬迁流程默认开启数据校验, 开启之后可以有效发现并规避源端数据异常, 保障搬迁数据正确性, 但是运维人员为了加速完成搬迁任务, 违规关闭了数据校验;
第二是正常数据搬迁完成之后, 源仓库数据应保留 24 小时, 用于搬迁异常情况下的数据恢复, 但是运维人员为了尽快降低仓库使用率, 违规对源仓库进行了数据回收.
改进措施: 经过技术复盘, 腾讯云技术团队深入到每个环节, 通过责任到人与流程闭环的双管齐下, 相应作出如下的加强和改进措施:
首先, 我们将全面审视所有的数据流程, 涉及数据安全的流程自动化闭环, 进一步提升我们常规运维自动化和流程化, 降低人工干预. 同时把全流程的数据安全校验作为系统的常开功能, 不允许被关闭.
其次, 针对物理硬盘静默数据错误, 在当前用户访问路径数据校验自愈的基础上, 我们优化现有巡检机制, 通过优先巡检主副本数据块, 跳过近期用户访问过的正确数据块等方法, 加速发现该类错误, 进行数据修复.
总结一下, 故障的原因是: 操作人员手工关闭数据校验, 并且删除了源库, 当发现静默错误导致的损坏时悔之晚矣.
无论如何, 现在的事故已经发生, 我想整个实践给行业以警示, 我们的客户已经在设置方案将云上的数据库同步备份回本地.
而腾讯的一条改进建议是: 提升自动化运维, 降低人工干预. 这一方面说明了自动化运维的重要性, 另一方面仍然要警惕自动化中的故障传播.
既然有这样一个机会让我们了解了静默错误, 那么我们可以进一步来看一看, 在 Oracle 数据库中的静默错误是如何处理的.
首先还是回顾一下在我上一篇文章中描述的: 什么是静默错误.
什么是静默错误
静默错误在英文中被称为: Silent Data Corruption, 我们知道硬盘最核心的使命是正确的存入数据, 正确的读出数据, 在出错时及时抛出异常告警. 磁盘出现异常的情形可能包括硬件错误, 固件 BUG 或者软件 BUG, 供电问题, 介质损坏等, 常规的这些问题都能够正常被捕获抛出异常, 而最可怕的事情是, 数据处理都是正常的, 直到你使用的时候才发现数据是错误的, 损坏的. 这就是静默错误.
网上的一篇论文: Silent data corruption in SATA arrays: A solution - Josh Eddy August 2008 对静默错误进行了解释. 这篇文章提到:
有些类型的存储错误在一些存储系统中完全未报告和未检测到. 它们会导致向应用程序提供损坏的数据, 而不会发出警告, 记录, 错误消息或任何类型的通知. 虽然问题经常被识别为静默读取失败, 但根本原因可能是写入失败, 因此我们将此类错误称为 "静默数据损坏". 这些错误很难检测和诊断, 更糟糕的是 它们实际上在没有扩展数据完整性检测功能的系统中相当普遍.
在某些情况下, 当写入硬盘时, 应该写入一个位置的数据实际上最终写入另一个位置. 因为某些故障, 磁盘不会将此识别为错误, 并将返回成功代码. 结果, RAID 系统未检测到 "错误写入", 因为它仅在硬盘发出错误信号时才采取措施.
因此, 不仅发生了未检测到的错误, 而且还存在数据丢失. 在图 2 中, 数据块 C 应该覆盖数据块 A, 而是覆盖数据块 B. 因此数据块 B 丢失, 数据块 A 仍然包含错误的数据!
结果, 数据被写入错误的位置; 一个区域有旧的, 错误的数据; 另一个区域丢失了数据, RAID 系统和 HDD 都未检测到此错误. 检索 B 或 C 的访问将导致返回不正确的数据而不发出任何警告.
撕裂写入
在其他情况下, 只有一些应该一起写入的扇区最终会出现在磁盘上. 这称为 "撕裂写入", 其导致包含部分原始数据和部分新数据的数据块. 一些新数据已丢失, 一些读取将返回旧数据. 同样, 硬盘不知道此错误并返回成功代码, 因此 RAID 无法检测到它. 访问检索 B 将返回部分不正确的数据, 这是完全不可接受的.
上文提到的 "撕裂写入", 如果在 Oracle 数据库中发生, 那么就是分裂块, 当然 Oracle 数据库会自动检测这种情况.
那么 "静默损坏" 发生的概率有多少呢? 该文提供了一组数据:
... 一项针对 NetApp 数据库中 150 万个硬盘驱动器的学术研究在 32 个月内发现, 8.5%的 SATA 磁盘会产生静默损坏. 某些磁盘阵列运行后台进程, 以验证数据和 RAID 奇偶校验是否匹配, 并且可以捕获这些类型的错误. 然而, 该研究还发现, 后台验证过程中错过了 13%的错误.
那些未被发现的错误, 就会成为企业的灾难.
即便没有任何错误, 数据也需要定期进行读取, 以确保数据无误, 在几年前, 我遇到过一起案例, Oracle 数据库莫名的发生了一定批量的数据损坏, 存储上没有任何错误, 但是数据库端大量的分裂块, 存储没有检测到错误, 并且复制到灾备站点, 最后导致了数据丢失.
Oracle 的静默错误
如果存储上出现了静默错误, 在 Oracle 数据库中会是什么样的表现?
毫无疑问, 在 Oracle 中经常出现的坏块就是静默错误的受害者之一. 几乎所有的资深 DBA 都经历过坏块问题, 当坏块出现在索引时, 通过重建往往就修复了, 而一旦坏块出现在数据或者元数据上, 修复过程可能就比较复杂, 甚至需要通过备份介质恢复, 影响业务运行, 更有甚者, 会出现数据丢失, 无法恢复的情况.
而坏块出现的原因, 很少能够被明确定位, 除了极少数的存储介质物理故障, 其他多数都是说不清楚原因的逻辑损坏, 通常我们都期望这是一次偶然意外, 期望以后不会发生, 当然, 的确这是一个小概率事件.
在 Google 上, 能够找到一些与静默错误相关的文献, 由于这里不能链接, 我统一放在下载中, 大家可以自行下载学习:
- Carnegie Mellon University - Disk failures in the real world: What does an MTTF of 1,000,000 hours mean to you? (2007)
- CERN - Data integrity study (2007)
- University of Wisconsin-Madison & Network Appliance - An Analysis of Data Corruption in the Storage Stack(2008)
- Google - Failure Trends in a Large Disk Drive Population (2007)
经过很多研究表明,静默错误真实存在, 并且不仅仅是磁盘驱动器的原因, 在某些情况下, 电源电缆, HBA 卡, 有 BUG 的驱动程序或微码也会导致静默损坏.
在 Oracle 官网上有一篇 2013 年的文章: How to Prevent Silent Data Corruption, 这篇文章的两位作者一个是 Oracle Enterprise Linux 的内核开发者, 另外一位来自 Emulex - 光纤卡厂商.
文章这样描述静默损坏:
静默损坏是在没有警告的情况下发生, 可以定义为由于组件故障或无意的管理操作而导致的非恶意数据丢失. 读取或写入无效数据时并不提示 I/O 问题, 最终导致数据损坏. 这种类型的损坏是迄今为止最具灾难性的, 并且没有有效的方法来检测.
通常情况下, 保证数据一致性的 ECC 和 CRC 技术可用于大多数服务器, 存储阵列和 HBA. 但是这些检查仅在单个组件内临时保护数据, 无法确保写入的数据在从应用程序传输到 HBA, 交换机, 存储阵列和物理磁盘驱动器的数据路径中不会损坏. 发生数据损坏时, 大多数应用程序都不知道存储在磁盘上的数据不是要存储的数据.
在过去几年中, EMC,Emulex 和 Oracle 共同合作推动并实施了 T10 SBC 标准的附件信息保护功能, 该功能可以在数据通过数据路径时对数据进行验证, 以确保数据阻止静默损坏的发生.
最终目标是通过创建完整性元数据(也称为保护信息, 与数据同时创建), 然后在整个数据路径中验证元数据, 并将错误回馈给应用程序进行修复, 从而提供针对从应用程序到磁盘的静默数据损坏的保护. 下图就是这个链路的保护过程:
写入数据时会发生以下步骤:
第一: Oracle 自动存储管理库在写入内存时为每个 512 字节扇区添加保护信息.
第二: 保护信息附加到 I / O 请求, 并通过 Oracle Linux 操作系统内核中的层传递给 Emulex 驱动程序.
第三: Emulex LightPulse Lpe16000B 光纤通道 HBA 从内存缓冲区收集信息, 验证数据完整性, 合并数据和保护信息, 然后根据 T10 PI 模型发送 520 字节扇区.
第四: EMC VMAX 阵列固件验证保护信息并将数据写入磁盘.
第五: 磁盘驱动器固件在将数据提交到物理介质之前验证保护信息.
读取数据时, 步骤反向完成.
在 2013 年这篇文章提到, 在基于 OEL 和 Emulex 的配置下, 增强可以被启用以防范数据损失:
这是多方努力的结果, 在 oracleasm-discover 中可以观察到相关信息:
- # oracleasm-discover
- Using ASMLib from /opt/oracle/extapi/64/asm/orcl/1/libasm.so
- [ASM Library - Generic Linux, version 2.0.8 (KABI_V2)] Discovered
- disk: ORCL:P00 [20971520 blocks (10737418240 bytes), maxio 512, integrity DIX1-512/512-IP] Discovered disk: ORCL:P01 [20971520 blocks
- (10737418240 bytes),
- maxio 512, integrity DIX1-512/512-IP] [...]
进一步的展开一下, 看看 Oracle 的增强一致性检查实现. 在复杂的数据流转过程中, 每一个步骤都可能产生数据分歧:
在典型的 I/O 处理栈中, 最后在存储和驱动器层, 8 Byte 的 PI 校验位才被增加进去, 而存储出现静默错误问题时, 顶层是无法感知的.
T10 的保护信息模型, 就是增加额外 8 字节的校验位, 从应用到 HBA 再到底层存储, 层层校验保护:
在最高的 T10 PI 保护级别上, 校验位从应用层就开始被添加, 实现了端到端的校验:
在 Oracle 10g 年代, Oracle 曾经推出 H.A.R.D. 计划, 全程是 - Hardware Assisted Resilient Data , 被翻译为 硬件辅助弹性数据 (HARD) 计划, 用于防止数据损坏.
在 HARD 倡议下, Oracle 与选定的系统和存储供应商合作, 构建可以及早发现损坏并防止损坏的数据写入磁盘的操作系统和存储组件, 并且此功能的实施对最终用户或 DBA 都是透明的.
要使用 HARD 验证, 所有数据文件和日志文件都放在符合 HARD 标准的存储上, 同时启用 HARD 验证功能. 当 Oracle 将数据写入存储时, 存储系统会验证数据. 如果它看起来已损坏, 则写入将被拒绝并显示错误.
HARD 的设计就是用于防范那些可能的静默错误, 以下这些描述就是典型的问题:
写入损坏的块
数据由 Oracle 写入, 并在到达磁盘之前被操作系统或硬件组件干预破坏. 这些组件可能包括操作系统, 文件系统, 卷管理器, 设备驱动程序, 主机总线适配器和 SAN 交换结构. 虽然 Oracle 可以在读取数据时检测到损坏, 但 Oracle 可能会在几天或几个月后才读取数据. 到那时, 用于恢复数据的良好备份可能不再可用.
将块写入不正确的位置
Oracle 向磁盘上的特定位置发出写入. 不知何故, 操作系统或存储系统将块写入错误的位置. 这可能导致两个损坏: 破坏磁盘上的有效数据并丢失已提交事务中的数据.
Oracle 以外的程序对 Oracle 数据的错误写入
Oracle 数据文件可能被非 Oracle 应用程序覆盖. 非 Oracle 进程或程序可能会意外覆盖 Oracle 数据文件的内容. 这可能是由于应用程序软件, 操作系统中的错误或人为错误(例如, 意外地将正常操作系统文件复制到 Oracle 数据文件上).
损坏的第三方备份
将备份复制到磁带时可能会发生数据损坏. 这种类型的损坏特别有害, 因为备份用于修复数据损坏. 因此, 如果备份也已损坏, 则无法恢复任何丢失的数据. 对于第三方备份尤其如此(其中磁盘存储单元直接将数据复制到备份设备而不通过 Oracle.)
可能的 HARD 检查
在实现 Oracle HARD 功能的存储系统中, Oracle 服务器可以通过大量检查来验证 Oracle 块结构, 块完整性和块位置. 如果块在写入时未通过验证, 则存储器拒绝写入, 从而保护数据的完整性. 在系统管理操作期间也可以选择性地禁用 HARD 验证检查, 这可能会暂时使数据处于不一致状态.
- ***
- Corrupt block seq: 37288 blocknum=1.
- Bad header found during deleting archived log
- Data in bad block - seq:810559520. bno:170473264. time:707406346
- beg:21280 cks:21061
- calculated check value: 9226
- Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
- Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
- Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
- Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
- Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
- ***
- Dump file /ADMIN/bdump/erp_p007_19216.trc
- Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
- With the Partitioning, OLAP and Data Mining options
- ORACLE_HOME = /DBMS/erp/erpdb/10g
- Linux
- eygle.com
- 2.6.9-34.ELhugemem
- #1 SMP Fri Feb 24 17:04:34 EST 2006
- i686
- Instance name: erp
- Redo thread mounted by this instance: 1
- Oracle process number: 22
- Unix process pid: 19216, image: oracle@eygle.com (P007)
- *** SERVICE NAME:() 2010-11-10 10:37:26.247
- *** SESSION ID:(2184.1) 2010-11-10 10:37:26.247
- *** 2010-11-10 10:37:26.247
- KCRP: blocks claimed = 61, eliminated = 0
- ----- Recovery Hash Table Statistics ---------
- Hash table buckets = 32768
- Longest hash chain = 1
- Average hash chain = 61/61 = 1.0
- Max compares per lookup = 0
- Avg compares per lookup = 0/61 = 0.0
- ----------------------------------------------
- ----- Recovery Hash Table Statistics ---------
- Hash table buckets = 32768
- Longest hash chain = 1
- Average hash chain = 61/61 = 1.0
- Max compares per lookup = 1
- Avg compares per lookup = 1426/1426 = 1.0
- ----------------------------------------------
- \GPAYMENTdxn
- AP_CHECKS
- Q(xn
- .1=N
- \Gxn
- .1=N
- ^0e
- ^0e!
- ^0e"
- ^0e#
- ^0e$
- ^0e%
- ^0e&
- ^0e'
- ^0e(
- ^0e)
- ^0e*
- ^0e+
- ^0e+
- ^0e&
- ^ij1
- R0:b
- Q(xn
- PaymentsN
- a'VND
- Userxn
- AP_INVOICE_PAYMENTS
- 105273
- 5406105305-20101020-003
- 3001CASH CLEARING
- CREATED
- Dump file /ADMIN/bdump/erp_p002_19206.trc
- Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
- With the Partitioning, OLAP and Data Mining options
- ORACLE_HOME = /DBMS/erp/erpdb/10g
- Linux
- eygle.com
- 2.6.9-34.ELhugemem
- #1 SMP Fri Feb 24 17:04:34 EST 2006
- i686
- Instance name: erp
- Redo thread mounted by this instance: 1
- Oracle process number: 17
- Unix process pid: 19206, image: oracle@eygle.com (P002)
- *** SERVICE NAME:() 2010-11-10 10:37:26.263
- *** SESSION ID:(2187.1) 2010-11-10 10:37:26.263
- *** 2010-11-10 10:37:26.263
- KCRP: blocks claimed = 84, eliminated = 0
- ----- Recovery Hash Table Statistics ---------
- Hash table buckets = 32768
- Longest hash chain = 1
- Average hash chain = 84/84 = 1.0
- Max compares per lookup = 0
- Avg compares per lookup = 0/84 = 0.0
- ----------------------------------------------
- ----- Recovery Hash Table Statistics --------
- Hash table buckets = 32768
- Longest hash chain = 1
- Average hash chain = 84/84 = 1.0
- Max compares per lookup = 1
- Avg compares per lookup = 880/880 = 1.0
- ----------------------------------------------
- ^A&A
- ^1b#
- ^1b!
- ^1b"^0e'
- ^Mj8
- ^;&3
- 2010PS_Legal Entity
- ^6&L
- Eoi_VND
- Quick Payment: ID=47708
- http://www.oracle.com/technetwork/articles/servers-storage-dev/silent-data-corruption-1911480.html
- https://docs.oracle.com/cd/B12037_01/server.101/b10726/apphard.htm
- https://bartsjerps.wordpress.com/2011/12/14/oracle-data-integrity/
来源: http://database.51cto.com/art/201808/580933.htm