# 如何管理 OceanBase 租户

## OceanBase 租户原理 

生产环境 OceanBase 集群通常是三副本架构，有三个 ZONE，每个 ZONE 至少一台主机，集群架构是 `N-N-N` (N>=1) 。OceanBase 集群将所有节点的资源（主要是 CPU 、内存和磁盘空间）聚合成大的资源池子后，再二次分配出租户。租户就是逻辑实例，每个租户对应一部分资源（资源主要是CPU、内存）。租户的资源也叫资源池，由每个 ZONE 里一个小的资源池组成。通常三个 ZONE 的资源池大小规格是一样的（可以不一样）。每个 ZONE 里的资源池由至少一个资源单元组成，每个资源单元只能从一个 节点上分配资源。资源单元规格 * 资源单元数 就是租户的可以利用的最大资源。

![](media/16321130545360/16332475304698.jpg)

如上图：
+ 集群的架构是 `3-3-3` 。每个节点总资源是 `30C 200G 内存` ，集群总资源是 `270C 1800G 内存`。
+ 默认租户 `sys` 的架构是 `1-1-1`，每个 ZONE 里 的资源单元大小是 `5C20G` 。
+ 业务租户 `t_trade` 的架构是 `1-1-1`，每个 ZONE 里 的资源单元大小是 `20C40G` 。
+ 业务租户 `t_pay` 的架构是 `2-2-2` ，每个 ZONE 里 的资源单元大小是 `20C100G` ，每个 ZONE 有两个资源单元，分布在 2 个节点上。

每个租户的数据就从租户的资源池对应的资源单元中分配。租户每个 ZONE 的资源单元数就组成租户的架构：`n-n-n` ( 1<=n<=N)。租户的数据管理单位是分区，分区是表的子集。普通表是一个分区，分区表有多个分区。每个分区有三个副本，分布在三个 ZONE 里，具体就在租户的某三个资源单元中。

![](media/16321093510830/16332484797678.jpg)

说明：
+ 表 `t1` 是普通表，只有一个分区，标识是 `t1(p0)`，有三个副本，分别在资源单元 UNIT7, UNIT8, UNIT9 上。
+ 表 `t3` 是分区表，有三个分区，标识是 `t3(p0)`、`t3(p1)` 和 `t3(p2)` ，每个分区有三个副本。每个分区的三副本分别在三个资源单元上。
+ 单看一个 ZONE ，表 `t3` 的三个分区分散在两个节点上。如 `t3(p1)` 一个节点， `t3(p0)` 和 `t3(p2)` 是一个节点。

总结：一个表有多个分区，这个技术类似存储的条带技术（`Striping`), 每个分区有三个副本，类似存储的镜像技术（`Mirroring`）。


## 如何修改 OceanBase 租户变量/参数

集群的参数有部分参数的生效范围是租户，这部分参数也称为租户的参数，会影响租户的行为。

常用租户参数如下。

| 参数名                                   | 参数值 | 参数含义                    |
|---------------------------------------|-----|-------------------------|
| writing_throttling_maximum_duration   | 1h  | 增量内存写入限速剩余时间目标上限        |
| writing_throttling_trigger_percentage | 100 | 增量内存写入限速触发阈值。建议设置为 90 。 |
| max_stale_time_for_weak_consistency   | 5s  | 弱一致性读能容忍最大延时            |

除了参数，租户更常用的是用变量（`variable`）来定制租户的行为。
变量跟参数其实是同一类概念，参数的定位和风格取自 ORACLE 数据库，变量的定位和风格取自 MySQL 数据库。集群 SYS 租户一样可以设置变量值（SYS 租户变量通常不用调整，必须很谨慎），不同租户的变量设置彼此独立，互不影响。大部分变量的设置是立即生效，生效范围根据设置方法不同有实例全局层面和会话层面生效。极少数变量类似 MySQL 的初始化变量，不能在租户里后期修改，只是在 SYS 租户里新建业务租户的时候设置。当然也能后期在 SYS 租户后期修改，只是这个修改也必须充分评估影响。

变量的值、描述都可以通过命令查看。命令是： ` show global |  [session] variables [ like '%变量名特征%' ] ; ` ， 或者 `show global | [session] variables where variable_name in ('参数1' [, '参数2']) ; ` 
变量的值的修改命令是： ` set global | [session] 变量名 = 变量值（如果是字符串，用两个单引号引起来）; ` 。`global` 指全局生效，对新建会话生效，当前会话不生效。 `session` 或者没有限定符，都表示对当前会话生效。会话断开重连后，又会取全局层面的默认值。

