Разбираемся с Buffer и Cache в Linux

Разбираемся с Buffer и Cache в Linux


Прежде чем перейти к сегодняшней теме, давайте сначала посмотрим, как система использует память - на примере вывода команды free:

$ free
              total        used        free      shared  buff/cache   available
Mem:        8169348      263524     6875352         668     1030472     7611064
Swap:             0           0           0

Как видно, в этом выводе содержится подробная информация об использовании физической памяти (Mem) и пространства подкачки (Swap): общий объём памяти, использованная, свободная, кэш, доступная память и так далее. Обратите внимание, что показатель buff/cache - это сумма Buffer и Cache.

Большинство этих метрик понять довольно просто, а вот различить Buffer и Cache уже сложнее. Если буквально, Buffer - это временная область хранения данных, и Cache - тоже разновидность временного хранения в памяти. Но знаете ли вы, в чём разница между этими двумя типами «временного хранилища»?

Источник данных для free

Если заглянуть в документацию к free через man, можно найти подробные пояснения по каждому показателю. Например, выполнив man free, вы увидите примерно такое описание:

       buffers
              Memory used by kernel buffers (Buffers in /proc/meminfo)
 
       cache  Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
 
       buff/cache
              Sum of buffers and cache

Из мануала видно, как трактуются buffer и cache:

Buffers - память, используемая буферами ядра; соответствует значению Buffers в /proc/meminfo.

Cache - память, занятная страничным кэшем (page cache) и slab-аллокатором ядра; это сумма Cached и SReclaimable из /proc/meminfo.

То есть free просто берёт данные из /proc/meminfo и агрегирует их. Но такое объяснение не раскрывает, что именно означают Buffers, Cached и SReclaimable на более глубоком уровне - а значит, до реального понимания того, что происходит с памятью, мы ещё не добрались.

Файловая система proc

/proc - это специальная файловая система, которую предоставляет ядро Linux. По сути, это интерфейс для взаимодействия пользователя с ядром. Через неё можно, например, посмотреть текущее состояние ядра и его параметры, изучить статистику и состояние процессов, а в некоторых случаях - даже изменить конфигурацию ядра.

Многие инструменты для анализа производительности берут данные именно отсюда. Как мы уже видели, команда free получает информацию об использовании памяти, читая /proc/meminfo.

Раз уж определения Buffers, Cached и SReclaimable неочевидны, имеет смысл копнуть глубже и разобраться, что именно стоит за этими значениями в /proc.

Если выполнить man proc, откроется подробная документация по файловой системе proc. Документ довольно объёмный, поэтому лучше сразу искать по ключевым словам - например, meminfo, чтобы быстро найти раздел, связанный с памятью.

В документации можно увидеть такие описания:

Buffers %lu
    Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
 
Cached %lu
   In-memory cache for files read from the disk (the page cache).  Doesn't include SwapCached.
...
SReclaimable %lu (since Linux 2.6.19)
    Part of Slab, that might be reclaimed, such as caches.
    
SUnreclaim %lu (since Linux 2.6.19)
    Part of Slab, that cannot be reclaimed on memory pressure.

Buffers - относительно временное хранилище для «сырых» блоков диска, которое обычно не должно разрастаться до больших размеров (порядка 20 МБ или около того).

Cached - кэш в оперативной памяти для файлов, прочитанных с диска (то есть page cache). Значение SwapCached сюда не входит.

SReclaimable - часть Slab-памяти, которую можно освободить при необходимости (например, различные кэши).

SUnreclaim - часть Slab-памяти, которую нельзя освободить даже при нехватке памяти.

Из этой документации можно сделать такие выводы:

Buffers - это временное хранилище «сырых» блоков диска. Иными словами, оно используется для кэширования данных, которые должны быть записаны на диск. Обычно объём такого кэша невелик (примерно около 20 МБ). Это позволяет ядру оптимизировать операции записи: объединять разрозненные мелкие записи в более крупные и эффективные, например, сливать несколько маленьких операций в одну большую.

