# BuntDB: an ACID key/value store that lives in memory and indexes your JSON

> BuntDB is Josh Baker's, tidwall's, MIT-licensed embeddable in-memory key/value store in pure Go, persisting to an append-only file, ACID compliant, with locking for many readers and one writer, custom indexes over any data type, JSON field indexing, multi-value indexes and spatial indexing up to 20 dimensions for geospatial data. It favors speed over data size, and the whole database is one Go file with a handful of tidwall dependencies.

**tidwall/buntdb** — BuntDB is an embeddable, in-memory key/value database for Go with custom indexing and geospatial support

- Repository: https://github.com/tidwall/buntdb
- Stars: 4,872 · Forks: 314
- Language: Go
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/tidwall-buntdb

## Low-level, in-memory, in one file

BuntDB is a low-level, in-memory, key/value store in pure Go that persists to disk, is ACID compliant, and uses locking for multiple readers and a single writer, ideal for projects that need a dependable database and favor speed over data size. Each phrase is a boundary. Low-level means the API is close to the storage model rather than a query language. In-memory means reads and writes hit RAM, with the append-only file as the durability layer. Pure Go means no cgo, cross compiling for free, and the repository confirms the minimalism, buntdb.go and buntdb_test.go beside the go.mod, with dependencies limited to the author's own packages, tidwall's btree, gjson, match, rtred, grect and testing utilities. There are no GitHub releases, the master branch and the go get flow are the distribution, last pushed 2026-05-19.

## Open a file, or open :memory:

The primary object is a DB, opened or created with buntdb.Open on a file path, with the getting started example opening data.db and deferring the close. The documented alternative is the special path:

```go
buntdb.Open(":memory:") // Open a file that does not persist to disk.
```

which keeps everything in RAM with no persistence at all, the mode for caches and transient state where even the append-only file is unwanted. Installation is the standard go get of the package, and the API surface begins and largely ends with the DB, the transactions and the iteration functions, the simple embeddable API the feature list names. The two open modes cover the deployment spectrum, durable storage and pure cache, with identical code otherwise.

## Transactions are the only door

All reads and writes must be performed from inside a transaction, and the rules are stated as warnings. BuntDB can have one write transaction open at a time but many concurrent read transactions, each transaction maintaining a stable view, once begun, its data cannot be changed by others. Transactions run in functions exposing a Tx object, and inside one, all operations go through that object, you should never access the origin DB object while inside a transaction, doing so may have side effects such as blocking your application. Read-only transactions run through db.View and can run many concurrently, read-write transactions through db.Update, one at a time, closed as soon as done. A returned error rolls the transaction back, reverting all changes, and a successful write transaction persists to disk, the ACID story in two function names. The stable-view guarantee also gives iteration a property worth naming, a walk over keys or an index sees a consistent world for its entire duration even as writers commit in parallel, which removes the read-skew bugs that plague hand-locked caches.

## Set, get, and eleven ways to iterate

Setting a value means opening an Update transaction and calling tx.Set with a key, value and an options argument, and getting means a View transaction and tx.Get, with non-existent values returning ErrNotFound. All pairs are ordered by key, and iteration exposes the full family, Ascend over everything, then AscendGreaterOrEqual, AscendLessThan, AscendRange, AscendEqual, Descend, DescendLessOrEqual, DescendGreaterThan, DescendRange and DescendEqual, each a bounded or filtered walk over the ordered keys. The iteration functions take a callback returning a boolean, with true continuing and false stopping, so early exit is built into the shape rather than requiring a break against an iterator object. The key-ordered baseline is the foundation the custom index system then builds on.

## Custom indexes over one B-tree world

Initially all data lives in a single B-tree, each item one key and one value ordered by key, with the implementation living in the author's separate tidwall/btree package. Custom indexes add orderings beyond the key, each index its own B-tree with custom ordering, created with a name, a key pattern and a comparison function. The names example creates an index with db.CreateIndex using the * wildcard to accept all keys and IndexString, the built-in case-insensitive string ordering, then seven user names inserted through Update iterate through the names index in alphabetical order regardless of their insertion order. The pattern parameter filters, changing the wildcard to user:* means only prefixed keys enter the index, so one database can hold several logical datasets each with their own indexes over the same storage.

## JSON fields, multi-value, i18n, and 20-dimensional space

The index system extends in four directions. JSON indexes index fields inside JSON documents, using the gjson dependency to pull values out of stored JSON strings, so a document store pattern works over the key/value base. Multi-value indexes behave like a SQL multi-column index, ordering on several extracted values at once. The optional collate package adds i18n collation, language-aware ordering beyond byte comparison. And spatial indexing supports up to 20 dimensions, useful for geospatial data, backed by the rtred and grect dependencies, the rectangle types and R-tree variant that make bounding-box and nearest-neighbor queries natural. Together with the built-in IndexInt, IndexUint and IndexFloat beside IndexString, the index layer turns a flat key/value store into something queryable along any dimension the data carries.

## Expiration, and the speed-over-size trade

The feature list closes with data expiration, the option to evict old items with a TTL, and the append-only file format named as durable persistence, the two operational features a cache-with-durability needs. The positioning sentence, favoring speed over data size, is the honest scope statement, everything lives in memory, so the dataset must fit RAM, and the append-only file grows with write volume until compaction, the trade a disk-first embedded store like the BoltDB family makes differently. The MIT license, the single-author dependency graph, and the README's own performance section complete the picture of a component library rather than a product, the kind of dependency a Go developer pulls in for one job and forgets it exists, which is the highest compliment an embeddable database earns. The go.mod's floor of Go 1.18 keeps the dependency buildable on years-old toolchains, matching the embed-and-forget deployment style the library targets.

## Conclusion

Use BuntDB when a Go application needs a dependable embedded store where read and write speed outranks dataset size, configuration, caches, small indexes, geospatial lookups, with persistence and ACID transactions included rather than bolted on. Choose a disk-first database or a client-server store when data exceeds memory or multiple processes must share it, since everything lives inside one process. Before adopting, remember all operations must run inside View or Update transactions, never touch the DB object from inside a transaction, and pick the index types deliberately, the built-in string, int, uint and float comparators or your own function for anything else.

## FAQ

### What is BuntDB?

BuntDB is an embeddable, in-memory key/value database for Go with custom indexing and geospatial support, MIT licensed and written in pure Go by tidwall. It persists to an append-only file, is ACID compliant, uses locking for multiple readers and a single writer, and indexes JSON fields, multiple values and up to 20 spatial dimensions.

### How do you use BuntDB in a Go project?

Install with go get github.com/tidwall/buntdb, open a database with buntdb.Open on a file path or Open with :memory: for a non-persistent store, then perform all reads through db.View and writes through db.Update transactions, using tx.Set, tx.Get and the Ascend and Descend iteration family. Custom indexes are created with db.CreateIndex with a key pattern and an index function.

### When should you choose BuntDB over a disk-based database?

Choose BuntDB when speed matters more than data size and the dataset fits in memory, since it is in-memory first with persistence through an append-only file. For datasets larger than RAM, multi-process access or disk-first durability semantics, an embedded disk-based or client-server database is the better fit.

## Sources

- [Issues](https://github.com/tidwall/buntdb/issues)
- [License: MIT](https://github.com/tidwall/buntdb/blob/master/LICENSE)
- [README](https://github.com/tidwall/buntdb/blob/master/README.md)
- [tidwall/buntdb on GitHub](https://github.com/tidwall/buntdb)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tidwall-buntdb
