暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

数据库学习Q&A 065:在OceanBase数据库中,资源隔离有哪些类型?

内存隔离

节点的内存资源实际上不支持超卖,引入内存超卖反而会导致租户工作不稳定,从 V4.0.0 版本开始,OceanBase 数据库不再支持内存超卖。废弃 MIN_MEMORY 和 MAX_MEMORY 配置,采用 MEMORY_SIZE 参数代替。

CPU 隔离

在 OceanBase 数据库 V3.1.x 版本之前,主要通过控制线程数来控制 CPU 的占用;在 OceanBase 数据库 V3.1.x 及之后的版本,允许配置 cgroup 来控制 CPU 的占用。

线程分类

节点会启动很多不同功能的线程,详细的分类请参见 observer 线程,本节按照最粗略的标准可以分为以下两类:

  • 一类是处理 SQL 和事务提交的线程,统称为租户工作线程。

  • 其余的是处理网络 IO、磁盘 IO、Compaction 以及定时任务的线程,统称为后台线程。

在当前 V4.0.0 版本,租户工作线程和大部分后台线程是分租户的,网络线程是共享线程的。

基于线程数的租户工作线程的 CPU 隔离

Unit 的 CPU 隔离是通过一个 Unit 的活跃租户工作线程数实现的。

由于 SQL 执行过程中可能会有 IO 等待、锁等待等,所以一个线程无法用满一个物理 CPU,故在缺省配置下,OBServer 节点会给每个 CPU 启动 4 个线程,4 这个倍数可以通过配置 cpu_quota_concurrency 来控制。这就意味着如果一个 Unit 的 MAX_CPU 是 10,那么它能同时运行的活跃线程是 40,最大物理 CPU 的占用是 400%。

基于 cgroup 的租户工作线程的 CPU 隔离

开启 cgroup 后最大的变化是不同租户的工作线程放到不同的 cgroup 目录内,租户间的 CPU 隔离效果会更好。最后的隔离效果如下:

  • 如果一个 OBServer 节点上只有一个租户负载很高,其余租户比较空闲,那么这个负载高的租户的工作线程可以用满与线程数相同数目的物理 CPU。

  • 延续上面的场景,如果有多个空闲的租户的负载上升了,导致物理 CPU 不够了,cgroup 会按照权重分配时间片。

后台线程的 CPU 隔离

  • V4.0.0 版本的后台拆分了租户,每个租户下有对应的线程数控制。

  • 为了更好的隔离效果,后台线程也会放到不同的 cgroup 目录内,实现租户间隔离,租户内和工作线程隔离。

大查询处理

我们认为相比于大查询,让短查询尽快返回对用户更有意义,即大查询的查询优先级更低,当大查询和短查询同时争抢 CPU 时,系统会限制大查询的 CPU 使用。

当一个线程执行的 SQL 查询耗时太长,这条查询就会被判定为大查询, 一旦判定为大查询,执行大查询的线程会等在一个 Pthread Condition 上,这样就为其它的租户工作线程让出了 CPU。

具体实现上,OBServer 节点在代码中插入了很多检查点,租户工作线程在运行过程中会通过检查点定期检查自己的状态,如果判断应该挂起,那么线程就会等待在一个 Pthread Condition 上,等到合适的时机再被唤醒。

如果同时有大查询和小查询,大查询最多占用 30% 的租户工作线程,30% 这个百分比值可以通过配置项 large_query_worker_percentage 来设置。

有两点需要说明:

  • 当没有小查询的时候,大查询可以用到 100% 的租户工作线程。只有当同时有大查询和小查询时,30% 的比例才生效。

  • 一个租户工作线程因为执行大查询被挂起时,作为补偿,系统可能会新创建一个租户工作线程,但是总的租户工作线程数不能超过 MAX_CPU 的 10 倍,10 这个倍数可以通过配置项 workers_per_cpu_quota 来设置。

提前识别大查询

由于节点挂起一个大查询线程,就会启动一个新的租户工作线程, 但是如果有大量大查询涌入,OBServer 节点新创建的线程还是被用来处理大查询,很快达到租户工作线程数上限,在这批大查询消耗完之前就没有机会再处理短查询了。

为了优化这个场景,OBServer 节点会在 SQL 开始执行之前预判它是不是大查询,预判的本质就是估计 SQL 的执行时间。预判主要依据以下假设场景:如果两条 SQL 的执行 Plan 是一样的,可以猜测它们的执行时间也是相似的,这样就可以用 Plan 最近的执行时间来判断 SQL 会不会是大查询。

如果某条 SQL 被预判为大查询,那么该查询就会被放入一个特殊的大查询队列,其线程会被释放,系统就会接着执行后面的请求了。

监控项和日志

在日志中搜索 dump tenant info 关键字,可看到租户的规格、线程、队列及请求统计等信息,且这条日志每个租户每 30s 打印一次。

日志示例:

grep 'dump tenant info.tenant={id:1002' log/observer.log | sed 's/,/,\n/g'

