那些年跟领导聊过的数据归档【DB篇】:从梳理到落地-DB单表千万级归档详细流程讲解

article/2025/9/16 17:54:06

文章目录

    • 知人论世
    • 执笔蓝图
      • V1 - 浅尝辄止
      • V2 - 初窥门镜
      • V3 - 木已成舟
    • 躬行方案
      • 安内
      • 攘外
    • 卓有成效
    • 沉淀之石
    • 道阻且长

知人论世

无论何种需求的出现都是因为某种迫切解决的问题契机,它是业务发展中定数也是劫数,近期DBA反馈磁盘存储空间超过80%不足以支撑未来业务发展,勉强能够承受1年存储;随后TL高度重视,计划一整个Q完成,一场渡劫之路拉开帷幕。。。。。

公司内部调整到核心部门不久的我和另一个partner临危受命,对业务不熟悉的我从0开始,随机整理一份数据现状供给分析

其实分析维度还有很多,如唯一索引使用情况、涉及服务、数据更新场景等没有一一列举。

表名单表数据量分表数量分表维度单表月平均增幅描述
xxA千万+64订单编号100w+核心
xxB千万+64订单编号100w+核心
xxC千万+-用户id同用户增长1:1核心

DB磁盘占用:80.5%     ES磁盘占用76%
磁盘容量和单表压力日渐明显, 迫切需要优化方案并落地,而后便开始漫长的方案设计,长路漫漫,上下求索


执笔蓝图

老实说这是我第一次处理这类技术需求,所以第一反应就是借鉴前人思路,公司wiki、Google大厂优化案例扒了几遍,有了大致思路,然后执笔挥斥方遒。大约用了一周制定了第一版方案

V1 - 浅尝辄止

第一种方案基本1:1是借鉴其它部门处理方式:在原来基础上进行水平扩容分表,即由64扩到128,这个是基于订单号构造想的一个快速方案,由于前六位是系统生成的时间戳,所以只需要在路由算法处进行升级即可,如下示意图

在这里插入图片描述

核心思想: 根据设定时间点进行切分,在时间点以前的现有表不会新增,只做查询和更新,时间点以后继续按hash策略路由新表处理读写
方案优势:

  1. 改造代码成本极低,只需要更改路由算法
  2. 仅需要同步数仓部门进行binlog感知【当时并没有注意这一点】

方案劣势:

  1. 仅解决数据增量问题,并没有解决现有单表的查询压力
  2. 数据库磁盘存储容量压力仍然存在
    实施上这个方案想的过于简单,可行性极低,且没有解决实际问题,有人会说,如果加上定期归档是否可性,当时会议确实提过,但我的TL随机而来五连问:
序号问题
1当前表影响面是怎样的,是否影响C端核心链路?
2只有本部门在使用改表吗?归档后会影响其它业务方使用?
3归档粒度你如何界定,产品是否有思考?
4归档后历史数据查询怎么办,现有接口是否做了容错和兼容?
5归档表有数据能确定是最终态吗?比如未支付状态

毫无准备的我瞬间懵逼,当时基于对业务初步梳理,准确说没有深入理解业务,就开始激扬文字般地开始设计,注定产物是不成熟的,或者说缺乏实践;了解到自己的认知不足后,迅速开始深入业务理解,在一周后开启v2版本制定


V2 - 初窥门镜

经过第一次和领导的沟通后认识到很多地方欠缺考虑,也随机带着回答问题方式做了如下努力:
业务梳理:

  1. 从普罗米修斯监控上查询影响的所有服务的所有接口的一个月日志API调用

    看自己公司日志维护情况,至少2个周的调用日志保证是需要的

  2. 从go和java等服务dao 层往上梳理所有接口,确定使用场景、读写分类

业务沟通

  1. 和产品确定归档的边界,即业务方能接受的存储时间维度

    实施上当时产品给的意见尽量全量保留,那势必技术方面就要划分冷热数据源,暂定1年为界限;而这也为前端和客服分别带来了改造成本和使用成本,这个过程撕逼不断。。。

  2. 确定表的使用方

    确实也有其他部门直连我们业务表,推动其改造,道阻且长;有的时候你认为的高优先级对方未必觉得,组织一致性这时就显得很重要

  3. 和数仓部门确定归档操作对其无感知

    作为业务部门我们对表的任何DDL操作 这个是沟通次数最多,也是最费劲的,不过他们给我们的结论也是最坑的【后面会提到】,当时沟通是等待其内部新老系统改造完毕后即无感知操作

