关于pd tso 一次性获取超过 1 << 18 个tso时失败的问题

【TiDB 使用环境】生产环境 /测试/ Poc
【TiDB 版本】
【操作系统】
【部署方式】云上部署(什么云)/机器部署(什么机器配置、什么硬盘)
【集群数据量】
【集群节点数】
正在上传:f706dc6650e332fef50b2c2e3a38b74.png…

【问题复现路径】
官方文档如下:


描述中说减小tso-update-physical-interval 可以获取超过262144个tso,但是从代码中来看如果超过了262144时会一直失败,代码如下:

tso/tso.go line 354 fn:func (t *timestampOracle) getTS(ctx context.Context, count uint32) (pdpb.Timestamp, error)
...
		if resp.GetLogical() >= maxLogical {
			log.Warn("logical part outside of max logical interval, please check ntp time, or adjust config item `tso-update-physical-interval`",
				logutil.CondUint32("keyspace-group-id", t.keyspaceGroupID, t.keyspaceGroupID > 0),
				zap.Reflect("response", resp),
				zap.Int("retry-count", i), errs.ZapError(errs.ErrLogicOverflow))
			t.metrics.logicalOverflowEvent.Inc()
			time.Sleep(t.updatePhysicalInterval)
			continue
		}
		// In case lease expired after the first check.
		if !t.member.IsLeader() {
			return pdpb.Timestamp{}, errs.ErrGenerateTimestamp.FastGenByArgs(fmt.Sprintf("requested %s anymore", errs.NotLeaderErr))
		}
		return resp, nil
	}

如果超过了1 << 18个tso resp.GetLogical() >= maxLogical 总是会失败,所以无论tso刷新时间是多少,获取总会失败,有大佬看看这个是个bug吗
【遇到的问题:问题现象及影响】
【资源配置】进入到 TiDB Dashboard -集群信息 (Cluster Info) -主机(Hosts) 截图此页面
【复制黏贴 ERROR 报错的日志】
【其他附件:截图/日志/监控】

并没有这么说吧,意思应该是更新周期内最多提供262144个。

比如50秒内获取,如果更新周期是50s,那50秒内最多提供262144个;
如果更新周期是25s,那50秒内最多提供262144 x 2个,前25秒提供262144个,后25秒提供262144个。

1 个赞


emm…那是我理解问题?

:yum:我是这么理解的。像上面的例子,周期改成25秒之后,50秒内能获得tos就翻了一倍,不是一次请求的翻了一倍。

不太对吧 :grinning:,原文是默认的一个时钟周期内最多可以提供262144个,如果需要获取更多,也就是超过262144个…你的解释没问题,应该是文档描述不太严谨,代码中也没找到限制的代码,这个也算是一个缺陷 :joy:

:thinking:你这么说也有道理,可以在 建议反馈 版块提交一个。

提了提了

1 个赞

机制:

  • TiDB 的 TSO 机制是将时间戳分为物理部分和逻辑部分。
  • 在一个物理时间窗口内(由 tso-update-physical-interval 控制),逻辑部分最大值为 262144。
  • 超过后会 sleep,等下一个物理时间窗口再分配。

如果业务场景下出现这个瓶颈,可以缩短 tso-update-physical-interval 配置,让物理时间窗口更短,提升单位时间可分配的 TSO 总数

如果仍然无法满足需求,可以向官方反馈,建议优化逻辑或提升该参数上限。·
我觉得文档没说错,单位时间内的 TSO 总数的确增加了

1 个赞

你的说法也就是一次性获取的tso不能超过262144个咯 :hushed:

如果超过了就要分批次获取

是这个时间段内最多获取这么多,你把这个时间段调到 1ms,那就比默认的 50ms 多了50倍。说真的,你硬件能让你把这个限制打破吗?感觉现在挺难的。

1 个赞

50ms提供262144个,和1ms提供262144个的区别。

最大都是262144个,但是周期可以缩短到1ms。

1 个赞

他在设想这个场景,即使是 UUID 一次性生成这么多也要吃很多资源拉
还要支持时间兼容和单调性,目前只有UUIDv7 可以,感觉也会很困难,所以这个场景的设定目标并不明确。

2 个赞

我是突然看到这个代码,比较疑惑,感觉这个代码健壮性不高,去翻了一下官网,发现解释也摸棱两可,所以提了这个问题讨论;你在建议中提到的,一次性获取1 << 18个其实在理论上是可以实现的,相当于 逻辑计数 每次加到 1<< 18个 那么物理时间就得进一位,只要保持连续这个时间片段连续(没有其他线程从中取走一部分tso),那么理论上一次可以获取1 << 64个(只是要等待的时间很长) 。只是现在的代码并没这样做,且count在pd上是没有做检查的代码如下:

// HandleTSORequest forwards TSO allocation requests to correct TSO Allocators of the given keyspace group.
func (kgm *KeyspaceGroupManager) HandleTSORequest(
	ctx context.Context,
	keyspaceID, keyspaceGroupID uint32,
	count uint32,
) (ts pdpb.Timestamp, curKeyspaceGroupID uint32, err error) {
	if err := checkKeySpaceGroupID(keyspaceGroupID); err != nil {
		return pdpb.Timestamp{}, keyspaceGroupID, err
	}
	allocator, _, curKeyspaceGroupID, err := kgm.getKeyspaceGroupMetaWithCheck(keyspaceID, keyspaceGroupID)
	if err != nil {
		return pdpb.Timestamp{}, curKeyspaceGroupID, err
	}
	err = kgm.checkTSOSplit(curKeyspaceGroupID)
	if err != nil {
		return pdpb.Timestamp{}, curKeyspaceGroupID, err
	}
	err = kgm.checkGroupMerge(curKeyspaceGroupID)
	if err != nil {
		return pdpb.Timestamp{}, curKeyspaceGroupID, err
	}
	// If this is the first time to request the keyspace group, we need to sync the
	// timestamp one more time before serving the TSO request to make sure that the
	// TSO is the latest one from the storage, which could prevent the potential
	// fallback caused by the rolling update of the mixed old PD and TSO service deployment.
	err = kgm.markGroupRequested(curKeyspaceGroupID, func() error {
		return allocator.Initialize()
	})
	if err != nil {
		return pdpb.Timestamp{}, curKeyspaceGroupID, err
	}
	ts, err = allocator.GenerateTSO(ctx, count)
	return ts, curKeyspaceGroupID, err
}

所以对于count的边界值检查是没有的。

然后 你说一个周期调小,就算是1ms一个周期内也只能获取 1<< 18个 只是在多个周期变小了,获取速度变快了而已

好像很有道理

你要实现进一位的效果一样要考虑并发安全。
你把未来的tso分掉了,那未来那个时间点来请求tso的怎么分呢?到时候继续进位?而且并发越高这个可能性也越大。
话又说回来,如果能提高请求tso的并发,多个并发每次取1<<18个,对提高tso的吞吐来说,这又有什么特别难以接受的点么?

这里面有一个误解,pd的tso不是取未来的时间,而是当前剩余的时间窗口不足以分配count个,那么会继续等待 :joy:,知道能够分配count个,并不是取未来的。

你可以看看这两个,有些特殊情况会导致pd提前分配了tso。物理时间已经提前分掉了,又要保证tso的单调递增,才会有每到1<<18就进一位。慢慢的推进物理时间,等着实际的物理时间追上来。

总之,结论都是逻辑时间戳就是1<<18个,你不改tso编码方式,逻辑位就这么多。不改tso编码方式的情况下,选择进位,其本质就是提前把物理时间分掉了。

1 个赞