funcnode-storage
funcnode-storage
Файловые примитивы для долгоживущих сервисов на Crystal.
Библиотека семейства funcnode: небольшие сфокусированные классы для работы с файлами, рассчитанные на процесс, который живёт неделями и обязан пережить внезапное убийство. Примитивов четыре: журнал предзаписи (WAL), файл произвольного доступа (FnFile), файловый справочник строк (Dictionary) и индекс «ключ → значение» (BPlusTreeFile).
Возможности
Wal— журнал только на дозапись:appendвозвращает LSN, чтение вперёд и назад, произвольный доступ по LSN и возобновление с чекпоинта.- Устойчивость к обрыву. Каждая запись самопроверяемая (CRC32 + парные длины); недописанный при падении хвост срезается при следующем открытии. Битую запись невозможно принять за целую.
- Групповой коммит. Конкурентные
appendиз разных файберов схлопываются в одинwriteи одинfsync, не ослабляя долговечность: вызов возвращается только после того, как его запись легла на диск. - Удаление с обоих концов —
truncate_head,truncate_tail,truncate_head_to(lsn),truncate_head_while, плюсcompact. - Ротация — настройка, а не отдельный класс. Сегменты по периодам (час / день / неделя / месяц) и/или по объёму, хранение по числу сегментов, суммарному размеру и возрасту. Включается и выключается правкой конфига: код приложения не меняется, файлы никуда не переносятся, сохранённые LSN остаются валидными.
FnFile— файл произвольного доступа: атомарныйappend, позиционные чтения и записи, типизированные big-endian-хелперы и строки с префиксом длины; политики fsyncalways/interval/never. Изменяющие вызовы в одной очереди, чтения не ждут писателя; байтовый формат совместим сjava.nio.ByteBuffer.Dictionary— справочник строк со стабильными id (Int64): строка интернируется один раз, id — номер строки в обычном текстовом файле; новая запись fsync-ается до выдачи id, оборванный хвост чинится при открытии.BPlusTreeFile— индекс «ключ → значение» с интерфейсом усечённойMap:get,put,has_key?,delete, обходы в порядке ключа, диапазонные срезы и пакетные транзакции. Внутри — copy-on-write B+дерево: страница, однажды записанная, не перезаписывается, а коммит меняет одну из двух meta-страниц. Поэтому убийство процесса не может испортить файл, а потеря питания стоит транзакций с последнегоfsyncи ничего сверх того. Ключ и значение типизируются сериализаторами (кодирование ключа сохраняет порядок), крупные значения уезжают в цепочку страниц, естьverify,compact!иrepair.- Потокобезопасность в многопоточном рантайме, межпроцессная блокировка файла, Windows и Linux на равных.
- Только stdlib, без зависимостей.
Установка
Добавьте зависимость в shard.yml и выполните shards install:
dependencies:
funcnode-storage:
gitlab: funcnode_crystal/funcnode-storage
Быстрый старт
require "funcnode-storage"
alias Storage = Funcnode::Storage
Storage::Wal.open(Storage::Wal::Config.new(path: "data/app.wal")) do |wal|
lsn = wal.append("операция 1".to_slice)
wal.append("операция 2".to_slice)
wal.each { |record| puts String.new(record) }
wal.read_at(lsn) # произвольный доступ по LSN
wal.truncate_head(1) # снять одну запись с головы
end
Ротация — то же самое плюс несколько полей конфига:
config = Storage::Wal::Config.new(
path: "data/app.wal",
period: Storage::Wal::Period::Daily,
max_segments: 14)
Конфигурация
# объектом — путь к журналу это поле конфига:
wal = Storage::Wal.new(
Storage::Wal::Config.new(
path: "data/app.wal",
sync_policy: Storage::Wal::SyncPolicy::Always,
max_record_bytes: 16 * 1024 * 1024,
exclusive: true
))
# или путём к YAML-файлу: нет файла — создастся шаблон с дефолтами и
# русскими комментариями, и первый запуск упадёт с ConfigError, чтобы
# оператор его увидел; битый файл — ConfigError тоже:
wal = Storage::Wal.new("./config/wal.yaml")
Подробности и YAML-ключи — в руководстве.
Документация
- Руководство — полный API, формат файла на диске, восстановление после сбоя, многопоточность и групповой коммит, ротация.
- Индекс BPlusTreeFile — отдельная страница: гарантии, сериализаторы ключа и значения, обходы, транзакции, политики fsync, обслуживание и формат файла.
- CHANGELOG.md — дельты между релизами; версии — это git-теги (
vX.Y.Z) на веткеmain.
Разработка
crystal spec # однопоточный прогон
crystal spec --no-debug # то же с урезанными backtrace
crystal run --release benchmarks/wal.cr
Библиотека предназначена для серверов под Ubuntu 24, а состоит целиком из файлового ввода-вывода — прогон в production-parity контейнере обязателен (нужен запущенный Docker):
docker run --rm -v "<путь-к-репо>:/src:ro" crystallang/crystal:1.21.0 bash /src/scripts/linux_spec.sh
Успех — финальная строка LINUX MATRIX GREEN (Ubuntu 24.04, ...).
Участие в разработке
- Сделайте форк (https://gitlab.com/funcnode_crystal/funcnode-storage/-/forks/new)
- Создайте ветку под фичу (
git checkout -b my-new-feature) - Закоммитьте изменения (
git commit -am 'Add some feature') - Запушьте ветку (
git push origin my-new-feature) - Создайте Merge Request
Авторы
- Василий Бутер — автор и мейнтейнер
funcnode-storage
- 0
- 0
- 0
- 0
- 0
- about 8 hours ago
- August 1, 2026
MIT License
Mon, 24 Aug 2026 00:56:25 GMT