← На главную

Хранилище на SSD без fsync работает втрое быстрее

07.05.2026 09:25 · hackernews

Команда разработчиков создала одноузловую хранилище для ключ-значений, которое принципиально не вызывает fsync при операциях записи или удаления. Конструкция базируется на предвыделении файлов фиксированного размера, предзаполнении нулями, использовании O_DIRECT и журнале, где коммиты выровнены по атомарным блокам записи SSD. Это решение работает не против самой идеи fsync, а внутри узких границ: хранилище использует только SSD, управляет выделением и восстановления самостоятельно, а контракт на долговечность уже ограничен форматом POSIX. На тестовой площадке AWS i8g.2xlarge с локальным NVMe новая реализация достигла производительности 190,985 объектов/сек против 116,041 у комбинации ext4 с O_DIRECT и fsync. Задержки упали в среднем в 3.1 раза, а максимальные задержки в 99-ти процентилях сократились почти вдвое.

Проблема стандартных систем в том, что каждая операция записи превращается в сложную транзакцию файловой системы: обновление данных, индекса, записи в каталоге и фиксация журнала требуют множественных вызовов fsync. Особые системы, вроде etcd, синхронизируют каждый шаг протокола Raft, а Postgres оптимизирует это через групповые коммиты. Kafka вообще жертвует безопасностью единичного узла ради скорости, полагаясь на репликацию. Разработчики решили перенести границу долговечности из файловой системы в собственный движок. Это стало возможно благодаря жестким ограничениям: только SSD, значения от сотен байт до сотен КБ и простой API. Если бы добавили поддержку HDD или полноту POSIX, пришлось бы всё переписывать.

Архитектура движка состоит из индекса, журнала и области данных. Индекс, написанный на базе Fractal ART, хранится преимущественно в памяти и лишь фиксирует адреса значений. Журнал записывает изменения индекса и карту пространства, служа точкой отказа при перезапуске. Область данных хранит сами значения под управлением стратегии движка. Оба журнала и данных лежат на файловой системе, но обходят её проблемы через предвыделение fallocate и прямую запись O_DIRECT. Предвыделение фиксированного размера исключает необходимость обновления индекса при изменении размера файла, что убирает мета-данные из критического пути. Запись через O_DIRECT убирает промежуточное состояние «грязных» страниц в кэдре ядра.

Журнал критически важен и гарантирует целостность без fsync только при условии поддержки устройством 4-килобайтных атомарных записей. Это гарантирует, что коммит не прервется при пропадании питания. Коммиты пишутся с выровненными по 4 КБ блоками, поэтому потеря питания не может затронуть середину записи. Также журнал умеет собирать множество запросов в одну операцию ввода-вывода, снижая задержки. При перезапуске движок загружает последнюю контрольную точку и проигрывает журнал, проверяя контрольные суммы. Если питание ушло между записью данных и фиксацией в журнале, при запуске система признает этот регион незавершенным, и данные остаются недоступны.

Удаление данных работает ещё эффективнее. Запись в журнал описывает изменение, индекс удаляет ключ, а карта пространства помечает область для пересмотра. Физические данные не трогается, место моментально освобождается для нового объекта. В обычной файловой системе удаление меняет много метаданных: запись в директории, inode и журнал. Это создает конфликты с одновременными записями, фрагментирует свободное место и повышает задержки. Новый движок разделяет эти потоки в памяти. Предзаполнение нулями после fallocate также необходимо: только так все разделы становятся «записанными», и первые данные не вызовут обновления мета-данных.

Тесты на случайной записи 4 КБ показали результат в 2.4 раза лучше, чем ext4 с буферизацией и fsync, и в 1.6 раза быстрее варианта с O_DIRECT. Сложности возникают при использовании HDD, где важны скорость позиционирования головы и компиляция данных, а также на виртуальных дисках с небезопасными кэшами. Текущий API подходит только для конкретных задач продукта, не поддерживая транзакции, TTL или вторичные индексы. Для локальных серверов следующим шагом может стать отказ от файловой системы вообще и прямой контроль над блочным слоем, что позволит еще лучше управлять паттернами записи и избежать лишней работы NAND. Убрать fsync получилось за счет переноса ответственности на движок и жесткий контракт на использование оборудования, что оправдано высокой скоростью работы в текущем сценарии.

Читать оригинал →