【OceanBase诊断调优】—— KVCache 排查手册

原文链接:OceanBase分布式数据库-海量数据 笔笔算数

本文介绍 KVcache 相关问题的排查方法。

KVCache 相关概念

在进行排查前,需要了解几个概念。

  • pin

    一个 cache 块 ( memblock ) 被 pin 住,表示它正在被引用。

    cache 的由多个定长的块组成,每个块称为一个 memblock 。每个 memblock 中存放了多个 KV ,使用者通过 KV 指针来读取 KV 。容易理解,在使用指针的过程中,需要保证指针的安全,也即需要 pin 住存放 KV 的 memblock ,保证它不被释放。cache 内部通过引用计数来实现,cache 每向外吐出一个 KV 指针,都要对 KV 所在的 memblock 的引用计数加 1 ( pin 住这个 memblock ) ,当不再使用 KV 指针时,引用计数减 1 。也就是说,当引用计数大于 0 时,说明有人正在读其中的内存,那这个 memblock 就不能被释放,反之如果等于 0 ,则释放是安全的。

    简而言之,如果一个 memblock 被 pin 住,那么它不能被释放。

  • sync wash

    sync wash 指 cache 腾出自身内存给租户使用的过程。 从 1 可知,如果一个 memblock 没有被 pin ,那么它是可以被释放的。sync wash 就是 cache 找到没有被 pin 的 memblock ,释放内存,腾给租户使用。

  • cache 大小无上限

    cache 的大小在一个租户内是无上限的。 为了最大化利用租户内存,在租户内不限制 cache 的大小,理论上 cache 最大可以占满一个租户的内存。但是 cache 的内存比较特殊,在租户需要内存时,会触发 cache 的 sync wash ,腾出内存给租户使用。

常见问题

ret=-4273 can not find enough memory block to wash

首先 4273 不是 cache 的问题,4273 报错表示在 sync wash 过程中发现 cache 中所有的 memblock 都被 pin 了,没有内存可以释放。 导致 4273 的原因可能有:

  • cache 本身已经被榨干了,没有内存可以 wash ,所以就找不到不被 pin 的 memblock。

  • cache 本身还有内存,但是都被 pin 住了,无法 wash 出来;这个原因又分为两种情况:

    • 确实有需要较多 cache 的 SQL 在执行。
    • cache handle 引用计数有泄漏。

排查的思路就是逐步确认是哪个原因导致的 4273 ,步骤如下:

  1. 查看当时 cache 的大小。

    复制代码
    * 通过 MEMORY 日志,memory 日志可以看到租户的 cache_hold ,这个字段记录了 cache 的总大小。
    * 通过 CACHE 日志,可以看到当时各个 cache 的大小。
    * 通过虚拟表 `__all_virtual_kvcache_info`,各版本都有,但是是实时数据,需要在案发时查询。

    如果此时 cache 大小很少,说明此时 cache 本身已经被榨干,wash 不出内存符合预期,应该查看当时租户的内存分布,看看其他 mod 是否符合预期,否则进入第二步。

  2. 判断是否存在泄漏。

    这一步是通过查看 cache 的大小能否降下来,来区分是 cache handle 引用计数有泄漏还是确实有需要较多 cache 的 SQL 在执行。

    对于 OceanBase 数据库 V4.0 及以后版本,可以停止所有查询后尝试手动 flush cache ,如果 cache 能降下来,则说明没有泄漏,是确实有需要较多 cache 的 SQL 在执行导致。

    手动 flush 需要直连到 OBServer 节点上执行,如果可以 flush 干净 ( 可能需要手动 flush 多次 ) ,则表示没有泄漏,对比 flush 前后 __all_virtual_kvcache_info 表中 size 的变化来判断。也可以通过查询 __all_virtual_kvcache_store_memblock 虚拟表来直接查看所有 memblock 引用计数,看是否有异常。

    复制代码
    select * from __all_virtual_kvcache_store_memblock where ... order by ref_count desc limit 10;

    对于 OceanBase 数据库 V4.0 之前版本,只能通过日志查看在 4273 报错后,cache 的大小是否降下去过,如果能降下去,则说明一定没有泄漏,如果没降下去过,则无法判断。( 因为之前版本手动 flush 并不会立即释放 cache 的内存,而是通过降低其访问热度,通过后台 wash 线程慢慢刷出去。 )

    如果是较多 cache 的 SQL 在执行导致,规避方案是,将 cache 用量较大的操作分散在租户内存压力较小的时候执行,如果仍有报错,尝试对租户内存进行扩容,增大内存。 如果是 cache handle 引用计数有泄漏导致,则需要复现问题,排查泄漏的路径。

