当前位置 > CPDA数据分析师 > “数”业专攻 > swap空间不足导致MySQL被OOM kill案例

swap空间不足导致MySQL被OOM kill案例

来源:数据分析师 CPDA | 时间:2015-11-23 | 作者:admin

背景:

  • 某机器内存256G,安装2实例mysql,每个 buffer_pool各106G,总计212G;
  • 某套DB晚上10:00左右迁移到该环境,第2天早上10:00左右收到OOM kill短信,因swap空间不足一个Mysql实例被强制kill;
  • 该实例mysqld进程没有被彻底清除,而是变成了僵尸进程,导致后续无法重启该实例,最后重启机器才解决。
  • 调查:
  • 151111141698651上图为oom kill后的top输出,因为该mysqld变为僵尸进程故一直没有释放内存。mysql的BP设置为 106G,但是其RES分别达到125G和119G,加起来接近机器物理内存上限,而机器swap只有7G且被消耗完毕。

    至此原因已经很清晰,解决方案也很简单,将BP调小90G。

    注:自调整截至目前超过10天,没有再发生类似故障。

    延伸

    1 mysql内存开销

    Innodb_buffer_pool_size定义了缓存池的大小,但是缓冲池本身需要额外的数据结构进行管理。

    比如,缓冲池每个page都需要一个buf_block_t管理,这部分内存没有计入参数。

    各种额外消耗加起来约占整个BP的8%,也有资料说是10%,具体可参看//mysqlha.blogspot.co.uk/2008/11/innodb-memory-overhead.html。

    这些只是global buffer的开销,加上session buffer,Mysql所需的内存只会更高。

    2 为什么会发生swap

    首先大致说一下linux的内存管理,numa架构下linux内存被分为多个node,非numa则只有1个,由pg_data_t描述,每个node又分为3个zone,由zone_struct结构体描述。

    每个zone都有active_lru和 inactive_lru,每个lru又各分为anon匿名页和file cache映射页链表,总计4个LRU;

    zone同时定义了pages_low,pages_min和pages_high,当zone可用内存小于pages_low时唤醒kswapd回收内存,而当其小于pages_min时则以同步方式唤醒kswapd,直到zone可用内存达到pages_high为止;

    Linux会缓存很多数据,譬如page cache和slab cache,这部分内存在回收时会先同步到磁盘然后直接重用,而对于其他内存页,诸如用户态地址空间的匿名页,以及IPC共享内存区的页,只能将其置换到swap分区,不可直接回收。

    OS何时回收内存?

    1 定期回收:kswapd定期唤醒,当zone空闲内存小于pages_low则进行页面回收,小于pages_min则以同步方式回收;

    2 直接回收:linux为用户进程分配内存或者创建缓冲区,而当前系统又没有足够多物理内存时,则linux会进行页面回收;当OS尝试内存回收后仍无法获取足够多的页面,则调用find_bad_process并进行OOM kill;

  • 151111141698652
  • 不管哪种回收方式,最后都调用shrink_list(),对4个链表的扫描逻辑定义在vmscan.c中的get_scan_count函数内,其变量scan_balance决定了要回收哪个lru的内存,大致逻辑如下:1. 如果系统禁用了swap或者没有swap空间,则只扫描file based的链表,即不进行匿名页链表扫描

    if (!sc->may_swap || (get_nr_swap_pages() <= 0)) {

    scan_balance = SCAN_FILE;

    goto out;

    }

    2. 如果当前进行的不是全局页回收,并且swappiness=0,则不进行匿名页链表扫描

    if (!global_reclaim(sc) && !vmscan_swappiness(sc)) {

    scan_balance = SCAN_FILE;

    goto out;

    }

    3. 如果是全局页回收,并且空闲内存和file based链表page数目相加都小于zone->pages_high,则进行匿名页回收,即便swappiness=0,系统也会进行swap

    if (global_reclaim(sc)) {

    unsigned long zonefile;

    unsigned long zonefree;

    zonefree = zone_page_state(zone, NR_FREE_PAGES);

    zonefile = zone_page_state(zone, NR_ACTIVE_FILE) +

    zone_page_state(zone, NR_INACTIVE_FILE);

    if (unlikely(zonefile + zonefree <= high_wmark_pages(zone))) {

    scan_balance = SCAN_ANON;

    goto out;

    }

    }

    4. 如果系统inactive file链表比较充足,则不考虑进行匿名页的回收,即不进行swap

    if (!inactive_file_is_low(lruvec)) {

    scan_balance = SCAN_FILE;

    goto out;

    }

    至此,我们可以大致了解swappiness=0的作用,其并不能完全禁止swap。

    何时触发OOM kill?

    当系统内存被消耗殆尽,swap分区也被填满的时候,内核无法分配到新的空闲内存,便会启动OOM删除程序;

    其内核调用路径为out_of_memory() – select_bad_process() – oom_kill_process()

    其中select_bad_process()负责挑选待杀死的进程,其扫描系统中的每一个进程并调用oom_badness(),该API逻辑如下:

    1 获取进程的oom_score_adj,如果其等于OOM_SCORE_ADJ_MIN即-1000则不Kill,该参数由/proc/NNN/ oom_score_adj记录,可手工修改

    adj = (long)p->signal->oom_score_adj;

    if (adj == OOM_SCORE_ADJ_MIN) {

    task_unlock(p);

    return 0;

    }

    2 根据该进程消耗的内存计算分数,如果是root进程则乘以3%,尽量避免其被Kill,最后将points返回

    points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +

    atomic_long_read(&p->mm->nr_ptes) + mm_nr_pmds(p->mm);

    task_unlock(p);

    if (has_capability_noaudit(p, CAP_SYS_ADMIN))

    points -= (points * 3) / 100;

    select_bad_process()通过比较每一个进程的oom_badness()返回值,找出得分最高且不是线程组leader的进程,将其返回给out_of_memory(),由其调用oom_kill_process()发送sigkill信号进行扑杀。

    除了杀死进程,Linux可以选择在发生OOM时直接panic,当vm.panic_on_oom=1时成立

    结束语

    至此我们可以大致了解swappiness=0的意义,以及OOM kill发生的原因,为避免此行为应尽量留出充足的内存给OS,一般应为物理内存的20%左右。