记一次mongodb内存使用过高的排查解决方案

最近经常收到mongodb一个从库内存使用率过高的告警,于是进行排查和解决,下面是过程

# 查看mongo服务使用的内存大小
db.serverStatus().mem
{ "bits" : 64, 
"resident" : 23127,     # 物理内存,单位是MB
"virtual" : 29093,      # 虚拟内存,单位是MB
"supported" : true }


# 这个是查看wiredTiger占用了多少内存
db.serverStatus().wiredTiger.cache
{
        "application threads page read from disk to cache count" : 204298331,
        "application threads page read from disk to cache time (usecs)" : 38747559298,
        "application threads page write from cache to disk count" : 106092771,
        "application threads page write from cache to disk time (usecs)" : 17318222221,
        "bytes allocated for updates" : 296788997,
        "bytes belonging to page images in the cache" : 16482438247,
        "bytes belonging to the history store table in the cache" : 64156976,
        "bytes currently in the cache" : 17193353577,                                        # 占用的内存,实际缓存的数据大小
        "bytes dirty in the cache cumulative" : NumberLong("4525111900725"),
        "bytes not belonging to page images in the cache" : 710915329,
        "bytes read into cache" : NumberLong("8704327108782"),
        "bytes written from cache" : NumberLong("10127087031660"),
		......
}


db.serverStatus().tcmalloc
{
        "generic" : {
                "current_allocated_bytes" : NumberLong("16824344000"),    # 已分配的内存
                "heap_size" : NumberLong("28263317504")
        },
        "tcmalloc" : {
                "pageheap_free_bytes" : NumberLong("8540606464"),         # 堆空闲内存占用,这个较大的话需要回收
                "pageheap_unmapped_bytes" : NumberLong(2132684800),
                "max_total_thread_cache_bytes" : NumberLong(1073741824),
                "current_total_thread_cache_bytes" : 117897648,
                "total_free_bytes" : 765682240,                           # 总空闲内存
                "central_cache_free_bytes" : 647679184,
                "transfer_cache_free_bytes" : 105408,
                "thread_cache_free_bytes" : 117897648,
                "aggressive_memory_decommit" : 0,
                "pageheap_committed_bytes" : NumberLong("26130632704"),
                "pageheap_scavenge_count" : 13037917,
                "pageheap_commit_count" : 16333243,
                "pageheap_total_commit_bytes" : NumberLong("4585848258560"),
                "pageheap_decommit_count" : 13687337,
                "pageheap_total_decommit_bytes" : NumberLong("4559717625856"),
                "pageheap_reserve_count" : 8988,
                "pageheap_total_reserve_bytes" : NumberLong("28263317504"),
                "spinlock_total_delay_ns" : NumberLong("75789350902"),
                "release_rate" : 1,
                "formattedString" : "------------------------------------------------\nMALLOC:    16824344576 (16044.9 MiB) Bytes in use by application\nMALLOC: +   8540606464 ( 8145.0 MiB) Bytes in page heap freelist\nMALLOC: +    647679184 (  617.7 MiB) Bytes in central cache freelist\nMALLOC: +       105408 (    0.1 MiB) Bytes in transfer cache freelist\nMALLOC: +    117897072 (  112.4 MiB) Bytes in thread cache freelists\nMALLOC: +    100925440 (   96.2 MiB) Bytes in malloc metadata\nMALLOC:   ------------\nMALLOC: =  26231558144 (25016.4 MiB) Actual memory used (physical + swap)\nMALLOC: +   2132684800 ( 2033.9 MiB) Bytes released to OS (aka unmapped)\nMALLOC:   ------------\nMALLOC: =  28364242944 (27050.2 MiB) Virtual address space used\nMALLOC:\nMALLOC:         482469              Spans in use\nMALLOC:            301              Thread heaps in use\nMALLOC:           4096              Tcmalloc page size\n------------------------------------------------\nCall ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).\nBytes released to the OS take up virtual address space but no physical memory.\n"
        }
}

Call ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).
Bytes released to the OS take up virtual address space but no physical memory.

# 可以看到pageheap_free_bytes占了大头。

解释一下:
Virtual address space used 是mongo总的使用的虚拟内存。
Actual memory used (physical + swap)是mongo总的使用的实际内存。(我没有开swap)

实际内存又分成两部分,freelist中的和非freelist的。freelist的就是已经分配后来又用完释放的内存,
存在这个freelist数据结构中,已备后面重用这些内存,我的理解就是我用完了,但是我先拿着。
这样后面的业务来了,mongo就不需要再向os申请分配内存这一步了,从性能和效率的维度来看更好。

但是缺点是内存一直没有还给os,导致os角度来看,内存的使用率很高。

tcmalloc 为性能考虑,每个线程会有自己的 local free page cache,还有 central free page cache;
内存申请时,按 local thread free page cache ==> central free page cache 查找可用内存,找不到可用内存时才会从堆上申请;
当释放内存时,也会归还到 cache 里,tcmalloc 后台慢慢再归还给 OS操作系统, 
多数情况下,内存使用率高的原因是 tcmalloc 未能及时将内存归还给操作系统,导致内存最大可能达到几十GB。
mongo为了提高性能,倾向于利用os上尽可能多的内存。


解决办法:
所以可以将freelist的内存及时释放给os,可以解决内存水位过高的问题。

db.adminCommand({setParameter:1,tcmallocAggressiveMemoryDecommit:1})
tcmallocAggressiveMemoryDecommit 是一个服务器参数,用于控制 TCMalloc 内存分配器在什么程度上积极地将不再使用的内存释放回操作系统。
当设置为 1(开启状态)时,tcmallocAggressiveMemoryDecommit 会使 TCMalloc 更积极地释放不再使用的内存。
这意味着当应用程序释放内存后,TCMalloc 会尝试将这部分内存标记为空闲并返回给操作系统,
而不是保留在进程的地址空间中以便快速重用。
posted @ 2025-05-12 22:16  有形无形  阅读(348)  评论(0)    收藏  举报