方案设计

  • 基于梳理认知构建归档流程
  • 细化归档节奏
  • 先入为主,思考归档前后可能出现的潜在问题

整体设计分为两部走:

  1. 用户维度数据: 进行散表64张表

    • 根据user_id 进行hash % 64取余
      • hash算法对比【减少碰撞】
    • 增量同步
      • 开关控制,一键回切
      • 双写事务保障数据一致性【可选】
      • 无则新增,有则修改
    • 全量同步
      • 无则新增,有则跳过
    • 读切换
      • 开关控制,离线比对
      • 比对期间返回老数据
      • 比对无误全切新
    • 原功能下线
      • 原读写代码下沉
      • 原单表数据delete

事实上该方案理论可行,单当前的数据库磁盘存储压力明显,因为是物理机的原因在原基础已经无法继续扩容,所以此刻水平扩表失去了先机,但思想是可以借鉴的,后续的真实落地方案也是基于此设计而成。

  1. 订单维度数据根据产品要求保留的时间范围之外进行归档
    设计归档策略时也和领导聊了很多,最终罗列如下:
  • 归档策略
    • 旧库 -> 新库,旧库直接物理删除,新库按年分库,分表同旧【短时间数据double,读写都需要改造,长期维护】
    • 旧库 -> 数仓 ,旧库直接物理删除,查询依赖数仓【历史全量数据依托BI,时间排期、优先级不可控】
    • 旧库 -> ES, 旧库直接物理删除,查询依赖 ES 【内部闭环,改造全部查询接口】
    • 旧库 -> MongoDB 【无使用经验,新增经济成本,疫情当下需要降本增效】

当时领导决策决定采用旧库 -> 数仓 方案,从数据维护合理性上考虑,业务侧确实无法支撑历史全量数据的查询,这的确是一个快速且稳妥的方案,但事与愿违:

数仓TL反馈不在一个体系,短时间满足需求可以,但其组织变更后未必能持续满足,言外之意深重,而后经过几次会议同步,一次比一次会议的沟通结果和最开始目标相去甚远,索性另辟蹊径,另一方面即便下沉到数仓,有些底层查询sql势必要下沉,从业务合理性数仓确实应该业务无关性。
最终归档策略定为:旧库 -> ES,这里值得关注为啥一开始不选取该方案,因为在数据库优化的时候,ES磁盘占用也来到了75%,当时双重压力现在也不得不去面对和解决


V3 - 木已成舟

确定了归档策略,我们还要从全局执行节奏上考虑发现还有几处遗漏:

  • 行动纲领

    这就是后续所有开发节奏和操作内容的总纲领。

在这里插入图片描述

  • 数据同步能力

    技术选型
    1. 采用现成组件logstash | binlog
    优点:开箱即用
    缺点:黑盒,需要申请资源部署、全量同步能力弱
    2. 手动编写脚本
    优点:白盒,增量和全量同步可以自定义
    缺点:开发成本较高【choose

  • 故障修复能力

    如果归档期间出现问题影响了使用方,那么需要回滚,如果采用数据库方式回滚,那么大批的delete这个过程是及其漫长,有锁表风险。只能另辟蹊径:从ES|数仓进行紧急情况下的数据恢复,依旧需要踩坑

  • 数据比对能力

    接口改造后不可能直接进行读切换,应该在具备校验数据一致性前提下,应该经历比对、灰度最后全量的操作,因此需要补足比对脚本和开关控制

紧接着做了开发前的最后信息同步和确认:

  1. 产品、前端、客服、上游业务部门沟通当前全量查询变更为冷热方式

    这是一个相互理解的过程,技术改造理应对上游无感知,尽最大努力闭环,冷热划分后我们对一些C端场景采取限制,B端可以支持全量,毕竟有迹可查,但需要上游配合约定一个标识让我们感知到冷热边界以便减轻查询压力。但与此同时带来一些使用成本,在这由衷感谢客服一线人员对技改的理解。

  2. 同步SRE 当前方案,评估DB、ES磁盘是否可以支撑到改造结束

  3. 产品、业务方进行方案宣贯

    这个很重要:确保各个业务部门感知到,以便撕逼时可以据理力争