Cached - это страничный кэш (page cache) для файлов, прочитанных с диска. То есть он хранит данные, полученные при чтении файлов. Благодаря этому при повторном доступе к тем же данным их можно быстро взять из памяти, не обращаясь снова к более медленному диску.

SReclaimable - часть Slab-памяти. Slab, в свою очередь, делится на две части: освобождаемую (reclaimable), которая учитывается в SReclaimable, и неосвобождаемую, которая учитывается в SUnreclaim.

Итак, мы наконец получили более подробные определения этих трёх метрик. На этом этапе можно с облегчением выдохнуть - кажется, что теперь-то всё стало понятно. Но означает ли знание определений, что вы действительно разобрались, как всё работает?

Вот два вопроса, над которыми стоит задуматься:

Первый вопрос. В документации прямо не сказано, кэширует ли Buffer данные при чтении с диска или только при записи. При этом во многих источниках в интернете утверждается, что Buffer используется исключительно для кэширования данных, предназначенных для записи на диск. Так ли это? Может ли он также кэшировать данные, считываемые с диска?

Второй вопрос. В документации говорится, что Cache используется для кэширования данных, прочитанных из файлов. А что насчёт данных, которые записываются в файлы - попадают ли они тоже в Cache?

Чтобы ответить на эти вопросы, дальше мы разберём несколько практических кейсов и посмотрим, как Buffer и Cache ведут себя в разных сценариях.

Разбор кейсов

Все сегодняшние примеры выполнены на Ubuntu, но в целом они актуальны и для других Linux-систем.

Моё тестовое окружение:

  • Конфигурация машины: 2 CPU, 8 ГБ памяти
  • Предустановленный пакет: sysstat (устанавливается через apt install sysstat)

sysstat нужен нам для использования vmstat - с его помощью удобно наблюдать изменения в Buffers и Cache. Те же данные можно получить из /proc/meminfo, но vmstat показывает их в более наглядной форме.

В примерах также используется dd для имитации дискового и файлового ввода-вывода, поэтому мы будем следить и за изменениями I/O.

После установки инструментов откройте два терминала и подключитесь к машине с Ubuntu.

И последний подготовительный шаг: чтобы минимизировать влияние уже существующего кэша, в первом терминале выполните команду для его очистки:

# Clearing Various Caches such as File Pages, Directory Entries, Inodes, etc.
$ echo 3 > /proc/sys/vm/drop_caches

Сценарий 1: запись на диск и в файл

Начнём с первого сценария. В первом терминале запустите vmstat:

# Output One Data Set Every Second
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7743608   1112  92168    0    0     0     0   52  152  0  1 100  0  0
0  0      0 7743608   1112  92168    0    0     0     0   36   92  0  0 100  0  0

В выводе vmstat нас интересуют:

  • buff и cache в секции memory - это те самые показатели памяти, которые мы уже видели (в КБ);
  • bi и bo в секции IO - объёмы чтения и записи блочного устройства (в блоках в секунду).
  • Поскольку размер блока в Linux - 1 КБ, фактически это КБ/с.

В обычной, простаивающей системе эти значения должны оставаться относительно стабильными от строки к строке.

Теперь во втором терминале выполним команду dd, чтобы прочитать данные из случайного устройства и создать файл размером 500 МБ:

$ dd if=/dev/urandom of=/tmp/file bs=1M count=500

После этого вернитесь к первому терминалу и посмотрите, как изменились Buffer и Cache:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7499460   1344 230484    0    0     0     0   29  145  0  0 100  0  0
1  0      0 7338088   1752 390512    0    0   488     0   39  558  0 47 53  0  0
1  0      0 7158872   1752 568800    0    0     0     4   30  376  1 50 49  0  0
1  0      0 6980308   1752 747860    0    0     0     0   24  360  0 50 50  0  0
0  0      0 6977448   1752 752072    0    0     0     0   29  138  0  0 100  0  0
0  0      0 6977440   1760 752080    0    0     0   152   42  212  0  1 99  1  0
...
0  1      0 6977216   1768 752104    0    0     4 122880   33  234  0  1 51 49  0
0  1      0 6977440   1768 752108    0    0     0 10240   38  196  0  0 50 50  0

