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-хелперы и строки с префиксом длины; политики fsync always / 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, ...).

Участие в разработке

  1. Сделайте форк (https://gitlab.com/funcnode_crystal/funcnode-storage/-/forks/new)
  2. Создайте ветку под фичу (git checkout -b my-new-feature)
  3. Закоммитьте изменения (git commit -am 'Add some feature')
  4. Запушьте ветку (git push origin my-new-feature)
  5. Создайте Merge Request

Авторы

  • Василий Бутер — автор и мейнтейнер
Repository

funcnode-storage

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 0
  • about 8 hours ago
  • August 1, 2026
License

MIT License

Links
Synced at

Mon, 24 Aug 2026 00:56:25 GMT

Languages