Разбираемся с 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-утилит. А значит, это отличный отправной пункт при анализе проблем производительности.