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

万里数据库GreatDB故障诊断:检查内存占用使用全流程

原创 Dbb 2024-06-06
339

检查内存占用

简介

performance_schema是对mysql的细力度的性能监控诊断工具,采集到的性能数据使用performance引擎存储,全部保存在内存。 本文关注 pfs 的对mysql内存使用相关的统计。首先从代码简单解释pfs内存统计的简单实现以及介绍下 pfs 统计内存相关的参数,然后介绍相关的表的使用介绍和,最后介绍一个监控项为例。

PFS内存统计的简单原理

在mysql系统中,为了统计内存的使用,提供了一套可以统计内存的使用内存管理接口,详见pfs_memory_privider.h 。大概实现思路为,定义key-value模型,相关模块的内存定义一个key,根据开辟和释放的虚拟内存,更改key的值,这个key名到具体实现中为event_name。但这个key 有不同的维度,可以是thread,accout,user,host,global。内存在开辟的时候,根据key的属性是否为global,保存thread或者global上。但保存的value会根据线程的退出等操作在不同维度间转移。

参数配置

consumers

永久修改

在配置文件中修改:

//控制global属性的instrument
performance_schema_consumer_global_instrumentation=ON
//控制thread属性的instrument
performance_schema_consumer_thread_instrumentation=ON

临时改动

更新performance_schema.setup_consumers的enable列值

mysql> select * from setup_consumers where name like '%instrumentation';
+------------------------+---------+
| NAME                   | ENABLED |
+------------------------+---------+
| global_instrumentation | YES     |
| thread_instrumentation | YES     |
+------------------------+---------+
2 rows in set (0.00 sec)

###关闭thread上的instrumentation收集
update setup_consumers set enabled = 'NO' where name = 'thread_instrumentation';

instruments

永久修改

在配置文件中修改

performance_schema_instrument='memory%=ON'

临时改动

更新performance_schema.setup_instruments的enable列值

select * from setup_instruments where name like '%memory%io_cache%' limit 1\G
*************************** 1. row ***************************
         NAME: memory/sql/TABLE::sort_io_cache
      ENABLED: YES
        TIMED: NULL
   PROPERTIES: 
   VOLATILITY: 0
DOCUMENTATION: NULL

select * from setup_instruments where name like 'memory%' limit 1\G
*************************** 1. row ***************************
         NAME: memory/performance_schema/mutex_instances
      ENABLED: YES
        TIMED: NULL
   PROPERTIES: global_statistics
   VOLATILITY: 1
DOCUMENTATION: Memory used for table performance_schema.mutex_instances

##更新语法
update setup_instruments set ENABLED = 'NO' where name like 'memory/mysys/IO_CACHE';

memory模块的 intruments从他们记录的内存内容可以分为两类:

  1. PFS内置内存

    performance_schema模块本身使用内存,名为memory/performance_schema/%,不调用注册接口直接注册的。

  2. 其它模块。

    其它模块使用的内存,使用pfs_register_memory_vc注册的。

这里对memory模块用到intruments做一个解释说明,整体的请看https://dev.mysql.com/doc/refman/8.0/en/performance-schema-setup-instruments-table.html

  • NAME:

    是注册的key名,在其它地方体现为event_name.

  • ENABLED

    开关,PFS管理模块的ENABLED不能变化,代码中有限制。

  • TIMED

    在memory模块没有tiemed属性,这类的还有error,thread.

  • PROPERTIES

    在memory模块只有global_statistics 或无属性global_statistics直接保存在global维度上,无属性开始保存在thread维度上,后面可能迁移到其它维度上。

  • VOLATILITY

    提示信息,可以理解为提示生命周期。在peformance_schema类别下的intrumentes都是1,表示永恒。其它memory相关的为0,这个是代码中的提示行为。

  • DOCUMENTATION

    注释信息

限制

  • performance_schema_max_memory_classes

PFS最多能管理的内存实例数

show variables like 'performance_schema_max_memory_classes';
+---------------------------------------+-------+
| Variable_name                         | Value |
+---------------------------------------+-------+
| performance_schema_max_memory_classes | 530   |
+---------------------------------------+-------+
  • Performance_schema_memory_classes_lost

记录丢失管理的内存实例数

show global status like 'Performance_schema_memory_classes_lost';
+----------------------------------------+-------+
| Variable_name                          | Value |
+----------------------------------------+-------+
| Performance_schema_memory_classes_lost | 0     |
+----------------------------------------+-------+