整体执行分为2期项目

  • 订单维度表
    1. 先对订单相关表查询接口进行改造 【DB不存在 -》ES】
    2. 相关脚本开发
      • 数据逆向恢复脚本【ES | 数仓 -> DB】
      • 数据同步脚本【DB -> ES】
      • 数据比对 【DB with ES】
      • 归档脚本 【delete from xx where order_sn=?】
    3. 接口上线,脚本执行
    4. 物理归档,释放磁盘空间

在这里插入图片描述

  • 用户维度表

    从业务领域划分考虑,有几个表不归属于订单,所以下沉给用户部门,具体步骤大同小异,节奏如下:
    在这里插入图片描述这其实就是整个流程的缩影


躬行方案

当真正开始着手开发的时候,可谓是一路坎坷不平,几乎上面的每个开发阶段都踩过大大小小坑,有时候甚至哭笑不得

安内

俗语有言:“打铁还得自身硬”, 又“一屋不扫何以扫天下”,从内部中遇到的坑如下:

  1. 为了保障作为归档库的数据准确性,对我们已有的全量数据和DB进行一次比对发现存在丢失和不一致的现象

    原因:
    1. 有些归档表是一次性数据,无二次查询可能
    2. 历史1年前数据不一致为主,新数据以db查询为主,整体无感知。
    改变:
    1. 编写逐字段比对脚本,开启全量比对,还没开始同步,已经开始修复历史遗留bug【我和另一个parter商量要不跑路?】
    2. 更改_id为sn,在分表情况下存在的重复id导致数据覆盖进而丢失数据,因此必须要保证_id绝对唯一
    如下是部分demo

      public boolean selfEquals(XXXEsMapping that) {if (this == that) {return true;}if (that == null) {return false;}return objectsEquals("id", this.getId(), that.getId())&& objectsEquals("type", this.getType(), that.getType())&& objectsEquals("data", this.getData(), that.getData())&& objectsEquals("status", this.getStatus(), that.getStatus())&& objectsEquals("createTime", this.getCreateTime(), that.getCreateTime())&& objectsEquals("updateTime", this.getUpdateTime(), that.getUpdateTime())......;
    }private boolean objectsEquals(String field, Object o1, Object o2) {boolean equals = Objects.equals( o1, o2);if (!equals) {log.warn("sn :{} 数据比对不一致字段: {} o1:{}  o2: {}", orderSn, field, o1, o2);}return equals;
    }
    
  2. 主键id增长顺序有变动,数据同步无法基于时间段去扫描,只能通过id进一步组合时间维度绑定每个批次

    原因:没有时间索引【可见设计之初,先见之明多么重要!】,好像这么处理没啥问题,但也是不得已给自己留的坑【搞起来连自己都埋了hhh】:
    小华有笔订单越看越不顺眼,删除了,好像就是delete_status=1[已删除]这么简单的事情,但我们有BI,必须得让BI感知到这个变化。此类场景,就会导致恢复到db的数据所使用id 还是历史的id,而增量同步任务再次基于id +时间段扫描时就会发生遗漏。
    解决: 恰好是归档结束后发现,重新添加索引,按时间一个纬度进行同步

  3. 初入茅庐,ES使用不当

    1.ES弱结构化,字段变更都是无责新增,有则覆盖;因此构造ES的VO对象时需要注意一比一复制mapping 字段,慎用继承
    2.DB 的 date 类型字段默认值 0000-00-00 ES date类型默认值 0001-00-00,这个容易造成业务误判
    3.对于不需要搜索的字段无需进行分词存储以减少倒排索引的构建【敬请期待:百炼成钢:ES生产使用手册-十八罗汉】

  4. 并发场景

攘外

现象:基本就是各种事中沟通和事后落地没有对齐, 时间成本被无情扩大,对于已经给出结论但事后反悔不认或者忘记的只能由衷歌颂:听我说,谢谢有你,温暖了四级【mmp】。。。
解决: 凡沟通必有记录和二次确认,是钉钉群宣贯


卓有成效

