mmap(2) したファイルのページキャッシュの reclaim

hiboma.hatenadiary.jp

sysctl vm.drop_caches=1mmap(2) しているファイルのページキャッシュを破棄することはできなかったが、他にはどういう方法があるんだっけ?

( 何か困っているケースがあるわけでなくて挙動を確かめたいという興味・関心で調べている )

memory pressure をかける

1. 大きな無名ページをもつプロセスを作る

ホストの RAM と同程度の無名ページを使うプロセスを作れば、ページキャッシュを追い出せる ( reclaim されると書いた方が正確だろうか )

#include <stdlib.h>

int main() {
  long size = 1L * 4 * 1024 * 1024 * 1024;

  char *p = malloc(size);
  for ( long i = 0; i < size; i += 4096) { 
      /* cause minor page fault */
      p[i] = '@';
  }
}

上記のような4GB の無名ページを使うコードを実行して検証する

before

top - 08:46:16 up  6:22,  6 users,  load average: 0.39, 0.15, 0.09
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3881976 total,  2708252 free,    51452 used,  1122272 buff/cache
KiB Swap:  1572860 total,  1490584 free,    82276 used.  3595604 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                  
 3972 vagrant   20   0 1052728 1.000g 1.000g S   0.0 27.0   0:00.95 .mmap                                                                                                                                                                                    

after

top - 08:47:14 up  6:23,  6 users,  load average: 0.32, 0.18, 0.10
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3881976 total,  3786392 free,    49364 used,    46220 buff/cache
KiB Swap:  1572860 total,  1488288 free,    84572 used.  3679080 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                  
 3972 vagrant   20   0 1052728    436    436 S   0.0  0.0   0:00.95 .mmap                                                                                                                                                                                    

スワップアウトも起こる状態なので、そりゃそうだよね感

2. 大きなファイルを読み込む

別のページキャッシュを増やしまくることで mmap 分のページキャッシュを追い出せる

before

top - 02:30:28 up 7 min,  3 users,  load average: 0.31, 0.16, 0.08
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3881976 total,   114612 free,   106444 used,  3660920 buff/cache
KiB Swap:  1572860 total,  1572860 free,        0 used.  3496964 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                  
 2295 vagrant   20   0 1052728 1.000g 1.000g S   0.0 27.0   0:01.06 .mmap                                                                                                                                                                                    

適当につくった巨大なファイルを cat でページキャッシュにのせる

cat /tmp/4000mb.txt > /dev/null
cat /tmp/4000mb.txt > /dev/null

# 2回実行することで、確実に mmap しているプロセス分のページキャッシュを追い出せる
# Inactive(file) LRU の挙動なんだろうけど、正確に説明できないな 

after

top - 02:31:31 up 8 min,  3 users,  load average: 0.37, 0.21, 0.10
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3881976 total,   144908 free,   107312 used,  3629756 buff/cache
KiB Swap:  1572860 total,  1572860 free,        0 used.  3527340 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                  
 2295 vagrant   20   0 1052728    404    320 S   0.0  0.0   0:01.06 .mmap                                                                                                                                                                                    

RssFile もこの通り減っている

[vagrant@localhost ~]$ grep RssFile /proc/$(pgrep mmap)/status
RssFile:             424 kB

実行中の sar -B 1 を取ると以下のように数値が変化する

[vagrant@localhost ~]$ sar -B 1
Linux 3.10.0-514.26.2.el7.x86_64 (localhost.localdomain)        2017年08月25日  _x86_64_        (2 CPU)

