Drizzle ORM 连接 TiDB Cloud Serverless 使用 $returningId() 出错求助

我们的数据库是 TiDB Cloud serverless,产品框架 Hono,使用 @drizzle-orm/tidb-serverless + @tidbcloud/serverless 连接数据库。产品部署在 Cloudflare Worker 上面。

之前一切正常,近期(不超过2周)突然遇到 worker memory exceed limits 错误,只有在生产环境上才会稳定复现。因为 Cloudflare Worker 只有 128MB 内存,本地内存比较富裕。经过一段时间的排查(二分切割法),最终基本确定是下面这段代码的问题:

    const uid = (
      await this.client
        .insert(schema.users)
        .values({
          externalId: externalId,
          firstName: firstName,
          lastName: lastName,
          email: email.toLowerCase(),
        })
        .$returningId()
    )[0];
    return uid.id;

$returningId() 去掉,然后单独写一个 query 就没问题了。

目前虽然问题已经解决,但我担心是不是没找对地方,所以发出来请教一下大家,看有没有人遇到过类似的问题,或者听说过类似的问题。

1 个赞

没有遇到过

1 个赞

TiDB Cloud serverless 结合 drizzle 的 $returningId () 可能在 Worker 中触发内存泄漏。

1 个赞

要不拆分查询规避 试试

1 个赞

有没有找到问题原因啊

好像找到了。

因为 TiDB 的 id 不是连续的,所以 $returningId() 的结果是一个巨大的数组,从 1 开始直到一个奇怪的数字(好像也不是最近插入的 id),于是就超了内存。没有办法解决,只能分开,插入一次查询一次。

二分法不错,看起来是Drizzle ORM 的 bug

const result = await this.client.execute(
INSERT INTO users (externalId, firstName, lastName, email) VALUES (?, ?, ?, ?) RETURNING id,
[externalId, firstName, lastName, email]
); 看下爆内存不

我理解你的场景是自增主键的场景
代码在这里:drizzle-orm/drizzle-orm/src/tidb-serverless/session.ts at a086f59fba7f46f3a077893ba912c99e91eaa760 · drizzle-team/drizzle-orm · GitHub

根据这个代码,理论返回的 $returningId() 的数据量是你实际插入行数,不应该是个大数组才对。
但它这里有个 bug,插入多行的时候,除了第一个 id,后续的 id 都可能是错的,因为 tidb cloud id 不一定连续。会不会是后续基于返回的 id 处理的时候导致的问题?

有可能,我没有认真考虑这里,也没有看源码,但是只看返回结果,像是我猜测的那样。

而且这个问题在表小的时候不明显(否则早就测出来了),而是在上线一段时间之后才暴露出来(表内容多了),所以我才做上面的猜测。

如果你插入若干条,实际却返回了极多的 ids, 我觉得可能是产生了溢出导致的。

drizzle-orm 源码遍历 lastInsertId 到 lastInsertId + affectedRows.
但 serverless-js 的 lastInsertId 存在 overflow 的问题:lastInsertId not correct with AUTO_RANDOM id · Issue #65 · tidbcloud/serverless-js · GitHub

你可以检查发生 OOM 时的数据中,id 实际值是否超过 MAX_SAFE_INTEGER,即 9007199254740991.

一个无限循环的例子:

let lastInsertId: number = Number.MAX_SAFE_INTEGER+1;
let rowAffected: number = 5;
let count = 0 
for (let i = lastInsertId; i < lastInsertId + rowAffected; i++) {
  count = count +1 
  if (count > 100) {
    console.log("stop the loop", count);
    break
  }
}

块引用

现场已经没有了,不过我印象里不是。

我印象里得到的是数组,[{id: 1}] 开始,结束的 id 不是我插入的 id,比我插入的 id 大,但是大的也不多。