Память и файловые системы — Примеры
Пример 1 — Запись, вернувшаяся прежде, чем диск её получил
Программа записывает отчёт и затем сообщает об успехе; ниже — один и тот же код со сбросом на диск и без него, прерванные потерей питания. Скажите, что на самом деле сделала запись длиной в микросекунды и какая строка создаёт разницу.
запуск 1: запись report.json, печать "saved", потеря питания, перезагрузка
после перезагрузки: report.json отсутствует
запуск 2: запись report.json, сброс на устройство, печать "saved", потеря питания, перезагрузка
после перезагрузки: report.json на месте целиком
Решение
write(fd, data) # попадает в кэш страниц; возвращается за микросекунды
fsync(fd) # не возвращается, пока устройство не получит байты
# в запуске 1 сброса не было: байты всё ещё жили в памяти
Запись возвращается, как только её данные оказываются в кэше страниц, что делает записи быстрыми, но превращает слово «сохранено» в утверждение о памяти, а не о диске; ждёт устройство только сброс. Поставить сброс перед сообщением об успехе — вот что делает утверждение правдивым, и именно этот шаг выполняет база данных перед сообщением о фиксации. Ловушка в том, что на исправной машине оба запуска выглядят одинаково — разница проявляется только тогда, когда кэш теряется.
Пример 2 — Быстро во второй раз и процесс, который продолжает расти
Замеры ниже сделаны на одном и том же файле в 400 МБ, прочитанном дважды, а ещё там есть парсер, который читает растущий файл в список; скажите, какие замеры проверяют диск, а какие кэш, и что происходит с парсером.
чтение 1: 1.9 с
чтение 2 сразу после: 0.2 с
чтение 3 после очистки кэшей: 1.8 с
парсер: файл 100 МБ -> процесс 180 МБ
файл 400 МБ -> процесс 700 МБ
файл 800 МБ -> процесс 1.4 ГБ, затем убит
Решение
чтение 2 быстрое потому, что чтение 1 наполнило кэш страниц, а не потому,
что файл стал дешевле — чтение 3 снова показывает холодную стоимость.
резидентный набор парсера следует за размером файла, поэтому, как только
рабочий набор перестаёт помещаться, ядро постоянно вытесняет и
перечитывает страницы.
Чтения берутся из кэша страниц, как только файл попадёт в память, поэтому второе чтение измеряет память, а не устройство, а очистка кэшей возвращает холодное число. Парсер читает весь файл в один список, поэтому его резидентный набор растёт вместе со входом; за точкой, где горячие страницы перестают помещаться в физическую память, каждый отказ вызывает очередное вытеснение, и машина перемещает страницы вместо разбора. Ловушка в том, что оба набора чисел выглядят фактами о файле, тогда как на самом деле это факты о слоях между вашим кодом и устройством.