架构师方案-人事系统的核心-时间轴

1. 什么是时间轴

所有发生的事件,都被按照时间的顺序详细记录,可以通过切换时间追溯到历史上任何一天,展示当时的状态。简单地说,就是给每一条数据更新打上一个时间戳,目的是可追溯。

2. 时间轴的应用

时间轴在人力资源规划模块、招聘与配置模块、培训与发展模块、薪酬管理模块等都有着非常重要的应用。

以薪酬管理模块为例:首先薪酬计算天然就存在时间上的错位,比如我们7月份算的发的是6月份的工资,依据的都是6月份的考勤数据、绩效数据、扣款数据。比如奖金的发放需要依据的是前几个月的数据。年终奖的发放则需要依据全年发生的数据进行计算。

3. 设计时间轴需要考虑的问题

数据量的激增: 好比原来围绕员工有100条数据,升维以后就可能增加几倍到几十倍,这样,保持原有的数据存储和查询性能就需要更加先进的数据处理技术;

对于数据的查询和应用: 特别是涉及到关联性的维护,就需要逐一分辨查询的业务逻辑,是应该使用最新的还是某个具体时间点的数据,需要考虑的业务逻辑自然远远比仅仅记录当前值要复杂;

用户体验: 怎么样能够让用户几乎无感知地自动保存历史、查询引用历史,同时还能在需要时完成必要的交互,保持时间轴数据的完整性和一致性,更加需要精密的设计。

如下图,腾讯使用PeopleSoft(2013年)。除了腾讯,Google、IBM、Yahoo...也在使用SAP或Oracle PeopleSoft的HR系统

4. 实现方案

4.1 定时快照snapshot模式

比如数据每天凌晨定时备份昨天数据

以人事为例,人事一天会多次调整修改员工数据。但是,我们可能只会用到最后一次数据作为当天数据进行核算。

