首批通过分布式安全可靠测评,为关键业务系统打造
CPU 与内存超卖问题简介
更新时间:2026-05-12 08:31
本文详述 OceanBase 数据库中 CPU 和内存超卖的风险与收益。
适用版本
OceanBase 数据库 V4.0 之前的版本。
CPU 和内存的超卖
- 如果一个 OBServer 上放置的所有 Unit 的 MAX_CPU 的值加起来超过这个 OBServer 的可用 CPU 总数,则表示该 OBServer 上的 CPU 是超卖的。
- 同理,如果所有 Unit 的 MAX_MEMORY 加起来超过该 OBServer 上可以分配的 Memory,则表示该 OBServer 上的 Memory 是超卖的。
- OBServer 资源超卖的比例受配置项
resource_hard_limit的控制,假设resource_hard_limit=200,那就意味着可以超卖成 2 倍,即 16 个 CPU 可以超卖成 32 个,16G 的内存可以超卖成 32G。 - 超卖是通过牺牲稳定性来获取更高的资源利用率的方案,是否要开启超卖需要根据应用特性和应用的 SLA 要求仔细评估。例如,一个比较适合的场景就是业务的研发测试环境。
注意
即使在分配资源的时候没有超卖,在实现 CPU 资源隔离时,Unit 可以使用的 CPU 并没有严格限制在 MAX_CPU 的值, 这个意义上的 CPU 默认是超卖的。更多 CPU 超卖的介绍信息可参见本文中的 CPU 隔离 。
内存隔离
- 内存是个刚性的资源,也就是内存一旦被占用了,很难保证能快速收回。所以内存是不适合超卖的。
- Unit 的内存上限由 MAX_MEMORY 决定,Unit 的 MIN_MEMORY 仅用来决定 Block Cache 的挤占时机,除此之外没有其它用途。
- 生产系统中推荐将 MIN_MEMORY 和 MAX_MEMORY 设置为相等的值。
CPU 隔离
相比于内存,CPU 是更弹性的资源,所以 OBServer 目前允许一个 Unit 使用的物理 CPU 超过分配给它的配额。
在 OceanBase 数据库 V3.2.3 及之前版本,主要通过控制线程数来控制 CPU 的占用。
线程分类
- OBServer 会启动很多不同功能的线程,细化的分类请参考 observer 线程的相关章节,本节按照最粗略的标准可以分为以下两类:
- 一类是处理 SQL 和事务提交的线程,统称为 Worker 线程。
- 其余的是处理网络 IO、磁盘 IO、Compaction 以及定时任务的线程。
- Worker 线程是分租户的,非 Worker 线程是所有租户共享的。本节的租户 CPU 隔离都是针对 Worker 线程的。
基于线程数的 CPU 隔离
- Unit 的 CPU 隔离是通过一个 Unit 的活跃 Worker 线程数实现的。
- 由于 SQL 执行过程中可能会有 IO 等待、锁等待等,所以一个线程无法用满一个物理 CPU。故在缺省配置下,OBServer 会给每个 CPU 启动 4 个线程,4 这个倍数可以通过配置
cpu_quota_concurrency来控制。这就意味着如果一个 Unit 的 MAX_CPU 是10,那么它能同时运行的活跃线程是 40。
基于 Cgroup 的 CPU 隔离
- 开启 Cgroup 后最大的变化是不同租户的 Worker 线程放到不同的 Cgroup 目录内,租户间的 CPU 隔离效果会更好。
- 最后的隔离效果如下:
- 如果一个 OBServer 上只有一个租户负载很高,其余租户比较空闲,那么这个负载高的租户的 CPU 也会受到 MAX_CPU 的限制。
- 延续上面的场景,如果有多个空闲的租户的负载上升了,导致物理 CPU 不够了,Cgroup 会按照权重分配时间片。
大查询处理
我们认为相比于大查询,让短查询尽快返回对用户更有意义,即大查询的查询优先级更低,当大查询和短查询同时争抢 CPU 时,系统会限制大查询的 CPU 使用。
当一个线程执行的 SQL 查询耗时太长,这条查询就会被判定为大查询, 一旦判定为大查询,执行大查询的 Worker 会等在一个 Pthread Condition 上,这样就为其它的 Worker 线程让出了 CPU。
具体实现上,OBServer 在代码中插入了很多检查点,Worker 线程在运行过程中会通过检查点定期检查自己的状态,如果判断应该挂起,那么线程就会等待在一个 Pthread Condition 上,等到合适的时机再被唤醒。
如果同时有大查询和小查询,大查询最多占用 30% 的 Worker 线程,30% 这个百分比值可以通过配置项 large_query_worker_percentage 来设置。
有两点需要说明:
- 当没有小查询的时候,大查询可以用到全部的 Worker 线程。只有当同时有大查询和小查询时,30% 的比例才生效。
- 一个 Worker 因为执行大查询被挂起时,作为补偿,系统可能会新创建一个 Worker 线程。但是总的 Worker 线程不能超过 MAX_CPU 的 10 倍,10 这个倍数可以通过配置项
workers_per_cpu_quota来设置。
提前识别大查询
由于 OBServer 挂起一个大查询线程,就会启动一个新的 Worker 线程。但是如果有大量大查询涌入,OBServer 新创建的线程还是被用来处理大查询,很快达到 Worker 数上限,在这批大查询消耗完之前就没有机会再处理短查询了。为了优化这个场景,OBServer 会在 SQL 开始执行之前预判它是不是大查询,预判的本质就是估计 SQL 的执行时间。预判主要依据以下假设场景:如果两条 SQL 的执行 Plan 是一样的,可以猜测它们的执行时间也是相似的,这样就可以用 Plan 最近的执行时间来判断 SQL 会不会是大查询。如果某条 SQL 被预判为大查询,那么该查询就会被放入一个特殊的大查询队列,其 Worker 线程会被释放,系统就会接着执行后面的请求了。