cache 占用内存较高

如前文所述,cache 在租户内是无上限的,所以理论上无论 cache 占多大内存,只要能被 sync wash ,就是符合预期的。 判断能否 sync wash 出来需要参考问题 1 中判断 cache 大小能否降下来的方法,能降下来就是能 sync wash 出来。

cache 预热

OceanBase 数据库 V4.0 及以后的版本支持 cache 预热功能,之前版本没有此功能。 为缓解 compaction 后的性能抖动,在 compaction 时会将新生成的微块放入 cache 中,进行预热。 预热并不会将所有新生成的微块都预热进 cache ,而是根据租户的内存情况进行预热。现有策略下,data block cache 使用租户空闲内存的 5% ,index block 使用租户空闲内存的 2% 。 data block 和 index block 按照不同的优先级被预热进 cache ,按照中间层索引树的等级分配不同的优先级,越接近根节点的 block 优先级越高。

wash

cache 腾出自身内存的过程称为 wash ,cache 的 wash 行为分为同步 wash 和异步 wash 两种。 同步 wash 就是上文提到的 sync wash,同步腾出内存,这里不再赘述。 异步 wash 由一个后台 wash 线程完成,wash 线程会定期地根据每个租户的内存压力 ( 包括租户大小、当前 cache 大小、租户当前空闲内存等 ) 计算出租户应该 wash 出的 cache size ,然后再根据每个 memblock 的访问热度从低到高 wash 。如果压力不大,计算结果可能是 0,不做 wash ,可以根据如下日志来判断,如果有则表示 wash 线程异步 wash 了 memblock。

复制代码
COMMON_LOG(INFO, "Wash memory, ",
            "tenant_id", wash_iter->first,
            "cache_size", tenant_wash_info->cache_size_,
            "lower_mem_limit", tenant_wash_info->lower_limit_,
            "upper_mem_limit", tenant_wash_info->upper_limit_,
            "min_wash_size", tenant_wash_info->min_wash_size_,
            "max_wash_size", tenant_wash_info->max_wash_size_,
            "mem_usage", lib::get_tenant_memory_hold(wash_iter->first),
            "reserve_mem", static_cast<int64_t>((static_cast<double>(tenant_wash_info->upper_limit_)) * tenant_reserve_mem_ratio_),
            "wash_size", tenant_wash_info->wash_size_);

wash 线程的异步 wash 实际上是减去 memblock 原始的引用计数,等待引用计数减为 0 时执行释放,因此:

  • 异步 wash 并不能立即释放 memblock ,需要等待不被 pin 。
  • 如果有引用计数泄漏泄漏,wash 线程一样不能 wash memblock。

手动 flush

手动 flush cache 表示手动清理指定 cache , 命令如下,目前只能在 sys 租户下执行,需要直连要 flush 的 OBServer 节点。

复制代码
alter system flush kvcache [tenant tenant_name [cache 'cache_name']];

cache_name 可以在 __all_virtual_kvcache_info 中查到,常用的 cache_name 有 :user_block_cache、index_block_cache、user_row_cache、fuse_row_cache、bf_cache。

OceanBase 数据库 V4.0 及以后版本,flush 包含了立即清空的功能,预期情况下,flush 之后 cache 应该是立即被清空(若内存占用太多,flush 只清理一部分内存)。如果发生 4274 的报错,是 cache 较多导致的超时问题,再次 flush 即可。

OceanBase 数据库 V4.0 之前版本,手动 flush 只会清除指定 cache 中 KV 的索引,索引删除后,对应的 KV 就无法再被访问到,其所在的 memblock 的热度就会持续降低,等待 wash 线程将其 wash 出去。

判断手动 flush 是否生效

无论新老版本,手动 flush 一定会清理掉全部的 kv_cnt ,可以观察 flush 前后 kv_cnt 是否清零过来判断手动 flush 是否生效。

复制代码
select * from __all_virtual_kvcache_info where cache_name = '<cache_name>';

监控 cache handle ,排查引用计数泄漏