相关表信息

performance_schema

在performance_schema中有如下5个表:

memory_summary_by_thread_by_event_name  
memory_summary_by_account_by_event_name
memory_summary_by_user_by_event_name   
memory_summary_by_host_by_event_name
memory_summary_global_by_event_name

PFS使用章节中,提到的key-value模型其中key可以在多个维度,表名中的thread,accout,user,host,global就是这些维度。这些表都是统计的意思,既是多个维度上的联合统计,也是可能是单个维度上的统计。

表详细信息

  • 相同字段

    EVENT_NAME:对应内存模块名。

    COUNT_ALLOC:合计开辟内存次数。

    COUNT_FREE:合计释放内存次数。

    SUM_NUMBER_OF_BYTES_ALLOC:合计开辟内存大小。

    SUM_NUMBER_OF_BYTES_FREE:合计释放内存大小。

    LOW_COUNT_USED:历史最少使用的数量,都是0。

    CURRENT_COUNT_USED:当前在使用的内存数量。

    HIGH_COUNT_USED:历史最高在使用的内存数量。

    LOW_NUMBER_OF_BYTES_USED:历史最少使用的内存大小。

    CURRENT_NUMBER_OF_BYTES_USED:当前使用在使用的内存大小。

    HIGH_NUMBER_OF_BYTES_USED:历史最高在使用的内存大小。

    其中各个表多出的字段就是维度上的体现。

  • 详细介绍

    • memory_summary_by_thread_by_event_name

      1. 统计各个thread上各个event_name占用的内存。根据thread维度上保存数据所得。
      2. 在thread退出后,对应的key会汇总到account上和部分在global上保存,如果account不存在会汇总到对应的user,host上和部分global上保存,如果acccount,user,host都没有,直接汇总到global上。
      3. 在thread1上开辟的内存,给thread2 使用并被thread2释放,在thread2释放时发现这块内存不属于自己管理,会将这部分释放的内存记录在对应的global维度上。
    • memory_summary_by_account_by_event_name

      1. 统计各个account上各个event_name占用的内存。根据thread纬度上和account维度上保存的数据聚合求的。
      2. 在truncate performance_shcema.users时,将account上的数据转移到host 和 user上保存,如果host 和 user 不存在,直接转移到global上保存,并将user维度上保存的数据清空。
    • memory_summary_by_user_by_event_name

      1.统计各个user,既username相同的,各个event_name占用的内存。根据thread, account, user维度上的数据聚合求的。

      2.不能再转移了,在truncate performance_shcema.users 时清空。

    • memory_summary_by_host_by_event_name

      1. 统计各个host,既hostname相同的,各个event_name占用的内存。根据thread, account, host纬度上的数据聚合求的。
      2. 在truncate performance_shcema.hosts时,先将account上的内容转移,再将host上的内容,转移到global上保存。
    • memory_summary_global_by_event_name

      1.统计各个key,既event_name总共占用的内存,根据thread, account, host, global维度上的数据聚合求的。

    SYS

    在sys中库中,将performance_schema中的memory表,根据维度做group by,将对应的event_name占用的内存值求sum,创建了几个统计内存的view,体现维度上总共消耗的内存。简单理解为performance_schema的显示(维度,event_name,内存信息),sys(维度,内存信息),但global相关的两者类似,详情请具体的sys表的实现,涉及到的表如下:

    ## 通过format_bytes转化最终结果,为带单位的字符串,更直观
    memory_by_host_by_current_bytes     
    memory_by_thread_by_current_bytes   
    memory_by_user_by_current_bytes     
    memory_global_by_current_bytes      
    memory_global_total
    
    ## 展示的原始数值
    x$memory_by_host_by_current_bytes   
    x$memory_by_thread_by_current_bytes 
    x$memory_by_user_by_current_bytes   
    x$memory_global_by_current_bytes 
    x$memory_global_total
    
    • memory_global_total

      统计当前pfs系统中总共统计到的虚拟内存。

    • memoryby\*_by_current_bytes

      该类表统计对应维度下内存的总体使用情况。都包含了如下列:

      current_count_used:当前在使用的内存数量

      current_allocated:当前分配的内存大小

      current_avg_alloc:当前平均每次分配的内存大小

      current_max_alloc:当前最大实例占用的内存

      total_allocated :历史总共开辟的内存大小。

    • memory_global_by_current_bytes

      统计各个实例使用的内存情况。

      current_count:当前在使用的内存数量

      current_alloc:当前分配的内存大小

      current_avg_alloc:当前平均每次分配的内存大小

      high_count:历史最高使用的内存数量

      high_alloc:历史最高分配的内存大小

      high_avg_alloc:历史最高平均每次分配的内存大小,这个值不准确。