sql 复制代码
#员工实时表
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键标识',
  `employee_no` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '员工工号',
  `name` varchar(100) COLLATE utf8mb4_bin DEFAULT '' COMMENT '姓名',
  `dept_id` int(11) DEFAULT NULL COMMENT '部门id',
  `job_id` int(11) DEFAULT NULL COMMENT '岗位id',
   #...员工其他信息
  `status`tinyint(2) DEFAULT '0' COMMENT '-1:离职、 0:在职',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_employee_no` (`employee_no`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='员工最新数据表'

凌晨备份当前最新数据

sql 复制代码
#员工每天备份表
CREATE TABLE `t_user_bk` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键标识',
  `employee_no` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '员工工号',
  `name` varchar(100) COLLATE utf8mb4_bin DEFAULT '' COMMENT '姓名',
  `dept_id` int(11) DEFAULT NULL COMMENT '部门id',
  `job_id` int(11) DEFAULT NULL COMMENT '岗位id',
   #...员工其他信息
  `status` tinyint(2) DEFAULT '0' COMMENT '-1:离职、 0:在职',
  `bk_day_no` int(8) DEFAULT NULL COMMENT '备份时间:以天为单位,时间格式: yyyymmdd',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_employee_no_bk_day_no` (`employee_no`,`bk_day_no`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='员工每天备份表'

4.2.2 使用示例

csharp 复制代码
select * from t_user_bk where bk_day_no>20240101 or bk_day_no<20240112

4.2.3 数据维护方法

  • 每天备份最新数据
  • 异常数据额外清洗

4.2 时间区间模式(SAP使用)

4.2.1 设计原则
  • 每个信息类型记录都有开始时间、结束时间
  • 每个信息类型都受时间特性类型的控制

时间区间字段

arduino 复制代码
  `start_date` datetime DEFAULT NULL COMMENT '开始日期',
  `end_date` datetime DEFAULT NULL COMMENT '结束日期',
4.2.2 使用示例
sql 复制代码
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键标识',
  `employee_no` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '员工工号',
  `name` varchar(100) COLLATE utf8mb4_bin DEFAULT '' COMMENT '姓名',
  `dept_id` int(11) DEFAULT NULL COMMENT '部门id',
  `start_date` datetime DEFAULT NULL COMMENT '开始日期',
  `end_date` datetime DEFAULT NULL COMMENT '结束日期',
  `status` tinyint(2) DEFAULT '0' COMMENT '-1:离职、 0:在职',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_employee_no` (`employee_no`)
) ENGINE=InnoDB AUTO_INCREMENT=130243 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='员工入职离职记录表'
csharp 复制代码
select * from t_user where start_date < '2022-12-31' and end_date < '2023-03-01';
4.2.3 数据维护方法
  • 添加最新或未来数据时,需将最后一条数据的结束时间改为新数据开始时间,新数据结束时间改为无穷大
  • 插入历史中间数据时,需将有冲突的历史数据开始结束时间做避让,空开新数据的时间区间。如果新数据将历史数据完整包含,历史数据将会被完全覆盖(对于时间特性1类型的数据)

4.3 生效字段模式(Oracle PeopleSoft使用)

4.3.1 设计原则

通过生效日期effect_date、生效状态effect_status、生效序号effect_seq三个简单的字段把系统内的任何一条信息在过去、现在、未来三个截面上构建了连续的数据模型,并且不会影响到数据的使用、展示、和对外提供。

日期类型 描述
当前日期-当前数据 当前日期指的是过去最接近今天(含今天)的日期,具有实时性,与当前日期对应的数据行即为单签数据。当前日期由有且仅有一个,当前数据也有且仅有一行。
历史日期-历史数据 小于当前日期的所有日期都是历史日期,与历史日期对应的数据行即为历史数据。显然历史数据可以有多行。
将来日期-将来数据 大于当前日期的所有日期都是将来日期,与将来日期对应的数据行即为将来数据。显然将来数据可以有多行。
通过生效日期,我们可以按时间线准确地维护数据。这样就可以回溯历史信息,同时可以安排将来的事件。

例如,假设今天是2024年4月25日,人事创建一个在2024年5月1日生效的新部门A。但是将来日期,普通员工看不到此记录。

4.3.3 使用示例
sql 复制代码
CREATE TABLE `t_department` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `dept_code` varchar(30) NOT NULL COMMENT '部门编号',
  `parent_dept_code` varchar(30) NOT NULL DEFAULT '' COMMENT '父部门编号',
  `effect_date` date NOT NULL COMMENT '生效日期',
  `effect_seq` int(11) NOT NULL COMMENT '生效序号',
  `dept_name` varchar(30) NOT NULL COMMENT '部门名称',
  `leader_id` varchar(20) NOT NULL COMMENT '部门负责人工号',
  `status` int(11) NOT NULL COMMENT '部门状态0:失效,1:生效',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=latin1
css 复制代码
#查询某个时间所有生效部门
select *
from t_department a
where a.effect_date =
      (select max(b.effect_date) from t_department b where b.dept_code = a.dept_code and b.effect_date <= '2024-12-01')
  and a.effect_seq =
      (select max(c.effect_seq) from t_department c where c.dept_code = a.dept_code and c.effect_date = a.effect_date)
  and a.status = 1;

4.3.3 数据维护方法
  • 添加或插入数据时,按期望的生效日期插入,如果当天已存在记录,则按序号添加,或在老序号中间插入,未来数据和历史数据同理。

5.方案对比

方案 优点 缺点
定时快照snapshot模式 能快速查看某一天的所有数据 全量数据备份数据冗余过多,处理未来事件比较麻烦,修复数据可能要前后的天数比较多
时间区间模式 sql查询时,只需按所需的日期在开始结束范围内查询,即可确定当时唯一的一组数据 因其对每条数据的开始结束时间都有不可重叠的强校验,因此编辑或者修复任何一条数据,都需要同时修改相邻的数据,这增加系统运维的复杂度
生效字段模式 维护时只需要对应按生效日期添加数据、修改数据、删除数据,可以较方便地对历史回溯数据进行修改。 sql查询时,有effect_date和effect_seq两个维度需要取max,sql语句取值较复杂,且容易出错。

6.参考

事业窗-人力资源数字化的那些黑科技(一):时间轴
得物-人事系统时间轴设计的演化历程
小彭同学-你为什么做不好人力资源系统?(二)------时间轴在HR场景中的应用
Crystal-时间轴是什么?怎么用

相关推荐
程序员爱钓鱼2 分钟前
GoWeb开发核心库: net/http深度指南
后端·面试·go
程序员Terry3 分钟前
Java 代理模式:从生活中的"中介"到代码中的"代理人"
后端·设计模式
野犬寒鸦4 分钟前
JVM垃圾回收机制深度解析(G1篇)(垃圾回收过程及专业名词详解)(补充)
java·服务器·开发语言·jvm·后端·面试
白宇横流学长4 分钟前
基于SpringBoot实现的信息技术知识赛系统设计与实现【源码+文档】
java·spring boot·后端
yhyyht6 分钟前
Maven命令学习记录(一)
后端
Soofjan8 分钟前
Go channel源码
后端
Soofjan10 分钟前
channel
后端
SimonKing15 分钟前
OpenClaw,再见!
java·后端·程序员
大阿明27 分钟前
Spring BOOT 启动参数
java·spring boot·后端
hutengyi32 分钟前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端