job-lifecycle-validity-and-cleanup

职位生命周期:有效性、过期与清理

系统从不假设一条职位还真实存在。每次要把某条职位提供出去时,它都必须重新证明自己仍然有效——而如何证明,取决于它从哪里来。系统按来源类型(origin type)把职位分开,为每一类跑一套不同的检查。检查一旦失败,这条职位不会被打标记、也不会被隐藏,而是当场硬删除,并告知调用方该职位已不存在。

两种来源,两条规则

JobValidityService 是一层建立在策略模式(Strategy)之上的薄路由。它持有一张从来源类型到 JobValidator 的映射;ensureJobIsValid 只做一件事:按职位的 originType 查出对应的校验器并委派过去。构造时注册了两个校验器——"direct""aggregated"——而任何没有注册校验器的来源类型都被默认视为有效,因此新增来源种类无需改动路由本身。另有一个 registerValidator 方法,允许调用方以同样的方式挂上更多校验器(对扩展开放,对修改封闭)。

直接来源(direct)的职位自带过期时间。DirectJobValidator 把职位的 expiresAt 时间戳与当前时间比较:若仍在未来,职位原样放行;若已过去,校验器就删除该职位、通知变更处理器,并抛出 JobInvalidError.expired。这条规则完全是本地的——不发任何网络请求,只做一次日期比较。

聚合来源(aggregated)的职位是从别人的招聘板抓来的,所以它的有效性由外部定义:只要它的源 URL 仍可访问,这条职位就有效。AggregatedJobValidator 先要求 sourceUrl 必须存在——没有源 URL 的职位会被立即删除并报为不可达(unreachable)。否则,它再判断是否到了该重新核查的时候。

重新核查,但不去砸源站

如果每次请求都重新核验每条聚合职位,就意味着每看一次就要一个网络往返,所以校验器给自己加了节流。shouldRecheck 只在三种情况之一成立时返回 true:源从未被核查过、上次核查已在 recheckMinutes 之前、或上次记录的状态不健康(非 2xx)。三者都不成立,就沿用缓存的判定,不发出任何请求。

当确实需要重新核查时,校验器调用源核验器,随后通过 updateValidity 持久化结果——sourceLastCheckedAtsourceLastStatus——好让节流机制下次有新鲜的状态可依据。若核验器报告源已不 OK,职位就被删除,并抛出 JobInvalidError.unreachable

SourceVerificationService 是一个单方法端口:verify(url) 返回 { ok, statusCode? }。其 HTTP 实现发一个 HEAD 请求(很便宜——只取响应头、不取正文),套在带短超时的 AbortController 里,并把 2xx 或 3xx 响应视为 OK。任何抛出的错误——超时、DNS 失败、连接被拒——都会收敛成 { ok: false } 且不带状态码,因此"不可达的源"与"已死的源"在结果上无从区分,这正是设计意图。(注意这里有个细微的不对称:核验器把 2xx–3xx 都算作 "OK",而校验器触发重核查时只把 2xx 算作"健康"——所以一条以 3xx 响应的职位能存活,但会被更积极地反复核查。)

删除,在各处是同一个动作

两个校验器在判定职位无效时都收敛到同一形状:先从仓储删除它,再调用变更处理器的 jobDeleted 钩子。这个钩子正是下游消费者——缓存、搜索索引——得知该行已消失的接缝,于是一次有效性失败会向外扩散,而不是留下过期的副本。这个通知器是可选的;缺席时删除照样发生,只是不再向外扩散。

清扫长尾

逐请求校验能在有人碰到某条已过期或已死的职位时立刻把它清掉,但那些再也没人看的职位会一直滞留。JobCleanupService 就是这条长尾的清道夫。triggerCleanup 被机会性地调用,且是非阻塞的——它记下时间、在后台启动清扫、随即返回;出错只记日志,绝不抛给调用方。它还自我节流为至多每小时一次,因此热路径可以放心随手调用。

后台清扫会删除过期已超过一年的职位;若存在报告仓储,还会删除处理已逾九十天的职位举报。每一遍都会记录清掉了多少行。这是刻意从宽的:近期历史被保留,只有真正陈旧的尾巴才被回收。

为什么是这个形状

这套设计守住一条不变式:无效的职位就是被删除的职位,而不是被打标记的职位。下游不存在"已过期但仍可见"这种需要额外推理的状态。有效性是在使用点惰性检查的,因此在最要紧的时刻应用最新鲜的判定;而每小时一次的清理,只是为了收拾那些惰性检查永远不会再回访的部分。按来源拆分规则,意味着每一类职位都只用真正定义它的那个信号来评判——直接来源看它自报的过期时间,聚合来源看它当下是否可达。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

job-lifecycle-validity-and-cleanup