使用流程

流程

  1. 结合系统内存工具TOP,/proc/$pid信息等
  2. 查看PFS统计到的总的内存,sys.memory_global_total
  3. 细化到线程、实例,结合 sys.memory_by_thread_by_current_bytes, sys.memory_global_by_current_bytes, performance_schema.memory_summary_by_thread_by_event_name,performance_schema.memory_summary_global_by_event_name, 以及实例的详细信息performance_scehma.setup_instruments整体分析。

小结

通过TOP或者/proc/pid/status等,看到mysql占用的实际内存,在很多情况下都比pfs总共统计(sys.memory_global_total)的内存多很多,这是一个正常现象。

  1. 在代码中,有很多使用内存的并没有使用pfs的内存统计。
  2. 进程本身占用的内存。可以分析/proc/$pid/status ,/proc/$pid/smaps这些。

pfs统计是虚拟内存。

案例分析

下面我们以一个global_statistics 和 一个非global_statistics的实例做一个演示。

memory/sql/Logevent

四川移动环境的发现sql/slave_sql的current_allocated180.77GIB,疑似内存泄露的样子

select * from setup_instruments where name like 'memory%Log_event';
+----------------------+---------+-------+------------+------------+---------------+
| NAME                 | ENABLED | TIMED | PROPERTIES | VOLATILITY | DOCUMENTATION |
+----------------------+---------+-------+------------+------------+---------------+
| memory/sql/Log_event | YES     | NULL  |            |          0 | NULL          |
+----------------------+---------+-------+------------+------------+---------------+
  1. 这里先查看了sys.memory_by_thread_by_current_bytes表,找到一个线程占用的内存比较高
  2. 通过查看performance_schema.memory_summary_by_thread_by_event_name,确定线程那个event_name占用的比较高,确定了memory/sql/Logevent 这个实例用内存比较多10多区,难道是这块内存泄露了?
  3. memory/sql/Logevent能在thread上统计到数据可以推出这个实例是非global_statistics的,通过查询setup_intruments表确定该信息。
  4. 继续查询performance_schema.memory_summary_global_by_event_name表,确定memory/sql/Logevent的内存是正常的。
  5. 最后总结得出,sql/slave_sql线程是开辟的内存再sql/worker线程上释放了,因为memory/sql/Logevent 是thread上的,释放线程和开辟线程不是同一个线程,就直接记录到global维度上了,在performance_schema.memory_summary_global_by_event_name显示的是才是正确的值。

memory/sql/IO_CACHE

四川移动的测试环境发现下面类似的问题,memory_global_total高,找到了是memory/sql/IO_CACHE 实例占用内存高,但找不到那个线程占用高,情况如下:

image-20220727224800158

image-20220727214316708

GreatDB Cluster[performance_schema]> select * from setup_instruments where name like 'memory/mysys/IO_CACHE';
+-----------------------+---------+-------+-------------------+------------+---------------+
| NAME                  | ENABLED | TIMED | PROPERTIES        | VOLATILITY | DOCUMENTATION |
+-----------------------+---------+-------+-------------------+------------+---------------+
| memory/mysys/IO_CACHE | YES     | NULL  | global_statistics |          0 | NULL          |
+-----------------------+---------+-------+-------------------+------------+---------------+

因为memory/mysys/IO_CACHE是global_statistics的,只保存在global维度上,在线程维度上是查不到的,所以是正常现象。但这里的memory/mysys/IO_CACHE高还是没有查到。(我以为内存泄露了。。。) 但得出三个现象 1.这个内存主要是在IO_CACHE模块 。

2.sys.memory_global_by_current_bytes 中的平均值很高,最少800M以上,那么开辟这个内存存在基数很大的值。

3.进程使用的实际内存并没有涨,而且增长是没有规律的,那么说明开辟了并没有写大量的数据。

思路一:

发现这个问题和定位这个问题时间间隔了一晚,环境在这间隔的时间IO_CACHE正好有增长,是不是有什么大操作导致呢?后面就看slow_log,但没有发现异常语句,这个路就结束了。