经过上述流程下来,历时2个Q,开发时间周期40天,其余时间基本会议协商、等待测试资源;
整体效果:磁盘占用:60% ES占用 60%【无新的大表情况下,不再增长】,支撑此结论有以下脚本:

任务描述
数据同步每日增量归档DB数据到ES
数据归档1年前数据每日增量归档 , 动态维持1年
数据比对1年前数据每日比对
离线恢复支持多维度数据恢复

沉淀之石

  1. 沟通能力- 方法论

    1. 凡沟通必有结论,凡结论必须同步,凡会议必有纪要。 2. 埋头猛干不可取,风险预知要早知 3. 不要我觉得,要大家都觉得
  2. 分析能力- 知己知彼

    1. 纸上得来终觉浅,得知此事要躬行,没有业务理论支撑的技改都是刀尖舔血。2. 总揽全局,排除潜在业务隐患
  3. 心理承受能力- 百炼成钢

    遇事不慌,胆大心细,警惕Easy Answer

  4. 编码能力 - up

    并发编程脚手架【重构之美:当多线程批处理任务挑起大梁 - 万能脚手架】、异常感知、灰度策略设计、兜底方案设计


道阻且长

虽然看上去只是完成一项工作,但从事情本身带给我的价值远远超过完成任务,这些经验有的是跨部门有的甚至是跨行业的,感受良多:

在这个过程中,面临很多困难和内外部的压力,从TL和其它同事那学习到了很多优秀经验和设计理念让我劈风斩棘,最终迎来胜利的曙光,这都离不开他们的帮助,在此感谢我的直属leader对我肯定和认可,他们的耐心指导和信任让我倍受鼓舞。

非常感谢我那未过门的媳妇儿在背后默默的支持和没有时常陪伴她所做出的理解,乘彼之爱,铭记在心

长路漫漫,继续求索


http://chatgpt.dhexx.cn/article/lrgFELC6.shtml

相关文章

进来偷学一招,数据归档二三事儿

Hello,大家好,我是楼下小黑哥~ 随着业务的快速增长,业务体量变得越来越大,这个过程我们会碰到各种问题,倒逼着我们进行技术升级。 那今天我们来聊下,这个过程将会碰到关于数据的问题。 数据增长带来的烦…

MySQL 归档数据的方法你知道了吗

归档,在 MySQL 中,是一个相对高频的操作。 它通常涉及以下两个动作: 迁移。将数据从业务实例迁移到归档实例。删除。从业务实例中删除已迁移的数据。 在处理类似需求时,都是开发童鞋提单给 DBA,由 DBA 来处理。 于…

7.数据归档(Archiver)

1.归档的基本概念 归档: 是指将数据写入文件存储到程序的沙盒中,当再次重新打开程序时,可以还原这些数据. 称它为数据序列化, 数据持久化. 临时数据: 存储在内存中的数据, 程序关闭, 内存释放,数据丢失数据持久性的方式 1) NSKeyedArchiver—-对象归档 2) NSUserDefaults 3)…

如何用matlab求出矩阵简化阶梯形顺带算出主元所在的列

matlab用rref函数 函数格式 [R,j]rref(A) A是矩阵 R是简化后的阶梯形 j是主元例子:求矩阵A的简化阶梯形与主元所在的列 >> A [1 1 -2 1 4; 2 4 -6 4 8; 2 -3 1 -1 2; 3 6 -9 7 9]; >> [R,j]rref(A)R 1 0 -1 0 40 1 -1 0 …

(线性代数笔记)2.阶梯型矩阵

1.阶梯形矩阵的定义 矩阵的主元 2.任意矩阵经过有限次初等行变换化为阶梯形 这里C是由B通过初等行变化得到的,C也是A的阶梯形 3.矩阵的秩 例题(矩阵通过初等行变换转化为阶梯形)

行阶梯型矩阵,行最简形矩阵,标准形矩阵

行阶梯形矩阵: 行最简形矩阵: 标准形矩阵:

MATLAB--矩阵操作(1.4)

矩阵的逆 >> A*inv(A) ans 1.0000 0 -0.0000 -0.0000 1.0000 -0.0000 -0.0000 0 1.0000 >> norm((ans-eye(3))) ans 1.8620e-15 一个矩阵中行(列)的最大线性无关组的行(列)向量的个…