常用的租户变量如下：

| 变量名                       | 变量默认值(OB）          | 变量含义                                                                             |
|---------------------------|--------------------|----------------------------------------------------------------------------------|
| autocommit                | ON                 | 是否自动提交。ON 表示每个DML后会自动添加 commit 语句；OFF 表示不会自动 commit，需要用户主动 commit 或 rollback 事务。 |
| max_allowed_packet        | 4194304            | 需要调大，否则 SQL文本或数据量很大的时候可能报错。                                                      |
| sql_mode                  | STRICT_ALL_TABLES  |                                                                                  |
| transaction_isolation     | READ-COMMITTED     |                                                                                  |
| tx_isolation              | READ-COMMITTED     |                                                                                  |
| lower_case_table_names    | 1                  | 1(ON) 表示自动将表名转小写。                                                                |
| ob_tcp_invited_nodes      | %                  | % 表示允许所有 IP 访问。                                                                  |
| foreign_key_checks        | ON                 | ON：检查外键约束；OFF：不检查外键约束。                                                           |
| ob_query_timeout    | 10000000  | 语句超时时间，单位是微秒（us）。             |
|---------------------|-----------|-------------------------------|
| ob_trx_idle_timeout | 120000000 | 事务空闲超时时间，单位是微秒（us）。           |
| ob_trx_timeout      | 100000000 | 事务超时时间，单位是微秒（us）。             |
| ob_trx_lock_timeout | -1        | 锁等待超时时间，默认是 -1，不超时。单位是微秒(us)。 |
| ob_sql_work_area_percentage | 3 | 租户里 SQL可用内存占租户内存比例。|


部分变量还有语句级别的设置方法，通过 SQL HINT，影响范围是所修饰的语句。语句级的变量名名字会去掉前面的 `ob_` 部分。下面是常用的几个语句级别的变量命名映射关系。

| 全局|会话变量名            | 语句级变量名           | 描述                           |
|---------------------|------------------|------------------------------|
| ob_query_timeout    | query_timeout    | 指定语句执行超时的阈值                  |
| ob_read_consistency | read_consistency | 指定读一致性级别，默认是强一致性读，可以修改为弱一致性读 |


新建租户常用租户变量修改。修改语句超时时间，避免复杂的语句查询经常超时报错。
如下面是语句超时报错示例：

```sql
MySQL [test]> select sleep(11);
ERROR 4012 (HY000): Timeout
```

租户变量初始化示例：

```sql
set global ob_query_timeout = 100000000;
set global ob_trx_idle_timeout = 1200000000;
set global ob_trx_timeout = 1000000000;
set global ob_trx_lock_timeout = 1000000;
set global ob_sql_work_area_percentage = 50;

```

以上修改需要会话断开重连后生效。

## 如何对租户资源扩容/缩容

租户扩容或缩容主要是指资源扩容或缩容，具体就是调整租户的 CPU 和内存规格。对租户资源扩容的前提是集群中有节点可以分配出租户需要的资源。通常会首先考虑租户所在节点是否有可用资源满足租户资源扩容需求；如果不满足，就考虑集群中其他节点是否有资源。后者会触发集群中租户的资源负载均衡，会伴随着数据迁移逻辑。这个数据迁移完全是 OceanBase 内部逻辑，具备高可用能力，不怕异常宕机，不需要 DBA 介入。

### 租户资源池分裂

在三副本集群里，租户资源池通常也是由三个 ZONE 里三个节点里的资源单元（`resource unit`）组成。在前面创建租户的过程中，每个资源单元使用了统一的资源单元规格，创建了一个资源池包含三个资源单元（同一个资源单元规格）。这是一种最简单的创建和使用形态。通过下面 SQL 查看资源池细节。

```sql
select t1.name resource_pool_name, t2.`name` unit_config_name, t2.max_cpu, t2.min_cpu, round(t2.max_memory/1024/1024/1024) max_mem_gb, round(t2.min_memory/1024/1024/1024) min_mem_gb, t3.unit_id, t3.zone, concat(t3.svr_ip,':',t3.`svr_port`) observer,t4.tenant_id, t4.tenant_name
from __all_resource_pool t1 join __all_unit_config t2 on (t1.unit_config_id=t2.unit_config_id)
    join __all_unit t3 on (t1.`resource_pool_id` = t3.`resource_pool_id`)
    left join __all_tenant t4 on (t1.tenant_id=t4.tenant_id)
order by t1.`resource_pool_id`, t2.`unit_config_id`, t3.unit_id
;

输出：
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone  | observer           | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       1 | zone1 | 172.20.249.52:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       2 | zone2 | 172.20.249.49:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       3 | zone3 | 172.20.249.51:2882 |         1 | sys         |
| my_pool            | unit1            |       9 |       9 |         11 |         11 |    1001 | zone1 | 172.20.249.52:2882 |      1002 | obmysql     |
| my_pool            | unit1            |       9 |       9 |         11 |         11 |    1002 | zone2 | 172.20.249.49:2882 |      1002 | obmysql     |
| my_pool            | unit1            |       9 |       9 |         11 |         11 |    1003 | zone3 | 172.20.249.51:2882 |      1002 | obmysql     |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.132 sec)

```

说明：

+ SYS 租户有一个资源池 `sys_pool`，规格是 `sys_unit_config` 。
+ 业务租户 `obmysql` 有一个资源池 `my_pool` ，规格是 `unit1` 。

下面示例是将业务租户的资源池分裂为三个资源池。

```sql
ALTER RESOURCE POOL `my_pool` SPLIT INTO ('my_pool_zone1','my_pool_zone2','my_pool_zone3') ON ('zone1','zone2','zone3');

再次确认资源池细节，输出：

+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone  | observer           | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       1 | zone1 | 172.20.249.52:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       2 | zone2 | 172.20.249.49:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       3 | zone3 | 172.20.249.51:2882 |         1 | sys         |
| my_pool_zone1      | unit1            |       9 |       9 |         11 |         11 |    1001 | zone1 | 172.20.249.52:2882 |      1002 | obmysql     |
| my_pool_zone2      | unit1            |       9 |       9 |         11 |         11 |    1002 | zone2 | 172.20.249.49:2882 |      1002 | obmysql     |
| my_pool_zone3      | unit1            |       9 |       9 |         11 |         11 |    1003 | zone3 | 172.20.249.51:2882 |      1002 | obmysql     |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.010 sec)

```

将原本一个资源池打散为三个资源池，主要是为了便于调整单独 ZONE 里的资源单元规格。这个不是必须的，只是为了便于更细粒度的扩容或缩容租户资源。

### 租户资源扩容

租户资源扩容就是调整租户单元使用的资源规格的大小，或者更换资源单元规格。

示例：

```sql
alter resource unit sys_unit_config min_cpu=5,min_memory='2G';
alter resource unit unit1 max_cpu=5, min_cpu=5, max_memory='8G', min_memory='8G' ;

+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone  | observer           | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       1 | zone1 | 172.20.249.52:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       2 | zone2 | 172.20.249.49:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       3 | zone3 | 172.20.249.51:2882 |         1 | sys         |
| my_pool_zone1      | unit1            |       5 |       5 |          8 |          8 |    1001 | zone1 | 172.20.249.52:2882 |      1002 | obmysql     |
| my_pool_zone2      | unit1            |       5 |       5 |          8 |          8 |    1002 | zone2 | 172.20.249.49:2882 |      1002 | obmysql     |
| my_pool_zone3      | unit1            |       5 |       5 |          8 |          8 |    1003 | zone3 | 172.20.249.51:2882 |      1002 | obmysql     |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.026 sec)

CREATE resource unit unit2 max_cpu=8, min_cpu=8, max_memory='10G', min_memory='10G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size='1024G';
alter resource pool my_pool_zone1 unit = 'unit2';

查看资源池分布细节输出：
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone  | observer           | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       1 | zone1 | 172.20.249.52:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       2 | zone2 | 172.20.249.49:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       3 | zone3 | 172.20.249.51:2882 |         1 | sys         |
| my_pool_zone1      | unit2            |       8 |       8 |         10 |         10 |    1001 | zone1 | 172.20.249.52:2882 |      1002 | obmysql     |
| my_pool_zone2      | unit1            |       5 |       5 |          8 |          8 |    1002 | zone2 | 172.20.249.49:2882 |      1002 | obmysql     |
| my_pool_zone3      | unit1            |       5 |       5 |          8 |          8 |    1003 | zone3 | 172.20.249.51:2882 |      1002 | obmysql     |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.047 sec)

```

除了调整租户资源单元对应的规格这种扩容方式外，还有一种扩容方式就是保留资源单元规格不变，但是增加每个 ZONE 里的资源单元数量。这个有个前提就是每个 ZONE 里至少有 2 个节点。
具体语法如下：

```sql
 alter resource pool my_pool_zone1 unit_num = 2;
 
```

默认情况下，租户在每个 ZONE 里的资源单元数量是 1 ，这种租户架构也是 `1-1-1` 。这种租户也叫小租户。这种扩容命令执行时，会从集群其他节点里找到可用资源分配该规格的资源单元。

租户资源扩容的时候，很容易遇到资源不足的报错，信息如下：

```sql
MySQL [oceanbase]> alter resource pool my_pool_zone1 unit_num = 2;
ERROR 4624 (HY000):  machine resource 'zone1' is not enough to hold a new unit
MySQL [oceanbase]>
```

为了避免遇到资源不足的错误，需要对集群可用资源进行准确统计。这就是前面常用的 SQL。为了确保可用资源统计准确，要求所有租户资源的 `min_cpu` 跟 `max_cpu` 、`min_memory` 跟 `max_memory` 值保持一致。默认 SYS 租户的规格 `sys_unit_config` 的这个最小值和最大值经常不一样。需要手动调整一下。

```sql
alter resource unit sys_unit_config min_cpu=5,min_memory='2G';

select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, (cpu_total-cpu_assigned) cpu_free, round(mem_total/1024/1024/1024) mem_total_gb, round((mem_total-mem_assigned)/1024/1024/1024) mem_free_gb, usec_to_time(b.last_offline_time) last_offline_time, usec_to_time(b.start_service_time) start_service_time, b.status, usec_to_time(b.stop_time) stop_time 
from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port)
order by a.zone, a.svr_ip
;

输出：
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+
| zone  | observer           | cpu_total | cpu_free | mem_total_gb | mem_free_gb | last_offline_time          | start_service_time         | status | stop_time                  |
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+
| zone1 | 172.20.249.52:2882 |        14 |        1 |           13 |           1 | 1970-01-01 08:00:00.000000 | 2021-09-25 08:19:06.622351 | active | 1970-01-01 08:00:00.000000 |
| zone2 | 172.20.249.49:2882 |        14 |        4 |           13 |           3 | 1970-01-01 08:00:00.000000 | 2021-09-25 08:19:07.392669 | active | 1970-01-01 08:00:00.000000 |
| zone3 | 172.20.249.51:2882 |        14 |        4 |           13 |           3 | 1970-01-01 08:00:00.000000 | 2021-09-26 14:05:58.641570 | active | 1970-01-01 08:00:00.000000 |
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+
3 rows in set (0.010 sec)

```

### 租户资源缩容

租户资源缩容就是调小租户的资源。具体方法跟扩容时命令一样。一种是调小资源规格，另外一种方法就是调小资源池对应的资源单元数量（`unit_num`）。

租户资源缩容需要考虑一种场景，当租户有大量增量数据在增量内存 MEMSTORE 中时，如果缩小租户资源的内存规格，可能会导致 MEMSTORE 内存水位变高触发转储或限流。为了避免这种情形，建议租户资源缩容之前，先对集群发起合并，降低 MEMSTORE 里已使用的资源。此外，租户资源内存缩容可以分多步缩容。

对租户资源池对于你给的资源单元数量缩容时，会面临一个选择，该释放那个资源单元呢。此时如果不指定 UNIT ID，会有 OceanBase 自动选择。租户缩容结束后，可能会出现集群资源的负载均衡。为了避免这种不必要的负载均衡，可以指定删除的资源单元（UNIT ID）

示例 SQL , `unit_num` 从 5 变为 4 :

```sql
ALTER RESOURCE POOL pool_total_zone1 unit_num = 4 DELETE unit = (1013 );
```

租户缩容资源，很可能会触发租户资源负载均衡操作，会触发数据迁移逻辑。建议关注数据迁移的进度和影响。
查看集群事件日志视图，可以观察到这个。

```sql
SELECT DATE_FORMAT(gmt_create, '%b%d %H:%i:%s') gmt_create_ , module, event, name1, value1, name2, value2, rs_svr_ip
FROM __all_rootservice_event_history
WHERE 1 = 1 
   AND gmt_create > SUBDATE(now(),interval  1 hour)
ORDER BY gmt_create DESC
LIMIT 20;

```