思路二:

定位这个问题时,我对pfs内存管理模块理解错误,以为是内存泄露,就是使用通用的内存泄露定位方法,记录了进程的pmap信息,想通过dump增长段内容的方式分析,但这是个远程环境,一点点分析内存太难操作了,就搁浅了。在这期间还分析了进程打开的临时文件这些,IO_CACHE可能会涉及到文件,但也没有异常。

思路三:

结合variables 信息,在现象2中,可以知道这个IO_CACHE开辟的内存很大,结合variables中%size大于800M,且可能使用到IO_CACHE的变量去找对应使用IO_CACHE代码,最后找到了是线程在一次记录binlog模块时开辟的IO_CACHE导致,对应的变量为binlog_cache_size,并在线程退出才释放。

show variables like 'binlog_cache_size';
+-------------------+-------------+
| Variable_name     | Value       |
+-------------------+-------------+
| binlog_cache_size | 10737418240 |
+-------------------+-------------+

并在本地复现了这个问题。

小结

  1. 在使用mysql监控指标时,一定要确定这些指标的意义,避免误判。
  2. 对mysql的空间相关的配置,一定要仔细研究,不能随意配,如果在案例二,有大量的大事务需要记录binlog,可能会把内存吃满,导致OOM。

扩展

在percona版本中,如果使用了jemalloc,可以通过如下两个表查看jemalloc管理的内存状态。

performance_schema.malloc_stats

select * from malloc_stats;
+-------+-----------+---------+---------+-----------+
| TYPE  | ALLOCATED | NMALLOC | NDALLOC | NREQUESTS |
+-------+-----------+---------+---------+-----------+
| small | 145390048 |  588903 |       0 |    594144 |
| large | 199573504 |    5227 |       0 |      8571 |
| huge  |         0 |       0 |       0 |         0 |
+-------+-----------+---------+---------+-----------+

performance_schema.malloc_stats_totals

select * from malloc_stats_totals;
+-----------+-----------+-----------+----------+----------+----------+
| ALLOCATED | ACTIVE    | MAPPED    | RESIDENT | RETAINED | METADATA |
+-----------+-----------+-----------+----------+----------+----------+
| 672081504 | 678526976 | 742391808 |        0 |        0 |        0 |
+-----------+-----------+-----------+----------+----------+----------+

相关解释请看jemalloc对应版本的文档。

附录

sys.memory_global_total表

显示总内存

GreatDB Cluster> select * from sys.memory_global_total;
+-----------------+
| total_allocated |
+-----------------+
| 522.28 MiB      |
+-----------------+
1 row in set (0.01 sec)

sys.memory_by_thread_by_current_bytes表

该表用于显示每个线程占用内存的情况,可以显示占用内存最大的N条记录,如下:

GreatDB Cluster> select thread_id tid, user, current_allocated ca, total_allocated from sys.memory_by_thread_by_current_bytes order by total_allocated desc limit 10;
+-----+-----------------------------------------+------------+-----------------+
| tid | user                                    | ca         | total_allocated |
+-----+-----------------------------------------+------------+-----------------+
|  59 | group_rpl/THD_certifier_broadcast       | 1.28 KiB   | 8.40 MiB        |
|  33 | innodb/buf_dump_thread                  |  334 bytes | 63.44 KiB       |
|   1 | sql/main                                | 1.11 MiB   | 6.23 MiB        |
|  54 | greatdb/sqlnode-manager                 | 29.38 KiB  | 51.50 KiB       |
|  36 | innodb/srv_worker_thread                | 1.36 KiB   | 4.06 MiB        |
|  38 | innodb/srv_worker_thread                | 1.28 KiB   | 396.96 KiB      |
|  43 | sql/event_scheduler                     | 16.38 KiB  | 31.46 KiB       |
|  50 | greatdb connection pool/connection pool | 3.45 MiB   | 3.48 MiB        |
|  24 | innodb/srv_master_thread                | 1.28 KiB   | 3.00 MiB        |
|  53 | greatdb/task-mgr                        | 324.55 KiB | 288.93 MiB      |
+-----+-----------------------------------------+------------+-----------------+
10 rows in set (0.40 sec)

performance_schema.memory_summary_by_account_by_event_name表

根据accountevent_name显示内存的使用情况