python 矩阵化为最简阶梯型

from sympy import Matrix import numpy as np原数据是矩阵matrix A_matrix np.array([[1, 0, 0, 0, 1, 0], [1, 0, 0, 0, 0, 1], [0, 1, 0, 1, 0, 0],[0, 1, 0, 0, 0, 1], [0, 0, 1, 1, 0, 0], [0,0,1,0,1,0],[1,1,1,-1,-1,-1]])#系数矩阵,类型matrix# 阶梯行 A…

Python3 矩阵求最简行阶梯矩阵

由于在Python numpy库中没有直接对Matrix求RREF的方法,度娘了好久发现在另一个科学计算包sympy中可以利用A.rref()的方法对Matrix直接求RREF,但是有另一个问题,大家一般常用的是numpy,而sympy和numpy使用的是不同的数据类型&#…

用c语言将一般矩阵化为简化阶梯型

(完整的程序附在文末) 1、问题描述: 用C/C设计一个算法,把矩阵M化为行最简形梯形矩阵A。矩阵A应该满足以下几个条件: 1)若有零行,则零行应在最下方; 2)非零首元(即非零行的第一个不为零的元素&a…

矩阵的行简化阶梯型和标准型

矩阵的行简化阶梯型是一种很有用的与原矩阵等价的矩阵,包括有相同的秩,相同的零空间,以及可以用来求解线性方程组 1 阶梯型矩阵和行简化阶梯型矩阵 下面以上节的方程组开始做初等变换: 由方程组得到增广矩阵 : B 下边对B进行初等变换: B…

阶梯形矩阵(Echelon Matrix)

初等矩阵 定义:与单位矩阵只有微小差别的矩阵。具体来说,就是一个单位矩阵经过一次初等行变换或一次初等列变换后得到的矩阵。 初等变换有下面三种形式: 1,两行(列)互换 2,把某行(…

如何将一个矩阵化为行阶梯形矩阵

2016-03-29 尾巴 线性代数 有同学反映上一课过于冷冰冰,都是一些不带证明的公式。如果线性代数所有公式都要证明的话,线性代数的难度会上好几个量级,有的公式的证明是特别特别难的。还有一个,虽然我们需要大家能对这门课有一些直观…

《线性代数》学习之———第一章 矩阵与方程组(1.2行阶梯形)

1.2行阶梯形矩阵 行阶梯形矩阵相关理论主要的应用还是针对矩阵的不同情况进行化简,因为有些矩阵在化简过程中会出现,系数矩阵的相关行等于0但是对应的增广矩阵相关行不等于0,使出现了违反常理的0实数的情况。 因此,就需要行阶梯形…

修改电脑网段

因为需要远程连接,把自己电脑网段修改一下。 控制面板-网络和Internet-网络连接“右键”-属性-IPv4-自行修改

OpenWrt修改IP网段

修改LAN的IP配置网段 修改此处即可

局域网同一个网段通信过程

局域网内同网段通信的过程 这个时候就需要用到ENSP模拟器了,之前已经安装好了,我们通过ENSP来搭建实验环境,这样可以看到实验的效果以及通过抓包来分享整个过程,先看同网段内的通信过程。 1、准备工作 2、开始测试 我们先用PC1访…

linux 跨网段ping,Linux中跨网段ping问题

问题是这样的,如图,pc的ip是192.168.1.2,设备是linux内核2.6.35,有两个网卡,一个是带外管理口192.168.1.1 一个是192.168.2.1。pc与设备的带外口也就是192.168.1.1相连 将pc的gateway设置为192.168.1.1 pc ping 192.16…

修改docker ip网段

一 问题描述 用docker方式安装完archery后,网络同事反馈该archery服务器的网段和现有网段冲突了,我在archery服务器上,的确发现docker自动生成了几个网卡: #查看docker使用的网卡 [rootArchery ~]# docker network ls NETWORK I…

批量ping网段

ping命令是我们检查网络中最常用的命令,一般常见的场景是去ping一个地址或三四个地址,三四个地址时分别去ping即可,如果碰到50个地址100地址甚至一整段地址,再去一个一个的ping就很麻烦了,我们可以使用一行命令实现批量…