身份与命名 · commit 8bf606d

名字不能当身份:三层分开,就不会打架

同一个源库被多个津梁连着、容器重启丢了本地状态、两个人都把任务叫 orders —— 全部不冲突

问题拆开看

冲突其实有三种,混在一起才难解:

  1. 源端对象撞名:两个实例在同一源库建了同名 slot / publication → 一个把另一个顶掉。
  2. 状态表串台:按「任务名」做键,两个不同环境里同名任务会互相覆盖位点。
  3. 清理互删:A 实例的 gc 把 B 实例正在用的书签当孤儿删了。
根因:把「用户起的名字」当成了身份。名字是给人看的,不能承担唯一性。

解法:三层身份,各管一件事

层由什么决定干什么不用于
job_keybase32(sha256(源|目标|范围))[:10]任务身份、状态表主键、对象命名、租约排他展示
instance_id首次启动随机 26 位 base32(--instance 可指定)审计、租约持有者、对象所有权、MySQL server_id 派生判断是否同一任务
object namejl_<job_key>_<n>源端 slot / publication / 临时快照展示
一句话:job: 名字只做展示、允许重复;判断「是不是同一个任务」永远看指纹。

四种真实场景的结果

场景结果
两台机器:同一个源 → 不同目标两个 job_key → 两个 slot(jl_aaa… / jl_bbb…),互不干扰
两台机器:同一个源 → 同一个目标同一个 job_key → 租约排他,后启动的直接退出(E1005),不双写
两个任务在同一源上选了不同的表两个 job_key → 两个 publication,互不覆盖
两个任务名字都叫 orders 但源不同两个 job_key,名字重复无所谓

实例 ID 从哪来

1. --instance <id>  或  JINLIANG_INSTANCE     (容器集群显式指定)
2. 本地状态目录 ~/.config/jinliang/instance     (0600)
3. 都没有 → 生成 26 位随机 ID,并登记 _jl_instance

容器重启导致 ID 变更是允许的:旧租约按 TTL 过期,旧对象靠 job_key 仍能找到,gc 用 _jl_object.instance_id 判断归属。

孤儿清理的所有权(这次新增)

gc 要同时满足四条才动手:

  1. 名字符合 jl_<job_key>… 且能在 _jl_object 找到登记;
  2. _jl_lease 里没有该 job_key 的有效租约;
  3. 对象的 instance_id 等于当前实例(默认)—— 跨实例必须 --force 且先打印对方实例信息;
  4. 源端确认不在使用(PG active=false)。
A 实例默认永远删不到 B 实例的书签。

顺带解决的两件

问题现在
MySQL server_id 撞号(会把真正的从库踢下线)由 (instance_id, job_key) 派生到 [2^20, 2^31),确定性且区间足够;不再需要报「请你手挑一个」
两个实例同时启动都想迁移状态表数据库锁串行化:PG pg_advisory_lock / MySQL GET_LOCK,拿不到就等待并重读版本

给了什么证据

internal/state/identity.go + 测试(已进 CI):