bitcask-dataset
A disk-backed blob store that's a good fit if:
- you mostly do random-access reads, e.g. ML training data,
- values are biggish blobs (KiB to MiB) and keys are small,
- you add data in batches and never update or delete.
Each extend writes new shard files. Opening a dataset mmaps the shards as one logical
file, so lookups are fast and efficient.
Values are optionally LZ4-compressed (on by default).
Because old shards never change, keeping a copy elsewhere (say, S3) after an extend
means uploading only the new shards and the manifest, and never the whole dataset.
Not a good fit if you need deletes, overwrites, concurrent writers, or more keys than what your RAM can fit.
Install
pip install bitcask-dataset
Example
from bitcask_dataset import BitcaskDataset
ds = BitcaskDataset.create("my-dataset", keyed=True)
ds.extend([
(b"image/1", image_bytes),
(b"image/2", more_image_bytes),
])
ds = BitcaskDataset("my-dataset") # settings come from the manifest
value = ds[b"image/1"] # read-only memoryview of the decompressed bytes
# insertion order is available too
value = ds.at(0)
key = ds.key_at(0)
len(ds)
b"image/1" in ds
Keys are optional:
ds = BitcaskDataset.create("my-keyless-dataset")
ds.extend([image_bytes, more_image_bytes])
value = ds.at(0)
Keys and values can be any C-contiguous buffer (bytes, bytearray, a contiguous NumPy
array, ...).
API
BitcaskDataset.create(path, keyed=False, compression="lz4")
BitcaskDataset(path)
ds.extend(entries, max_shard_size=1 << 30)
ds[key]
ds.at(index)
ds.key_at(index)
len(ds)
key in ds
create needs a missing or empty directory. BitcaskDataset(path) opens an existing
dataset; keyed and compression are fixed at creation and read from the manifest.
In keyed mode, extend takes (key, value) pairs and duplicate keys are errors. In
keyless mode it takes values directly. Each extend is all-or-nothing and only
creates new shards. Records aren't split, so a record over max_shard_size gets a
shard to itself. Values are compressed on a thread pool, so don't modify a value's
buffer after yielding it to extend.
Keys and values come back as read-only memoryviews, which np.frombuffer and
torch.frombuffer can use without copying. Non-empty compressed values are views of
freshly decompressed bytes (value.obj). With compression=None, values are read
straight from the mmap with zero copies. Keys are always uncompressed. at and key_at
support negative indexes. Key lookup and key_at raise TypeError in keyless mode.
Format
my-dataset/
manifest.json
000000.bin
000000.idx
000001.bin
000001.idx
manifest.json holds the settings and how many shards are committed:
{"keyed": true, "compression": "lz4", "shards": 2}
Each .idx lists its shard's records as little-endian uint32 sizes: key_size, value_size pairs when keyed, one stored-value size per record when keyless. Keyed
shards are key || value || key || value ...; keyless shards only contain values.
There are no record headers or padding. Values are independent LZ4 blocks using
high-compression mode, with the original size in each block. Empty values stay empty.
Rewriting the manifest commits an extend, so shard files it doesn't list are
leftovers from an interrupted extend; they're ignored and overwritten later. When
copying a dataset, copy the manifest last.
Keyed datasets copy keys into a Python dict in each process that opens them (e.g. every
DataLoader worker). Each key and stored value is limited to 2**32 - 1 bytes; with LZ4,
uncompressed values are also limited to about 2 GiB, the LZ4 block limit.
Missing stuff
No delete, overwrite, compaction, or concurrent writers. Also not actually an implementation of the Bitcask paper, though per-shard indexes are close to its hint files.
Dev
uv sync
make format
make lint
make test
Metadata
Release files for bitcask-dataset 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bitcask_dataset-0.2.0.tar.gz | 7.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bitcask_dataset-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.5 kB
Release files / bitcask_dataset-0.2.0.tar.gz
| Download URL | bitcask_dataset-0.2.0.tar.gz |
|---|---|
| Size | 7.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c34051439f51fe38e491e442c7dfe90fd0be4559e9045e752e8fbdccc3a1b9a0
|
|
BLAKE2b-256 checksum How to use checksums |
f3e7006b790796f746e363f9c9c310ab35e08cabf6550fc8c39c04366d374455
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / bitcask_dataset-0.2.0-py3-none-any.whl
| Download URL | bitcask_dataset-0.2.0-py3-none-any.whl |
|---|---|
| Size | 8.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
996ce6407df3a4dafa8c3e13cb5e4d58699461fd3406799e5923801081f3cf75
|
|
BLAKE2b-256 checksum How to use checksums |
79b1b0722c60b0268f572069e13586507c0c3473974aae66531192188a648dcc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|