之前文章介绍了LSM-Tree对比B-Tree的优势,和 LSM-Tree 的几种算法,并且我们知道了LSM-Tree大部分数据是在内存中。通过多种Compaction算法去优化平衡写放大、读放大、空间放大。 OceanBase存储结构就是用了LSM-Tree,本文通过上一章对 LSM-Tree 的基本概念,继续延伸到OceanBase中的内存及转储/合并。
OceanBase内存结构
如果大家安装配置过 OceanBase,相信会看到 memory_limit、memory_limit_percentage 这两个参数,OceanBase 提供了两种方式控制了一个 OBServer 的总内存上线:
- 按照物理机或虚拟机总内存的百分比计算 OBServer 内存上限:由 memory_limit_percentage 参数配置
- 直接设置observer内存上限:由memory_limit参数配置 memory_limit=0时,memory_limit_percentage决定observer内存大小;否则由memory_limit决定observer内存大小
memory_limit_percentage | memory_limit | OBServer内存上限 | |
场景1 | 80 | 0 | 80 |
场景2 | 80 | 90 | 90 |
- 场景1:memory_limit=0,因此由memory_limit_percentage确定observer内存大小,即100GB*80% = 80GB。
- 场景2:memory_limit='90GB',因此observer内存上限就是90GB,memory_limit_percentage参数失效
OBServer 内部内存结构又可以做多个划分,例如100G的物理总内存,OBServer 分配了80G,剩余 20G 为操作系统剩余的,OBServer 中的 80G 内存分别由租户内存和 system 内存组成,system 内存由参数 system_memory 设置,例如 OBserver 总内存上限为 80G,system_memory 设置为 20G,则租户内存为 memory_limit-system_memory=60G。
OceanBase 支持多租户,即可以在一个OBServer实例中,创建多个物理上隔离的租户,租户之间的内存、CPU、数据库、表相互隔离,可以供多个不同业务使用。
这里经常遇到的问题是创建租户时资源不足,默认 OceanBase 中会有一个 sys 租户,用户租户 +sys 租户的内存使用量不能超过整个租户的内存限制,可以用下面方法查看租户内存使用情况。
obclient [oceanbase]> SELECT t1.name resource_pool_name, t2.`name` unit_config_name, t2.max_cpu, t2.min_cpu, t2.memory_size/1024/1024/1024 memory_size, 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 | memory_size | unit_id | zone | observer | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+----------------+---------+-------+--------------------+-----------+-------------+
| sys_pool | sys_unit_config | 1 | 1 | 2.750000000000 | 1 | zone1 | 10.140.114.12:2882 | 1 | sys |
| sys_pool | sys_unit_config | 1 | 1 | 2.750000000000 | 2 | zone2 | 10.140.60.14:2882 | 1 | sys |
| sys_pool | sys_unit_config | 1 | 1 | 2.750000000000 | 3 | zone3 | 10.140.118.7:2882 | 1 | sys |
| pool_2 | S2 | 7 | 2 | 8.000000000000 | 1001 | zone1 | 10.140.114.12:2882 | 1002 | tenant_2 |
| pool_2 | S2 | 7 | 2 | 8.000000000000 | 1002 | zone2 | 10.140.60.14:2882 | 1002 | tenant_2 |
| pool_2 | S2 | 7 | 2 | 8.000000000000 | 1003 | zone3 | 10.140.118.7:2882 | 1002 | tenant_2 |
+--------------------+------------------+---------+---------+----------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.001 sec)
租户内的内存结构,分为两种:
- 不可动态伸缩:MemStore,之前提到过LSM-Tree新增和修改的数据都是先存储到MemTable中,对应的就是这部分内存。
- 可动态伸缩内存:主要是执行计划缓存(PLANCHE)、SQL执行期间用到的内存主要是parser和优化器使用(SQL AREA)、工作线程所占用的内存(WORKER AREA)
刚开始使用 OceanBase 时,使用 sysbench 造数,总是会遇到 "returned error 4030 (Over tenant memory limits) for query" 关于内存的错误,这个问题最主要是 MmeTable 的内存超过了 MemStore 的限制,OceanBase 中默认租户的 MemLimit 是租户总内存的 50%,是由参数 memstore_limit_percentage 控制,默认值是50。
当 MemStore 内存使用超过 freeze_trigger_percentage 定义的百分比时,触发冻结及后续的转储/合并等行为,下面需要在介绍下 OceanBase 中的转储/合并操作。
OceanBase中转储/合并
下面一张是 OceanBase 中分层存储的结构图,内存中的是 MemTbale,磁盘中分为三层 L0 层称为 Mini SSTable,L1 层称为 Minor SSTable,转储主要发生在这两层,L0 内还会进行细分分为 L0-LN。 L2 层是 Major SSTable 是做合并时产生的一个全新的版本。
转储:
转储有三类:MINI_MERGE/MINI_MINOR_MERGE/MINOR_MERGE。
- MINI_MERGE:是发生在从 MemTable 刷新到 Mini SSTable
- MINI_MINOR_MERGE:是多个 Mini SSTable 合并成一个新的 Mini SSTable
- MINOR_MERGE:是将L0与L1层合并,从GV$OB_TABLET_COMPACTION_HISTORY可以看到历史发生转储/合并的信息及次数。
OceanBase 中转储分为自动触发和手动触发
- 自动触发:
1. 当MemTable中数据量达到阈值时,就会做Fronze,原MemTable中数据会写入到Mini SSTable中,同时产生一个新的MemTable接受新的增量数据。当租户MemTable使用量达到memstore_limit_percentage * freeze_trigger_percentage时就会触发转储动作,freeze_trigger_percentage默认为50。
2. L0层中的Ln层SSTable达到一定数量后(由参数minor_compact_trigger控制,默认是2),会触发转储,但这里需要注意并不是每次都是先触发MINI_MINOR_MERGE类型的转储,内部会有会根据Mini SSTable数量、写放大系数、Minor SSTable数量、Row Count,综合判断转储的触发类型。
- 手动触发:
1. 手动转储OB中提供了更细粒度的管理,可做到集群/Server/租户/Replica 级别的转储,具体可以参考ALTER SYSTEM MINOR FREEZE命令的使用
合并
合并就是将所有的Mini SSTable和Minor SSTable与Major SSTable做合并,这样减少SSTable的数量,减少了读放大。这是一个比较耗时和占用CPU的操作,所以在OB中提供了不同方式的合并,去减少合并对线上的影响。 对于合并又三种触发方式:
- 定时触发:默认情况下会在每天凌晨2点做合并,可以用过ALTER SYSTEM SET major_freeze_duty_time = '02:00';做修改
- 手工触发: ALTER SYSTEM MAJOR FREEZE;
- 自动触发:当MemTable使用量达到memstore_limit_percentage * freeze_trigger_percentage,且转储次数达到minor_freeze_times(4.0中改为major_compact_trigger)设置的上限时,就会直接进行合并
合并方式可以从:https://www.oceanbase.com/docs/community-observer-cn-10000000000449401 查看到详细的解释,这里提下轮状合并的方式。
因为做合并操作对系统影响比较大,所以建议在业务低峰期做,但并不是所有业务有低峰期,可以利用 OceanBase 集群架构特点,进行轮状做合并,减少对业务的影响。 假设集群有6个Zone,每次只对3个Zone进行合并操作,可以如下设置:
alter system set enable_manual_merge = false; -- 关闭手动合并
alter system set enable_merge_by_turn = true; -- 开启轮转合并
alter system set zone_merge_order = 'z1,z2,z3,z4,z5,z6', zone_merge_concurrency = 3; -- 设置合并顺序
通过上面设置后,会按设置的顺序进行z1,z2,z3合并,当有Zone完成合并后,会按继续启z4,保证每次有三个Znoe在进行合并。 但通过轮转合并会加长整个轮状的时间,并且某一个ZONE的合并开始之前,会将这个ZONE上的Leader服务切换到其它ZONE,对长事务是有影响的。
监控转储与合并
我们可以通过GV$OB_SSTABLES视图,查看SSTable转储与合并的情况,假设当前参数设置如下,通过sysbench 做prepare操作,产生一些数据,观察转储与合并情况:
参数 | 值 |
memstore_limit_percentage | 50 |
freeze_trigger_percentage | 10 |
major_compact_trigger | 3 |
minor_compact_trigger | 5 |
租户内存 | 7G |
需要注意,上面这几个参数是租户级别设置。
- 内存情况如下,当MEMSTORE_USED>FREEZE_TRIGGER时就会触发转储,并且当转储次数达到3次后就会触发合并:
obclient [OCEANBASE]> select round(ACTIVE_SPAN/1024/1024/1024,2) as ACTIVE_SPAN_GB , round(FREEZE_TRIGGER/1024/1024/1024,2) as FREEZE_TRIGGER_GB, round(MEMSTORE_USED/1024/1024/1024,2) as MEMSTORE_USED_GB , round(MEMSTORE_LIMIT/1024/1024/1024, 2) as MEMSTORE_LIMIT_GB from GV$OB_MEMSTORE where tenant_id = 1002;
+----------------+-------------------+------------------+-------------------+
| ACTIVE_SPAN_GB | FREEZE_TRIGGER_GB | MEMSTORE_USED_GB | MEMSTORE_LIMIT_GB |
+----------------+-------------------+------------------+-------------------+
| 0.04 | 0.30 | 0.04 | 3.00 |
| 0.03 | 0.30 | 0.03 | 3.00 |
| 0.03 | 0.30 | 0.03 | 3.00 |
+----------------+-------------------+------------------+-------------------+
3 rows in set (0.003 sec)
- 可以单独针对一张表,查看SSTable的情况,先通过oceanbase.CDB_OB_TABLE_LOCATIONS查看表的TABLET_ID:
obclient [oceanbase]> select * from oceanbase.CDB_OB_TABLE_LOCATIONS where tenant_id = 1002 and database_name = 'sysbenchdb';
+-----------+---------------+------------+----------+------------+----------------+-------------------+------------+---------------+-----------+-------+-------+---------------+----------+----------+--------------+
| TENANT_ID | DATABASE_NAME | TABLE_NAME | TABLE_ID | TABLE_TYPE | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | DATA_TABLE_ID | TABLET_ID | LS_ID | ZONE | SVR_IP | SVR_PORT | ROLE | REPLICA_TYPE |
+-----------+---------------+------------+----------+------------+----------------+-------------------+------------+---------------+-----------+-------+-------+---------------+----------+----------+--------------+
| 1002 | sysbenchdb | sbtest1 | 500015 | USER TABLE | NULL | NULL | NULL | NULL | 200008 | 1002 | zone1 | 10.140.114.12 | 2882 | FOLLOWER | FULL |
| 1002 | sysbenchdb | sbtest1 | 500015 | USER TABLE | NULL | NULL | NULL | NULL | 200008 | 1002 | zone2 | 10.140.60.14 | 2882 | LEADER | FULL |
| 1002 | sysbenchdb | sbtest1 | 500015 | USER TABLE | NULL | NULL | NULL | NULL | 200008 | 1002 | zone3 | 10.140.118.7 | 2882 | FOLLOWER | FULL |
+-----------+---------------+------------+----------+------------+----------------+-------------------+------------+---------------+-----------+-------+-------+---------------+----------+----------+--------------+
3 rows in set (0.023 sec)
- 当达到minor_compact_trigger设置个数后,会触发MINOR_MERGE或MINOR_MERGE类型的转储
obclient [oceanbase]> SELECT count(*) , type FROM oceanbase.GV$OB_TABLET_COMPACTION_HISTORY where tenant_id = 1002 AND TABLET_ID = 2000068 and svr_ip = '10.140.114.12' group by type;
+----------+------------+
| count(*) | type |
+----------+------------+
| 6 | MINI_MERGE |
+----------+------------+
1 row in set (0.005 sec)
obclient [oceanbase]> SELECT count(*) , type FROM oceanbase.GV$OB_TABLET_COMPACTION_HISTORY where tenant_id = 1002 AND TABLET_ID = 200008 and svr_ip = '10.140.114.12' group by type;
+----------+-------------+
| count(*) | type |
+----------+-------------+
| 7 | MINI_MERGE |
| 1 | MINOR_MERGE |
+----------+-------------+
2 rows in set (0.004 sec)
- 当转储次数达到major_compact_trigger时就会触发合并(MAJOR_MERGE)
obclient [oceanbase]> SELECT count(*) , type FROM oceanbase.GV$OB_TABLET_COMPACTION_HISTORY where tenant_id = 1002 AND TABLET_ID = 200008 and svr_ip = '10.140.114.12' group
by type;
+----------+-------------+
| count(*) | type |
+----------+-------------+
| 1 | MAJOR_MERGE |
| 2 | MINI_MERGE |
+----------+-------------+
2 rows in set (0.005 sec)
使用上的建议
回到之前我们遇到内存不足的错误:"returned error 4030 (Over tenant memory limits) for query",对于使用过MySQL的用户来说,会觉得很奇怪,通过我们之前的分析现在也能知道个大概原因了。
如果我们内存很小,想尝试OB做一些简单的测试Prapare数据报错,那么可以调大memstore_limit_percentage并减小freeze_trigger_percentage,这样设置后会加快转储的次数,释放内存。
如果是性能测试,那么可以调大一些freeze_trigger_percentage并将major_compact_trigger设置为0或者很大(需要内存足够),这样大量的数据就在内存中。