Теперь видно, что во время выполнения dd значение cache быстро растёт, тогда как buff меняется незначительно.

Также можно заметить рост bo - это отражает запись данных на блочное устройство.

Этот сценарий даёт нам первый практический намёк на то, как именно используются Buffer и Cache при записи файла.

Если внимательно посмотреть на вывод vmstat, становится видно: во время выполнения dd значение cache непрерывно растёт, тогда как buff почти не меняется.

Если копнуть глубже в I/O-метрики, можно заметить следующее:

  • Когда cache только начинает расти, активность блочного устройства минимальна. Например, bi один раз показывает 488 КБ/с, а bo — всего 4 КБ.
  • Со временем появляются заметные записи на устройство - например, bo подскакивает до 122 880 КБ.
  • После завершения dd значение cache перестаёт увеличиваться, но запись на устройство продолжается ещё некоторое время. В итоге суммарный объём записанных данных совпадает с теми 500 МБ, которые создала команда dd.

Если сопоставить это с определением Cache, которое мы только что разобрали, возникает закономерный вопрос. В документации говорится, что Cache - это кэш страниц для чтения файлов. Но здесь он явно участвует и в записи файлов. Почему так?

Пока отложим этот вопрос и рассмотрим ещё один пример записи на диск. После того как разберём оба случая, вернёмся к анализу.

Но прежде - важное предупреждение.

Команда из следующего примера требует очень специфической конфигурации. В системе должно быть несколько дисков, и раздел /dev/sdb1 не должен использоваться. Если у вас только один диск, не запускайте эту команду - вы повредите раздел.

Если ваша система подходит под требования, выполните во втором терминале следующие команды. После очистки кэша запишем 2 ГБ случайных данных напрямую в раздел /dev/sdb1:

# First, clear the cache
$ echo 3 > /proc/sys/vm/drop_caches

# Then run the dd command to write 2GB of data to the disk partition /dev/sdb1
$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048

Теперь вернитесь в первый терминал и понаблюдайте за изменениями памяти и I/O:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
1  0      0 7584780 153592  97436    0    0   684     0   31  423  1 48 50  2  0
1  0      0 7418580 315384 101668    0    0     0     0   32  144  0 50 50  0  0
1  0      0 7253664 475844 106208    0    0     0     0   20  137  0 50 50  0  0
1  0      0 7093352 631800 110520    0    0     0     0   23  223  0 50 50  0  0
1  1      0 6930056 790520 114980    0    0     0 12804   23  168  0 50 42  9  0
1  0      0 6757204 949240 119396    0    0     0 183804   24  191  0 53 26 21  0
1  1      0 6591516 1107960 123840    0    0     0 77316   22  232  0 52 16 33  0

Из этих данных видно, что хотя запись данных - это в целом типовая операция, запись в файл и запись напрямую в диск ведут себя по-разному.

При записи в диск (когда bo больше 0) растут и buff, и cache, но при этом buff увеличивается значительно быстрее.

Это говорит о том, что при записи напрямую в блочное устройство активно используется Buffer - что полностью соответствует определению из документации.

Если сравнить оба сценария, получается следующее:

  • при записи в файл данные попадают в Cache;
  • при записи напрямую на диск активно используется Buffer.

И возвращаясь к нашему предыдущему вопросу: хотя в документации сказано, что Cache предназначен для кэширования данных, прочитанных из файлов, на практике Cache также используется и при записи в файл.

Сценарий 2: чтение с диска и из файла

С записью мы разобрались, теперь посмотрим, что происходит при чтении.

Вернитесь во второй терминал и выполните следующие команды. Сначала очистим кэш, затем прочитаем данные из файла /tmp/file и отправим их в null-устройство:

# First, clear the cache
$ echo 3 > /proc/sys/vm/drop_caches

# Run the dd command to read file data
$ dd if=/tmp/file of=/dev/null

После этого перейдите в первый терминал и понаблюдайте за изменениями памяти и I/O:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 7724164   2380 110844    0    0 16576     0   62  360  2  2 76 21  0
 0  1      0 7691544   2380 143472    0    0 32640     0   46  439  1  3 50 46  0
 0  1      0 7658736   2380 176204    0    0 32640     0   54  407  1  4 50 46  0
 0  1      0 7626052   2380 208908    0    0 32640    40   44  422  2  2 50 46  0

Если внимательно посмотреть на вывод vmstat, видно следующее: при чтении файла (когда bi больше 0) значение buff остаётся практически неизменным, тогда как cache стабильно растёт.

Это полностью совпадает с тем определением, которое мы нашли ранее: Cache - это страничный кэш для чтения файлов.

А что насчёт чтения напрямую с диска? Проверим это во втором сценарии.

Снова перейдите во второй терминал и выполните:

# First, clear the cache
$ echo 3 > /proc/sys/vm/drop_caches

# Run the dd command to read disk data
$ dd if=/dev/sda1 of=/dev/null bs=1M count=1024

После этого вернитесь в первый терминал и посмотрите на изменения памяти и I/O:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7225880   2716 608184    0    0     0     0   48  159  0  0 100  0  0
0  1      0 7199420  28644 608228    0    0 25928     0   60  252  0  1 65 35  0
0  1      0 7167092  60900 608312    0    0 32256     0   54  269  0  1 50 49  0
0  1      0 7134416  93572 608376    0    0 32672     0   53  253  0  0 51 49  0
0  1      0 7101484 126320 608480    0    0 32748     0   80  414  0  1 50 49  0

Если внимательно посмотреть на vmstat, видно: во время чтения с диска (когда bi больше 0) растут и buff, и cache, но buff увеличивается значительно быстрее.

Это означает, что при чтении напрямую с блочного устройства данные кэшируются в Buffer.

Если сопоставить оба сценария, вывод получается таким:

  • при чтении файла данные попадают в Cache;
  • при чтении напрямую с диска данные кэшируются в Buffer.

Картина начинает складываться: файловые операции работают через page cache, а операции с блочными устройствами - через buffer cache.

К этому моменту вы, вероятно, уже заметили: несмотря на то что документация даёт объяснение Buffer и Cache, она не раскрывает всех деталей. Сегодня мы выяснили как минимум две вещи:

  • Buffer используется не только для «кэширования данных перед записью на диск», но и для «кэширования данных, прочитанных с диска».
  • Cache - это не только page cache для чтения файлов, но и кэш, который участвует при записи в файлы.

Если обобщить: Buffer кэширует данные блочных устройств (дисков), а Cache - данные файловой системы. И в обоих случаях речь идёт как о чтении, так и о записи.

Заключение

Сегодня мы подробно разобрали, что на самом деле означают Buffer и Cache в контексте производительности памяти.

Buffer и Cache используются для кэширования операций чтения и записи - соответственно для дисков и файловых систем.

С точки зрения записи они не только оптимизируют работу с диском и файлами, но и позволяют приложениям продолжать работу, не дожидаясь фактической записи данных на диск.

С точки зрения чтения они ускоряют доступ к часто используемым данным и снижают нагрузку на I/O.

Но не менее важен и сам процесс исследования. При диагностике проблем производительности приходится иметь дело с огромным количеством метрик по разным ресурсам. Запомнить точный смысл каждой практически невозможно. Поэтому умение быстро и точно обращаться к документации - критически важно.

Стоит выработать привычку читать документацию и разбираться в деталях метрик, которые вы видите. Кроме того, файловая система /proc - крайне полезный инструмент. Она отражает внутреннее состояние системы и служит источником данных для множества performance-утилит. А значит, это отличный отправной пункт при анализе проблем производительности.

Report Page