Skip to content

seedAutonumber 的播种扫描是「取前 5000 行、不排序、不按前缀过滤」的 MAX —— 超过 5000 行的对象会播种出低于真实 MAX 的号 #6249

Closed

Description

@baozhoutao

发现于 #5495 的前提复核(engine-core 车道),未在该单 PR 内顺手修,按 Prime Directive #10 单独开单。

缺陷

packages/objectql/src/engine.ts 的 seedAutonumber()(:2111 起)用一次普通 find 播种内存计数器:

const rows = await this.find(object, {
  fields: ['id', field],
  limit: 5000,
  context: execCtx,
} as any);

三个问题叠在一起:

  1. limit: 5000 是硬上限,且
  2. 没有 sort —— 取的是驱动返回的任意 5000 行(SQL 上通常是插入顺序的前 5000 行),再在这个窗口里求 max;
  3. 没有按 prefix 下推过滤 —— 前缀筛选是在 JS 侧 if (prefix && !s.startsWith(prefix)) continue; 做的,所以对日期/{field} 分组格式,5000 行窗口还要先被其他 scope 的行占掉。

结果:对象行数一旦超过 5000(或某个 scope 的行排在窗口之外),播种得到的 max 低于库内真实 MAX,计数器从一个已被占用的号段起号。对声明了 unique 的自增号字段,这就是直接发出撞号值。

实测(fake driver 捕获引擎实际下发的查询):

PROBE2 seedQuery={"fields":["id","case_number"],"limit":5000,"object":"crm_case"}

—— 无 sort、无 filters,limit 恒为 5000。

与 #5495 的关系

#5495 记的是另外两个缺陷(计数器不按分区同步、撞号烧号)。本条是第三个、独立的播种正确性缺陷:即使 #5495 的两条都修好,超过 5000 行的对象仍然会播种出低号。#5495 报告的 HotCRM 现场行数远小于 5000,所以本条不是那次风暴的成因,是同一函数里发现的另一个洞。

修法的难点(供分诊参考,不预设结论)

「读真正的 MAX」在引擎层没有现成的便宜写法:

  • sort: [{ field, direction: 'desc' }] + limit: 1 取的是字符串字典序最大值。对定宽补零、且在同一前缀 scope 内的格式,字典序最大 == 数值最大;但对 prefix 为空的 legacy 路径(取整串最后一个数字段),字典序最大不等于数值最大。
  • 因此正确做法多半要按 scope 下推 startsWith(prefix) 过滤 + 定宽假设,或者走 aggregate 的 max。两条都要先决定引擎与驱动之间的契约面,不是一行改动。

影响面

只影响引擎兜底路径(驱动未声明 supports.autonumber)。driver-sql/driver-turso/driver-sqlite-wasm 走各自的 _objectstack_sequences,其播种是 scanMaxNumericTail(SQL 侧 where field like 'prefix%',无 limit),不受本条影响。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: pm:queue + domain:engine-core.

    Landing site. packages/objectql/src/engine.ts, seedAutonumber() ⇒ domain:engine-core. Verified on origin/main@2598216: limit: 5000 is still at engine.ts:2140, in a find with no sort and no filters — the probe in the body reproduces the query the engine actually emits.

    Dedup — NOT a duplicate of #5495. #5495 (pm:queue, domain:engine-core, target:v17) records two other defects in the same area (counter not synced per partition, collision burn). This is a third, independent seeding-correctness defect in the same function: fix both of #5495's and an object past 5000 rows still seeds below the real MAX. Cross-linked both ways.

    ⚠️ Serial constraint — same file and same function as #5495. Same-file work serialises strictly across rounds. Whoever claims either issue must name the other in "Serial constraints cleared"; ideally #5495 lands first (it is on the release board) and this one rebases onto it.

    Why queued rather than escalated. The body flags that the correct fix is "not a one-liner" (lexicographic max vs numeric max; pushdown of the prefix filter vs an aggregate max), but those are internal engine↔driver implementation choices, not a public contract question — no authorable surface and no stored-data shape changes. That is dev design work inside a queued issue. If a dev finds the route genuinely requires a new driver capability declaration, that is the point to return needs_decision.

    Not on the release board — deliberately, with the reasoning recorded. The body establishes the blast radius is the engine fallback path only: drivers that declare supports.autonumber (driver-sql / driver-turso / driver-sqlite-wasm) seed through _objectstack_sequences via scanMaxNumericTail, which pushes where field like 'prefix%' down with no limit and is unaffected. So a shipped SQL deployment is not exposed. If someone establishes that a supported deployment shape does run the fallback with >5000 rows, that changes the answer — raise it and it goes on the board.

    Not affected by the driver freeze (#5499, driver-memory / driver-mongodb): the landing site is the engine, and the drivers named above are the unaffected comparison, not the target.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 7, 2026
  3. self-assigned this
    on Aug 7, 2026
  4. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    ContributorAuthor

    认领(engine-core 席 #6019,会话 session_019Q7oc7ASjh8yxyS3Yz78We,第 17 轮):


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions