首页 > 系统相关 >PFS内存统计信息的聚合与准确性问题(四)

PFS内存统计信息的聚合与准确性问题(四)

时间:2023-08-02 17:59:21浏览次数:53  
标签:count stat capacity free 准确性 PFS 内存 memory size

内存统计信息的聚合

内存统计信息的聚合总共有5个维度,也分别对应以下5张表,分别是:

  • MEMORY_SUMMARY_BY_ACCOUNT_BY_EVENT_NAME
  • MEMORY_SUMMARY_BY_HOST_BY_EVENT_NAME
  • MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
  • MEMORY_SUMMARY_BY_USER_BY_EVENT_NAME
  • MEMORY_SUMMARY_GLOBAL_BY_EVENT_NAME

其包括内存分配、释放、聚合的涉及的入口函数及架构如下:

可以看到最基础的维度为thread,然后再往父bucket中进行聚合。

聚合的“主要”逻辑堆栈如下:

|PSI_THREAD_CALL(delete_current_thread)
|--pfs_delete_current_thread_vc
|----aggregate_thread
|------aggregate_thread_memory
|--------aggregate_all_memory_with_reassign
|----------memory_full_aggregate_with_reassign
|--------aggregate_all_memory
|----------memory_full_aggregate

在线程退出时,会调用PSI_THREAD_CALL(delete_current_thread)接口进行该连接相关的内存统计信息的聚合,并释放pfs thread对象。

其核心逻辑函数为aggregate_thread_memory,代码如下:

void aggregate_thread_memory(bool alive, PFS_thread *thread,
                             PFS_account *safe_account, PFS_user *safe_user,
                             PFS_host *safe_host) {
  if (thread->read_instr_class_memory_stats() == nullptr) {
    return;
  }

  if (likely(safe_account != nullptr)) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
      to MEMORY_SUMMARY_BY_ACCOUNT_BY_EVENT_NAME.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_account->write_instr_class_memory_stats(),
        global_instr_class_memory_array);

    return;
  }

  if ((safe_user != nullptr) && (safe_host != nullptr)) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME to:
      -  MEMORY_SUMMARY_BY_USER_BY_EVENT_NAME
      -  MEMORY_SUMMARY_BY_HOST_BY_EVENT_NAME
      in parallel.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_user->write_instr_class_memory_stats(),
        safe_host->write_instr_class_memory_stats(),
        global_instr_class_memory_array);
    return;
  }

  if (safe_user != nullptr) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME to:
      -  MEMORY_SUMMARY_BY_USER_BY_EVENT_NAME
      -  MEMORY_SUMMARY_GLOBAL_BY_EVENT_NAME
      in parallel.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_user->write_instr_class_memory_stats(),
        global_instr_class_memory_array, global_instr_class_memory_array);
    return;
  }

  if (safe_host != nullptr) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
      to MEMORY_SUMMARY_BY_HOST_BY_EVENT_NAME, directly.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_host->write_instr_class_memory_stats(),
        global_instr_class_memory_array);
    return;
  }
  /*
    Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
    to MEMORY_SUMMARY_GLOBAL_BY_EVENT_NAME.
  */
  aggregate_all_memory(alive, thread->write_instr_class_memory_stats(),
                       global_instr_class_memory_array);
}

根据代码可知,thread维度最为基础维度,通常情况下用户线程的内存统计信息都会从thread维度汇聚到account维度;MySQL后台线程或插件等会被聚合到global维度。所以这里我们主要关注account和global维度的具体聚合逻辑。

account维度的聚合实现主要通过aggregate_all_memory_with_reassign(bool alive,

                                        PFS_memory_safe_stat *from_array,

                                        PFS_memory_shared_stat *to_array,

                                        PFS_memory_shared_stat *global_array)函数实现,其中alive总是为false,from_array为thread维度的instruments内存统计信息数组指针,to_array为account维度的instruments内存统计信息数据指针,global_array为global维度的instruments内存统计信息数组指针。具体实现代码如下:

void aggregate_all_memory_with_reassign(bool alive,
                                        PFS_memory_safe_stat *from_array,
                                        PFS_memory_shared_stat *to_array,
                                        PFS_memory_shared_stat *global_array) {
  PFS_memory_safe_stat *from;
  PFS_memory_safe_stat *from_last;
  PFS_memory_shared_stat *to;

  from = from_array;
  from_last = from_array + memory_class_max;
  to = to_array;

  if (alive) {
    for (; from < from_last; from++, to++) {
      memory_partial_aggregate(from, to);
    }
  } else {
    PFS_memory_shared_stat *global;
    global = global_array;
    for (; from < from_last; from++, to++, global++) {
      memory_full_aggregate_with_reassign(from, to, global);
      from->reset();
    }
  }
}

在for循环中依次迭代from_array中的instrument内存统计信息往父bucket中进行聚合,其聚合实现代码如下:

void memory_full_aggregate_with_reassign(const PFS_memory_safe_stat *from,
                                         PFS_memory_shared_stat *stat,
                                         PFS_memory_shared_stat *global) {
  if (!from->m_used) {
    return;
  }

  stat->m_used = true;

  size_t alloc_count = from->m_alloc_count;
  size_t free_count = from->m_free_count;
  size_t alloc_size = from->m_alloc_size;
  size_t free_size = from->m_free_size;

  size_t net;
  size_t capacity;

  if (likely(alloc_count <= free_count)) {  
    /* Nominal path */
    stat->m_alloc_count += alloc_count;
    stat->m_free_count += free_count;
    stat->m_alloc_count_capacity += from->m_alloc_count_capacity;
    stat->m_free_count_capacity += from->m_free_count_capacity;
  } else {      
    global->m_used = true;

    stat->m_alloc_count += free_count; /* base */
    stat->m_free_count += free_count;

    /* Net memory contributed affected to the global bucket directly. */
    net = alloc_count - free_count;
    global->m_alloc_count += net;

    stat->m_alloc_count_capacity += from->m_alloc_count_capacity;

    size_t free_count_capacity;
    free_count_capacity = from->m_free_count_capacity;
    capacity = std::min(free_count_capacity, net);
    free_count_capacity -= capacity;

    /*
      Corresponding low watermark split between the parent and global
      bucket.
    */
    stat->m_free_count_capacity += free_count_capacity;
    global->m_free_count_capacity += capacity;
  }

  if (likely(alloc_size <= free_size)) {
    /* Nominal path. */
    stat->m_alloc_size += alloc_size;
    stat->m_free_size += free_size;
    stat->m_alloc_size_capacity += from->m_alloc_size_capacity;
    stat->m_free_size_capacity += from->m_free_size_capacity;
  } else {
    /* Global net alloc. */
    global->m_used = true;

    stat->m_alloc_size += free_size; /* base */
    stat->m_free_size += free_size;

    net = alloc_size - free_size;
    global->m_alloc_size += net;

    stat->m_alloc_size_capacity += from->m_alloc_size_capacity;

    size_t free_size_capacity;
    free_size_capacity = from->m_free_size_capacity;
    capacity = std::min(free_size_capacity, net);
    free_size_capacity -= capacity;

    stat->m_free_size_capacity += free_size_capacity;
    global->m_free_size_capacity += capacity;
  }
}

通过代码可知,通常情况下是直接将thread维度的统计信息属性值与account维度的统计信息属性值进行相加来进行聚合,即(alloc_count <= free_count)。

但还有一种情况,即父线程调用子线程进行一些工作,通常有两种场景,比如1.event调度,调度线程调用worker线程进行工作 2. main线程接受用户连接进行工作。这两种情况会产生内存分配与释放在同一线程的不均衡,比如内存的分配是在X线程进行的,但是内存的释放却是在Y线程进行的。这里的情况就对应函数中的另一半逻辑,即(alloc_count > free_count),此时可以理解为一个用户连接分配了一些global类型的内存(内存claim),在用户连接退出时,会将global类型的这部分内存直接聚合到global级别。

通过以上的逻辑分析,我们可以得出一个结论:内存统计信息是从thread维度往父bucket中进行聚合,其中通常会聚合到account维度和global维度,而在进行account维度的聚合时,通常情况下就是将thread维度的统计信息属性值直接与account维度的统计信息属性值相加,如果存在内存claim的情况,则会将额外分配的global类型的内存聚合到global级别。

从thread维度直接聚合到global维度的主要实现函数为memory_full_aggregate,具体实现代码如下:

void memory_full_aggregate(const PFS_memory_safe_stat *from,
                           PFS_memory_shared_stat *stat) {
  if (!from->m_used) {
    return;
  }
  stat->m_used = true;

  stat->m_alloc_count += from->m_alloc_count;
  stat->m_free_count += from->m_free_count;
  stat->m_alloc_size += from->m_alloc_size;
  stat->m_free_size += from->m_free_size;

  stat->m_alloc_count_capacity += from->m_alloc_count_capacity;
  stat->m_free_count_capacity += from->m_free_count_capacity;
  stat->m_alloc_size_capacity += from->m_alloc_size_capacity;
  stat->m_free_size_capacity += from->m_free_size_capacity;
}

这一部分的代码相对比较简单,直接就是将thread维度的统计信息属性值与global维度的统计信息属性值相加即可。

统计信息与OS内存使用不一致的问题

现象1:统计信息大于OS使用量

当前实例使用内存为1.4G

Buffer Pool设置为1G

相关内存统计信息为1G

此时将Buffer Pool调整为2G

如果此时分配的内存都被使用了,那么在OS上看到的实例使用的内存应该至少是大于2G的,但此时实际使用量却是1.6G。

此时产生的现象就是在OS层面观测到的实例实际使用的内容量与MySQL内存统计信息里看到的不一致。

问题产生的原因主要是Linux内存主要是通过虚拟内存文件映射的方式来管理的,当MySQL向Linux申请内存时,分配的都是虚拟内存,并没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,会发生缺页中断,操作系统再进行物理内存的分配,然后建立虚拟内存和物理内存之间的映射关系。

现象2:内存统计信息出现负值

在上一章节关于内存统计信息聚合的介绍中,我们看到在memory_full_aggregate_with_reassign函数中,内存聚合有两个路径,其中一个是(likely(alloc_size <= free_size)),从这个条件中我们可以看到alloc_size是有可能会小于free_size的,在PS表中,CURRENT_NUMBER_OF_BYTES_USED的值是通过alloc_size-free-size计算出来的,所以CURRENT_NUMBER_OF_BYTES_USED的值就也可能会出现负值。

而出现负值显然是不合理的,此种情况就属于是Bug缺陷了。在MariaDB的官方jira中,也找到了相关类似的issue:

https://jira.mariadb.org/browse/MDEV-23936,不过看记录已经过去三年了,官方也没有给出相关解释或解决方案。

还有一点,由于sys.memory_global_total表是performance_schema.memory_summary_global_by_event_name的视图,而total_allocated是通过sum(CURRENT_NUMBER_OF_BYTES_USED)得到的,那么既然CURRENT_NUMBER_OF_BYTES_USED的值是有可能会出现负值,所有total_allocated这个总计值就也不可信了。还有一点,我们之前讲过,内存统计信息的聚合是有几个不同的维度的,并不是所有的统计信息最后都会被聚合到Global级别,所有将sys.memory_global_total的total_allocated值作为内存分配量的统计值是一点不准确的。

现象3:存在未被统计的内存操作

在MySQL相关代码中,明确定义了只有这些文件中的内存分配会被PFS监控,这里这列出了一部分的MySQL代码文件,所以一定存在未被统计的内存。

/** List of filenames that allocate memory and are instrumented via PFS. */
static constexpr const char *auto_event_names[] = {
    /* Keep this list alphabetically sorted. */
    "api0api",
    "api0misc",
    "btr0btr",
    "btr0bulk",
    "btr0cur",
    "btr0pcur",
    "btr0sea",
    "btr0types",
    "buf",
    "buf0buddy",
    "buf0buf",
    "buf0checksum",
    "buf0dblwr",
    "buf0dump",
    "buf0flu",
    "buf0lru",
    "buf0rea",
    "buf0stats",
    "buf0types",
    "checksum",
    "crc32",
    "create",
    "data0data",
    "data0type",
    "data0types",
    "db0err",
    "dict",
    "dict0boot",
    "dict0crea",
    "dict0dd",
    "dict0dict",
    "dict0load",
    "dict0mem",
    "dict0priv",
    "dict0sdi",
    "dict0stats",
    "dict0stats_bg",
    "dict0types",
    "dyn0buf",
    "dyn0types",
    "eval0eval",
    "eval0proc",
    "fil0fil",
    "fil0types",
    "file",
    "fsp0file",
    "fsp0fsp",
    "fsp0space",
    "fsp0sysspace",
    "fsp0types",
    "fts0ast",
    "fts0blex",
    "fts0config",
    "fts0fts",
    "fts0opt",
    "fts0pars",
    "fts0plugin",
    "fts0priv",
    "fts0que",
    "fts0sql",
    "fts0tlex",
    "fts0tokenize",
    "fts0types",
    "fts0vlc",
    "fut0fut",
    "fut0lst",
    "gis0geo",
    "gis0rtree",
    "gis0sea",
    "gis0type",
    "ha0ha",
    "ha0storage",
    "ha_innodb",
    "ha_innopart",
    "ha_prototypes",
    "handler0alter",
    "hash0hash",
    "i_s",
    "ib0mutex",
    "ibuf0ibuf",
    "ibuf0types",
    "lexyy",
    "lob0lob",
    "lock0iter",
    "lock0lock",
    "lock0prdt",
    "lock0priv",
    "lock0types",
    "lock0wait",
    "log0log",
    "log0recv",
    "log0write",
    "mach0data",
    "mem",
    "mem0mem",
    "memory",
    "mtr0log",
    "mtr0mtr",
    "mtr0types",
    "os0atomic",
    "os0event",
    "os0file",
    "os0numa",
    "os0once",
    "os0proc",
    "os0thread",
    "page",
    "page0cur",
    "page0page",
    "page0size",
    "page0types",
    "page0zip",
    "pars0grm",
    "pars0lex",
    "pars0opt",
    "pars0pars",
    "pars0sym",
    "pars0types",
    "que0que",
    "que0types",
    "read0read",
    "read0types",
    "rec",
    "rem0cmp",
    "rem0rec",
    "rem0types",
    "row0ext",
    "row0ftsort",
    "row0import",
    "row0ins",
    "row0log",
    "row0merge",
    "row0mysql",
    "row0purge",
    "row0quiesce",
    "row0row",
    "row0sel",
    "row0types",
    "row0uins",
    "row0umod",
    "row0undo",
    "row0upd",
    "row0vers",
    "sess0sess",
    "srv0conc",
    "srv0mon",
    "srv0srv",
    "srv0start",
    "srv0tmp",
    "sync0arr",
    "sync0debug",
    "sync0policy",
    "sync0sharded_rw",
    "sync0rw",
    "sync0sync",
    "sync0types",
    "trx0i_s",
    "trx0purge",
    "trx0rec",
    "trx0roll",
    "trx0rseg",
    "trx0sys",
    "trx0trx",
    "trx0types",
    "trx0undo",
    "trx0xa",
    "usr0sess",
    "usr0types",
    "ut",
    "ut0byte",
    "ut0counter",
    "ut0crc32",
    "ut0dbg",
    "ut0link_buf",
    "ut0list",
    "ut0lock_free_hash",
    "ut0lst",
    "ut0mem",
    "ut0mutex",
    "ut0new",
    "ut0pool",
    "ut0rbt",
    "ut0rnd",
    "ut0sort",
    "ut0stage",
    "ut0ut",
    "ut0vec",
    "ut0wqueue",
    "zipdecompress",
};

我们找一个未被统计的案例,可以看到,在下图的堆栈中,在内存跟踪接口函数pfs_memory_alloc_vc中,在尝试进行内存分配跟踪时,由于找不到相关的Performance Schema Key,则函数直接return,不再进行后续的内存分配跟踪。(通过堆栈可以看到,该内存分配相关动作为后台master线程在进行主循环):

  TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  
  TRANSLATE with COPY THE URL BELOW Back EMBED THE SNIPPET BELOW IN YOUR SITE Enable collaborative features and customize widget: Bing Webmaster Portal Back     此页面的语言为英语   翻译为中文(简体)        
  • 中文(简体)
  • 中文(繁体)
  • 丹麦语
  • 乌克兰语
  • 乌尔都语
  • 亚美尼亚语
  • 俄语
  • 保加利亚语
  • 克罗地亚语
  • 冰岛语
  • 加泰罗尼亚语
  • 匈牙利语
  • 卡纳达语
  • 印地语
  • 印尼语
  • 古吉拉特语
  • 哈萨克语
  • 土耳其语
  • 威尔士语
  • 孟加拉语
  • 尼泊尔语
  • 布尔语(南非荷兰语)
  • 希伯来语
  • 希腊语
  • 库尔德语
  • 德语
  • 意大利语
  • 拉脱维亚语
  • 挪威语
  • 捷克语
  • 斯洛伐克语
  • 斯洛文尼亚语
  • 旁遮普语
  • 日语
  • 普什图语
  • 毛利语
  • 法语
  • 波兰语
  • 波斯语
  • 泰卢固语
  • 泰米尔语
  • 泰语
  • 海地克里奥尔语
  • 爱沙尼亚语
  • 瑞典语
  • 立陶宛语
  • 缅甸语
  • 罗马尼亚语
  • 老挝语
  • 芬兰语
  • 英语
  • 荷兰语
  • 萨摩亚语
  • 葡萄牙语
  • 西班牙语
  • 越南语
  • 阿塞拜疆语
  • 阿姆哈拉语
  • 阿尔巴尼亚语
  • 阿拉伯语
  • 韩语
  • 马尔加什语
  • 马拉地语
  • 马拉雅拉姆语
  • 马来语
  • 马耳他语
  • 高棉语
 

标签:count,stat,capacity,free,准确性,PFS,内存,memory,size
From: https://www.cnblogs.com/wagaga/p/17601354.html

相关文章