BoltDB
BoltDB — это встраиваемая файловая база данных для языка программирования Go, реализующая структуру данных «B+‑дерево» и обеспечивающая хранение пар «ключ — значение» с транзакционной поддержкой и изоляцией по протоколу ACID. BoltDB не использует клиент-серверную архитектуру и не требует отдельного процесса СУБД: данные хранятся в одном бинарном файле, с которым приложение взаимодействует напрямую через функции Go‑библиотеки.
История
Проект BoltDB был создан Беном Джонсоном (Ben Johnson) в 2013 году как ответ на потребность в простой, надёжной и производительной встраиваемой БД для Go. Изначально разработчик ориентировался на опыт использования Berkeley DB и LMDB, но хотел получить решение, полностью написанное на Go, без привязок к C‑библиотекам. Первая стабильная версия (v1.0) вышла в 2014 году.
В 2017 году разработка BoltDB была официально остановлена: автор объявил, что больше не поддерживает проект. Однако код остался доступным на GitHub под лицензией MIT, и сообщество продолжило использовать библиотеку. На основе BoltDB был создан форк Bbolt, который в настоящее время поддерживается компанией CoreOS (подразделение Red Hat) и используется в таких проектах, как etcd и Kubernetes. Bbolt сохранил API и внутреннюю архитектуру BoltDB, добавив исправления ошибок и оптимизации.
Архитектура и принципы работы
BoltDB хранит все данные в одном файле на диске. Внутренняя организация основана на B+‑дереве — сбалансированном дереве поиска, в котором все ключи хранятся в листьях, а внутренние узлы служат для навигации. Это обеспечивает логарифмическую сложность операций поиска, вставки и удаления.
Страницы и узлы
Файл BoltDB делится на страницы фиксированного размера (по умолчанию 4096 байт). Каждая страница относится к одному из типов:
- мета-страницы — содержат служебную информацию о состоянии базы (номер транзакции, корень дерева, размер файла);
- узлы (nodes) — внутренние или листовые узлы B+‑дерева;
- ветки (branches) — страницы, содержащие указатели на дочерние узлы;
- листья (leaves) — страницы, хранящие непосредственно пары «ключ — значение».
При вставке нового элемента BoltDB может выполнять разделение страницы (split), если она переполнена, или слияние (merge) при удалении, чтобы поддерживать сбалансированность дерева.
Транзакции
BoltDB поддерживает два типа транзакций:
- только чтение (ReadOnly) — несколько таких транзакций могут выполняться параллельно, так как они не модифицируют данные;
- чтение-запись (ReadWrite) — только одна транзакция этого типа может быть активна в любой момент времени (последовательный доступ).
Все транзакции удовлетворяют требованиям ACID:
- Атомарность — изменения либо применяются полностью, либо откатываются.
- Согласованность — база данных всегда остаётся в целостном состоянии.
- Изоляция — транзакции не видят незавершённых изменений других транзакций.
- Долговечность — после успешного завершения транзакции данные гарантированно записаны на диск.
Курсоры и итерация
Для навигации по данным BoltDB предоставляет курсоры (Cursor), которые позволяют последовательно обходить все пары «ключ — значение» в отсортированном порядке. Курсор можно перемещать в начало, конец, к определённому ключу, а также выполнять поиск первого ключа, большего или равного заданному.
Классификация и сравнение
BoltDB относится к классу встраиваемых (embedded) key-value хранилищ. В отличие от реляционных СУБД (PostgreSQL, MySQL) или документоориентированных БД (MongoDB), BoltDB не поддерживает SQL, индексы на произвольные поля, полнотекстовый поиск или репликацию. Его основное преимущество — минимализм и предсказуемая производительность для сценариев, где требуется быстрый доступ к данным по ключу.
Сравнение с другими популярными встраиваемыми БД для Go:
| Свойство | BoltDB / Bbolt | Badger | LevelDB (Go‑порт) |
|---|---|---|---|
| Тип дерева | B+‑дерево | LSM‑дерево | LSM‑дерево |
| Транзакции ACID | Да (одна пишущая) | Да (одна пишущая) | Нет (только пакетная запись) |
| Производительность чтения | Высокая (O(log n)) | Высокая (O(log n)) | Высокая (O(log n)) |
| Производительность записи | Средняя (из-за синхронной записи) | Высокая (асинхронная запись) | Высокая |
| Размер базы данных | Ограничен размером файла | Ограничен размером файла | Ограничен размером файла |
| Поддержка | Bbolt (активен) | Активен | Активен (dgraph-io/badger) |
Применение
BoltDB (и его форк Bbolt) широко используется в инфраструктурных проектах, где требуется надёжное локальное хранение небольших объёмов данных (обычно до нескольких гигабайт). Основные области применения:
- Распределённые системы: etcd (хранилище конфигураций для Kubernetes) использует Bbolt для хранения ключей и значений.
- Мониторинг и телеметрия: Prometheus может использовать BoltDB для хранения метрик (в некоторых конфигурациях).
- Локальные кэши и сессии: веб-приложения на Go часто применяют BoltDB для хранения сессий пользователей или кэша данных из внешних API.
- Прототипирование и малые проекты: благодаря простоте настройки (нет сервера, нет конфигурационных файлов) BoltDB популярен при разработке прототипов и инструментов командной строки.
Ограничения
- Однопоточная запись: только одна пишущая транзакция может быть активна, что ограничивает пропускную способность при высокой нагрузке на запись.
- Размер базы данных: хотя теоретически файл может быть очень большим, на практике производительность падает при размерах свыше 10–20 ГБ из-за фрагментации и времени на слияние страниц.
- Отсутствие встроенной репликации: BoltDB не поддерживает репликацию или шардирование — это локальное хранилище для одного узла.
- Нет поддержки SQL и сложных запросов: все операции выполняются только по первичному ключу.
Пример использования (Go)
```go package main
import ( "log" "github.com/boltdb/bolt" )
func main() { // Открываем или создаём файл базы данных db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close()
// Создаём корзину (bucket) и записываем значение err = db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte("MyBucket")) if err != nil { return err } return b.Put([]byte("key"), []byte("value")) }) if err != nil { log.Fatal(err) }
// Читаем значение err = db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("MyBucket")) if b == nil { return nil } v := b.Get([]byte("key")) log.Printf("The value is: %s\n", v) return nil }) if err != nil { log.Fatal(err) } } ```
Интересные факты
- Название «Bolt» происходит от английского «bolt» (засов, болт) — метафора надёжного запирания данных.
- BoltDB не использует WAL (Write-Ahead Log) — все изменения сразу записываются на диск в структуру B+‑дерева, что упрощает архитектуру, но снижает производительность записи.
- Проект etcd, который является основой для хранения состояния Kubernetes, перешёл с BoltDB на Bbolt в 2018 году после прекращения поддержки оригинального репозитория.
Источники
- Официальный репозиторий BoltDB на GitHub (boltdb/bolt) — документация и исходный код.
- Официальный репозиторий Bbolt (etcd-io/bbolt) — форк с поддержкой и исправлениями.
- Ben Johnson. «BoltDB: An Embedded Database for Go» — статья на блоге автора (2014).
- Документация etcd: раздел «Storage» — описание использования Bbolt.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →