Почему повторное чтение файла быстрее первого?
- Что такое page cache?
- Что делает fsync и зачем он нужен?
- Почему после записи в файл данные могут пропасть при сбое питания?
ОС кэширует содержимое файлов в свободной памяти — page cache. Повторное чтение берётся из памяти, а запись сначала попадает в кэш и на диск уходит позже.
- write() вернул управление — данные ещё не на диске
- fsync() ждёт, пока данные физически запишутся; без него при сбое питания их можно потерять
- «Свободной» памяти на сервере мало — это нормально, она занята кэшем
А ещё Базы данных делают fsync журнала на каждый коммит, отсюда цена записи
Подробно
Диск на порядки медленнее памяти, поэтому ядро держит прочитанные страницы файлов в оперативной памяти — это page cache. Под него идёт вся память, не занятая процессами; когда процессам нужно больше, кэш ужимается. Поэтому в free -h на долго работающем сервере колонка free маленькая, а available большая — паниковать не нужно.
Чтение. Первый read идёт на диск и заодно заполняет кэш; ядро ещё и читает наперёд (readahead), если доступ последовательный. Повторное чтение того же файла — копирование из памяти, на порядки быстрее. Отсюда эффект «холодного» и «прогретого» сервера после перезапуска.
Запись. write копирует данные в кэш, помечает страницы «грязными» и сразу возвращает управление. Сбросом на диск занимается ядро в фоне — через несколько секунд или при нехватке памяти. Это быстро, но если пропадёт питание или упадёт ядро, грязные страницы потеряются, хотя программа думала, что записала.
fsync. Вызов fsync(fd) ждёт, пока данные и метаданные файла дойдут до устройства. Именно его делают базы данных при коммите: транзакция считается подтверждённой, только когда запись в журнале (WAL) пережила бы сбой. fsync стоит от долей миллисекунды на SSD с конденсаторами до десятков миллисекунд на HDD, поэтому базы группируют коммиты (group commit), а настройки вроде synchronous_commit = off в PostgreSQL или acks/flush в брокерах — сознательный обмен надёжности на скорость.
Обход кэша. Базы, которые кэшируют данные сами (InnoDB, Oracle), часто открывают файлы с O_DIRECT, чтобы не держать два кэша. PostgreSQL, наоборот, опирается на page cache, поэтому shared_buffers ставят около четверти памяти. Kafka почти полностью полагается на page cache и последовательную запись.
Пример
$ free -h
total used free shared buff/cache available
Mem: 31Gi 6.2Gi 0.4Gi 0.1Gi 24Gi 24Gi
$ time cat big.log > /dev/null # первый раз — с диска
real 0m4.812s
$ time cat big.log > /dev/null # второй — из page cache
real 0m0.391sЧто хотят услышать
- Page cache: кэш файлов в свободной памяти, readahead.
- Запись отложенная, write не гарантирует сохранность.
- fsync и его цена, связь с коммитом в базах.
- «Свободной памяти мало» на сервере — норма.
Частые ошибки
- Считать, что после
writeили закрытия файла данные уже на диске. - Добавлять памяти серверу из-за маленького
free, глядя не на ту колонку.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Диск сам запоминает последние прочитанные блоки, а после write() данные уже гарантированно на диске, поэтому fsync не нужен
- ✗Мало свободной памяти на сервере — всегда признак утечки
- ✓ОС кэширует файлы в свободной памяти; повторное чтение берётся из памяти, а надёжная запись требует fsync
- ✗Page cache работает только для записи
Что спросят дальше
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.