02時44分11秒  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s    %vmeff
02時44分12秒      0.00      0.00     54.46      0.00     39.60      0.00      0.00      0.00      0.00
02時44分13秒      0.00      0.00     44.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分14秒 236856.00      0.00    332.00      1.00    165.00      0.00      0.00      0.00      0.00 🔥 1回目の cat
02時44分15秒 483328.00      0.00     18.00      0.00    183.00      0.00      0.00      0.00      0.00
02時44分16秒 491520.00      0.00     18.00      0.00    167.00      0.00      0.00      0.00      0.00
02時44分17秒 466944.00      0.00     18.00      0.00    184.00      0.00      0.00      0.00      0.00
02時44分18秒 450560.00      0.00     18.00      0.00    151.00      0.00      0.00      0.00      0.00
02時44分19秒 458752.00     28.00     20.00      0.00  28676.00 282092.00   8537.00  28497.00      9.81
02時44分20秒 475136.00      0.00     18.00      0.00 109258.00 109109.00      0.00 109109.00    100.00
02時44分21秒 458752.00      0.00     18.00      0.00 123089.00 122964.00      0.00 122964.00    100.00
02時44分22秒 458752.00     22.00     18.00      0.00 109122.00 108966.00      0.00 108966.00    100.00
02時44分23秒 434176.00      0.00     18.00      0.00 116487.00 116299.00      0.00 116299.00    100.00
02時44分24秒 450560.00      0.00     18.00      0.00 109265.00 109082.00      0.00 109082.00    100.00
02時44分25秒 377628.00      0.00     83.00      1.00  95768.00  95537.00      0.00  95533.00    100.00
02時44分26秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分27秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分28秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分29秒   4028.00      0.00   1405.00     25.00    383.00      0.00      0.00      0.00      0.00
02時44分30秒      0.00      0.00     18.00      0.00     31.00      0.00      0.00      0.00      0.00
02時44分31秒      0.00      0.00     83.00      0.00    224.00      0.00      0.00      0.00      0.00
02時44分32秒 384256.00      0.00    330.00      0.00  88813.00  88621.00      0.00  88621.00    100.00 🔥 2回目の cat 
02時44分33秒 491520.00      0.00     18.00      0.00 123285.00 123095.00      0.00 123095.00    100.00
02時44分34秒 516096.00      0.00     18.00      0.00 136552.00 136345.00      0.00 136345.00    100.00
02時44分35秒 770048.00      0.00     18.00      0.00 190944.00 190633.00      0.00 190633.00    100.00
02時44分36秒 704404.00      0.00     18.00      0.00 170837.00 170539.00      0.00 170539.00    100.00
02時44分37秒 606208.00      0.00     18.00      0.00 157094.00 156826.00      0.00 156826.00    100.00
02時44分38秒 655360.00      0.00     18.00      0.00 171118.00 170866.00      0.00 170866.00    100.00
02時44分39秒 729980.20      0.00     19.80      0.00 176368.32 176232.67      0.00 176200.99     99.98
02時44分40秒 377628.00      0.00     76.00      1.00  95306.00  95030.00      0.00  95062.00    100.03
02時44分41秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分42秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分43秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00
02時44分44秒      0.00      0.00     18.00      0.00     38.00      0.00      0.00      0.00      0.00

成功しなかった方法

posix_fadvise(2) + POSIX_FADV_DONTNEED

tech.mercari.com

こちらのエントリを思い出して posix_fadvise(2) + POSIX_FADV_DONTNEED を試してみたが、 mmap(2) しているファイルのページキャッシュは破棄できない

#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
        int fd = open("/tmp/1000mb.txt", O_RDONLY);
        if (fd == -1) {
                perror("open");
                exit(1);
        }

        if(posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) == -1) {
                err(1, "madvise failed");
         }

        exit(0);
}

カーネルのソース

Linux カーネルのソースをよむと POSIX_FADV_DONTNEED を指定した際は invalidate_mapping_pages を呼び出しているのだった。

SYSCALL_DEFINE4(fadvise64_64, int, fd, loff_t, offset, loff_t, len, int, advice)
{

//... 

    case POSIX_FADV_DONTNEED:
        if (!bdi_write_congested(mapping->backing_dev_info))
            __filemap_fdatawrite_range(mapping, offset, endbyte,
                           WB_SYNC_NONE);

        /* First and last FULL page! */
        start_index = (offset+(PAGE_CACHE_SIZE-1)) >> PAGE_CACHE_SHIFT;
        end_index = (endbyte >> PAGE_CACHE_SHIFT);

        if (end_index >= start_index) {
            unsigned long count = invalidate_mapping_pages(mapping,
                        start_index, end_index);

            /*
            * If fewer pages were invalidated than expected then
            * it is possible that some of the pages were on
            * a per-cpu pagevec for a remote CPU. Drain all
            * pagevecs and try again.
            */
            if (count < (end_index - start_index + 1)) {
                lru_add_drain_all();
                invalidate_mapping_pages(mapping, start_index,
                        end_index); ⭐
            }
        }
        break;

先のエントリで調べたように invalidate_mapping_pages では mmap(2) している場合は処理されない

hiboma.hatenadiary.jp

感想

ページキャッシュ確保の meory pressure によって reclam されるけど、 syctl vm.drop_caches=1 では破棄されないのが特殊な挙動だなと思った。

巨大なファイルを mmap(2) しているミドルウェア (例えば Elasticsearch ) が動作しているホストなんかで、別の大きなファイル (例. ログ) を cat や less で調査してたりすると、ミドルウェアmmap(2) で蓄えたページキャッシュを reclaim しちゃうというケースはあるだろうな (それによってどこまでパフォーマンスが下がるのかが主要な問題だけど)