OceanBase 数据库 V4.0 及之后版本,如果怀疑或确认 cache 引用计数有泄漏,可以通过如下方法诊断。

  1. binary 需要启用 ENABLE_DEBUG_LOG 编译选项。

  2. 打开监控,指定监控的 cache name 。

    复制代码
    alter system set leak_mod_to_check = 'cache_name';
  3. 查看泄漏 backtrace 。

    复制代码
    select * from __all_virtual_kvcache_handle_leak_info where tenant_id = tenant_id order by hold_count desc limit 10;

    在 OceanBase 数据库 V4.3 及以后版本虚拟表更名为 __all_virtual_storage_leak_info,并且新增配置项 _storage_leak_check_mod 用于指定泄漏监控的内容。

    这里简单介绍一下监控的实现方法。cache 对外吐出的引用计数都包含在 cache_handle 中,一个 cache_handle hold 住 1 个引用计数,所以在 cache 对外吐出 cache_handle 时,记录一条 backtrace ,在 cache_handle reset 时,消除记录。所以最后遗留下来未释放的 backtrace 很大可能就是泄漏的 backtrace 。

    根据记录方式可以知道,只要当前 cache 外部有 cache_handle ,那就会被记录 backtrace ,所以任何路径在持有 cache_handle 期间都会被记录,需要抓到持有时间过长或不符合预期的堆栈才是泄漏的堆栈。

    如果事先没有开启 cache handle 监控,可以通过 __all_virtual_kvcache_store_memblock 虚拟表简要确认一下问题,这张表会输出当前 server 上所有 memblock 的信息。 (OceanBase 数据库 V 4.3 及以后版本使用新的虚拟表)

    复制代码
    select * from __all_virtual_kvcache_store_memblock where ... order by ref_count desc limit 10;

    关注 ref_count 列,这一列表示 memblock 的当前的引用计数,目前当 memblock 没有被引用时,这里拿到的引用计数为 2 ( 初始引用计数为 1 ,查 memblock 信息时需要先加引用计数做保护,因此为 2 ) 。在确保某 memblock 当前不会被 pin 的前提下,ref_count 大于 2 的 memblock 都可以认为有引用计数泄漏。

泄漏检测扩展 (OceanBase 数据库 V4.3 及以后版本)

OceanBase 数据库 V4.3 及以后版本,新增存储层的泄漏检测功能,目前支持的检测内容有 cache handle、io handle、storage iter ,同时,新增配置项 _storage_leak_check_mod 用于配置泄漏检测的内容,有效值分别为 cache_name / all_cache / io_handle / storage_iter ,默认为空串,设定成空串或其他值时,关闭监控。

同时,__all_virtual_kvcache_handle_leak_info 虚拟表更名为 __all_virtual_storage_leak_info

检测步骤使用新的配置和虚拟表即可:

  1. binary 需要启用 ENABLE_DEBUG_LOG 编译选项。

  2. 打开监控,指定监控的内容。

    复制代码
    alter system set _storage_leak_check_mod = 'cache_name' | 'all_cache' | 'io_handle' | 'storage_iter' ;
  3. 查看泄漏 backtrace。

    复制代码
    select * from __all_virtual_storage_leak_info where ... order by hold_count desc limit 10;
相关推荐
广州智造3 小时前
OptiStruct实例:3D实体转子分析
数据库·人工智能·算法·机器学习·数学建模·3d·性能优化
技术宝哥6 小时前
Redis(2):Redis + Lua为什么可以实现原子性
数据库·redis·lua
学地理的小胖砸7 小时前
【Python 操作 MySQL 数据库】
数据库·python·mysql
dddaidai1238 小时前
Redis解析
数据库·redis·缓存
数据库幼崽8 小时前
MySQL 8.0 OCP 1Z0-908 121-130题
数据库·mysql·ocp
Amctwd8 小时前
【SQL】如何在 SQL 中统计结构化字符串的特征频率
数据库·sql
betazhou9 小时前
基于Linux环境实现Oracle goldengate远程抽取MySQL同步数据到MySQL
linux·数据库·mysql·oracle·ogg
lyrhhhhhhhh9 小时前
Spring 框架 JDBC 模板技术详解
java·数据库·spring
喝醉的小喵10 小时前
【mysql】并发 Insert 的死锁问题 第二弹
数据库·后端·mysql·死锁
付出不多11 小时前
Linux——mysql主从复制与读写分离
数据库·mysql