[2022-07-20 14:55:40.774143] INFO  [SERVER.OMT] run1 (ob_multi_tenant.cpp:1993) [80700][MultiTenant][T0][Y0-0000000000000000-0-0] [lt=621] 
dump tenant info(tenant={id:1002, //租户 ID
tenant_meta:{unit:{tenant_id:1002, //tenant_meta 是租户元数据包括 Unit 配置信息和SuperBlcok 以及 create_status
unit_id:1001, //Unit ID
has_memstore:true, //是否有 MemTable
unit_status:"NORMAL", //Unit 的状态: UNIT_NORMAL/UNIT_MIGRATE_IN/UNIT_MIGRATE_OUT/UNIT_MARK_DELETING/UNIT_WAIT_GC_IN_OBSERVER/UNIT_DELETING_IN_OBSERVER/UNIT_ERROR_STAT
config:{unit_config_id:1003, //Unit 的配置 ID
name:"2c2g", //Unit 配置的名字
resource:{min_cpu:2, //Unit 配置的最小 CPU 数
max_cpu:2, //Unit 配置的最大 CPU 数
memory_size:"1.5GB", //Unit 配置的内存大小
log_disk_size:"5.4GB", //Unit 配置的日志盘大小
min_iops:20000, //Unit 配置的最小磁盘 IOPS
max_iops:20000, //Unit 配置的最大磁盘 IOPS
iops_weight:2}}, //Unit 配置的 IOPS 权重
mode:1, //CompatMode 0:MYSQL 1:ORACLE
create_timestamp:1658298418435426, //Unit 的创建时间
is_removed:false}, //该 Unit 是否被标记删除
super_block:{tenant_id:1002, //ObTenantSuperBlock 信息
replay_start_point:ObLogCursor{file_id=1, //租户 slog 的日志回放点
log_id=440,
offset=343322},
ls_meta_entry:[140](ver=0, //租户 ls meta 的 slog checkpoint 入口点
mode=0,
seq=139),
tablet_meta_entry:[141](ver=0, // 租户tablet meta的slog checkpoint的入口点
mode=0,
seq=140)},
create_status:1}, // 租户的创建状态: CREATING = 0,CREATE_COMMIT=1,CREATE_ABORT=2,DELETING=3,DELETE_COMMIT=4
unit_min_cpu:"2.000000000000000000e+00", //最小cpu核数,保证提供
unit_max_cpu:"2.000000000000000000e+00", //最大cpu核数,限制上限
slice:"0.000000000000000000e+00",
slice_remain:"0.000000000000000000e+00",
token_cnt:8, //调度器分配的token数,一个token会转换为一个工作线程
sug_token_cnt:8,
ass_token_cnt:8, //租户当前确认的token数(根据token_cnt确认,一般两者相等)
lq_tokens:2, //Large Query token个数,根据token_cnt乘以大请求比例设置
used_lq_tokens:0, //当前持有LQ Token的Worker数
stopped:false, //租户unit是否正在删除
idle_us:6076629, //一轮(10秒)中工作线程空闲的总时间和, 所谓空闲实际只统计了等待队列的时间
recv_hp_rpc_cnt:1, //租户累计收到不同级别的rpc请求数,hp(High), np(Normal), lp(Low)
recv_np_rpc_cnt:5,
recv_lp_rpc_cnt:0,
recv_mysql_cnt:24, //租户累计收到的mysql请求数
recv_task_cnt:1, //租户累计收到的内部任务数
recv_large_req_cnt:0, //租户累计预判的大请求数,只会递增,不会清零。实际是重试的时候递增的。
tt_large_quries:0, //租户累计处理的大请求数, 只会递增,不会清零。实际是打点check的时候递增的。
pop_normal_cnt:1024555,
actives:8, //活跃工作线程数,一般和workers相等,它们的差包含:租户工作线程缓存+带工作线程的大请求缓存
workers:8, //租户持有的工作线程数, 实际就是workers_这个list的size。
nesting workers:7, //租户持有的嵌套请求专用线程数,共7个线程对应7个嵌套层级。
lq waiting workers:0, //处于被判定为处理大查询,并被挂起等待调度的工作线程
req_queue:total_size=0 queue[0]=0 queue[1]=0 queue[2]=0 queue[3]=0 queue[4]=0 queue[5]=0 , //不同优先级的工作队列,数字越小优先级越高
large queued:0, //当前预判出的大请求个数
multi_level_queue:total_size=0 queue[0]=0 queue[1]=0 queue[2]=0 queue[3]=0 queue[4]=0 queue[5]=0 queue[6]=0 queue[7]=0 , //存放嵌套请求的工作队列,1~7对应7个嵌套层级(queue[0]暂时不用,queue[5]也存放innersql请求)。
recv_level_rpc_cnt:cnt[0]=0 cnt[1]=0 cnt[2]=0 cnt[3]=0 cnt[4]=0 cnt[5]=0 cnt[6]=0 cnt[7]=0 ,
group_map:group_id = 1, //group_map后面跟着每个group_id对应group的线程和排队情况
queue_size = 0, //group队列排队情况
recv_req_cnt = 13526, //group累计push到队列请求数
pop_req_cnt = 13526, //group线程累计pop出的请求数
token_cnt = 2, //group分配的token数,一个token会转换为一个工作线程
min_token_cnt = 2,
max_token_cnt = 2,
ass_token_cnt = 2 , //group当前确认的token数(根据token_cnt确认,一般两者相等)
rpc_stat_info: pcode=0x150a:cnt=1489 pcode=0x150b:cnt=1091}) //租户一段时间内收到的最多的rpc pcode,统计周期10秒,最多打印前五
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论