GreatDB Cluster> select USER, HOST, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_account_by_event_name limit 5;
+------+------+-----------------------------------------------------+-------------+---------------------------+
| USER | HOST | EVENT_NAME                                          | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+------+------+-----------------------------------------------------+-------------+---------------------------+
| NULL | NULL | memory/sql/Locked_tables_list::m_locked_tables_root |           0 |                         0 |
| NULL | NULL | memory/sql/display_table_locks                      |           0 |                         0 |
| NULL | NULL | memory/sql/THD::transactions::mem_root              |           0 |                         0 |
| NULL | NULL | memory/sql/Delegate::memroot                        |           0 |                         0 |
| NULL | NULL | memory/sql/THD::main_mem_root                       |          57 |                    668616 |
+------+------+-----------------------------------------------------+-------------+---------------------------+
5 rows in set (0.00 sec)

performance_schema.memory_summary_by_host_by_event_name表

根据hostevent_name显示内存的使用情况

GreatDB Cluster> select HOST, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_host_by_event_name order by COUNT_ALLOC desc limit 5;
+-----------+-------------------------------------+-------------+---------------------------+
| HOST      | EVENT_NAME                          | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+-----------+-------------------------------------+-------------+---------------------------+
| NULL      | memory/innodb/sync0rw               |      213072 |                  18750336 |
| NULL      | memory/sql/Sid_map::Node            |      189438 |                  16668688 |
| localhost | memory/innodb/sync0rw               |      153793 |                  13533784 |
| localhost | memory/sql/Gtid_set::Interval_chunk |      135606 |                  25907256 |
| localhost | memory/sql/Sid_map::Node            |      113704 |                   5606276 |
+-----------+-------------------------------------+-------------+---------------------------+
5 rows in set (0.01 sec)

performance_schema.memory_summary_by_thread_by_event_name表

根据threadevent_name显示内存的使用情况

GreatDB Cluster> select THREAD_ID, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_thread_by_event_name order by COUNT_ALLOC desc limit 5;
+-----------+-------------------------------------+-------------+---------------------------+
| THREAD_ID | EVENT_NAME                          | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+-----------+-------------------------------------+-------------+---------------------------+
|        34 | memory/sql/Sid_map::Node            |      193171 |                  16997308 |
|        53 | memory/innodb/sync0rw               |      120237 |                  10580856 |
|        58 | memory/sql/Gtid_set::Interval_chunk |      111325 |                  22264664 |
|        58 | memory/sql/Sid_map::Node            |       57961 |                   2858172 |
|        59 | memory/sql/Gtid_set::to_string      |       57807 |                   2869830 |
+-----------+-------------------------------------+-------------+---------------------------+
5 rows in set (0.04 sec)

performance_schema.memory_summary_by_user_by_event_name表

根据userevent_name显示内存的使用情况

GreatDB Cluster> select USER, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_user_by_event_name order by COUNT_ALLOC desc limit 5;
+------+-------------------------------------+-------------+---------------------------+
| USER | EVENT_NAME                          | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+------+-------------------------------------+-------------+---------------------------+
| NULL | memory/innodb/sync0rw               |      213686 |                  18804368 |
| NULL | memory/sql/Sid_map::Node            |      192502 |                  16938320 |
| root | memory/innodb/sync0rw               |      155684 |                  13700192 |
| root | memory/sql/Gtid_set::Interval_chunk |      137870 |                  26342488 |
| root | memory/sql/Sid_map::Node            |      115540 |                   5696852 |
+------+-------------------------------------+-------------+---------------------------+
5 rows in set (0.01 sec)

performance_schema.memory_summary_global_by_event_name表

根据event_name显示全局内存的使用情况

GreatDB Cluster> select EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_global_by_event_name order by COUNT_ALLOC desc limit 6;
+---------------------------------------+-------------+---------------------------+
| EVENT_NAME                            | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+---------------------------------------+-------------+---------------------------+
| memory/innodb/sync0rw                 |      370520 |                  32605760 |
| memory/sql/Sid_map::Node              |      310287 |                  22800252 |
| memory/sql/Gtid_set::Interval_chunk   |      138922 |                  26542304 |
| memory/group_rpl/GCS_XCom::xcom_cache |      112561 |                  54620591 |
| memory/sql/Gtid_set::to_string        |      100779 |                   4917058 |
| memory/sql/dd::String_type            |       77358 |                  34742264 |
+---------------------------------------+-------------+---------------------------+
6 rows in set (0.00 sec)
© 万
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论