LevelDB is a fast, embeddable key-value storage library written at Google. It provides an ordered mapping from arbitrary string (byte-array) keys to arbitrary string values, persisted to the local filesystem. You link it directly into your C++ or C application — there is no server, no daemon, and no network protocol.
This guide is written for developers who embed LevelDB in their own programs. It covers building and installing the library, every public operation with copy-paste-ready examples, performance tuning, the bundled leveldbutil command-line tool, troubleshooting, and a reference of every option and status code.
Note
LevelDB is the storage engine that inspired RocksDB and underpins many systems. It is intentionally small and focused: keys/values in, keys/values out, sorted by key. If you need SQL, relations, or a network server, LevelDB is a building block, not a finished product — see Limitations.
Table of Contents
- Quick Start
- Installation
- Core Concepts
- Everyday Usage
- Iteration
- Atomic Updates with WriteBatch
- Snapshots
- Advanced Usage
- Performance Tuning
- Using the C API
- The
leveldbutilCommand-Line Tool - Troubleshooting
- Frequently Asked Questions
- Reference
1. Quick Start
The fastest path from zero to a working key-value store. The following program opens (creating if needed) a database, writes a value, reads it back, and closes cleanly.
#include <cassert>
#include <iostream>
#include <string>
#include "leveldb/db.h"
int main() {
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
// Open (or create) the database stored in the directory /tmp/testdb.
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
assert(status.ok());
// Write a key/value pair.
status = db->Put(leveldb::WriteOptions(), "name", "leveldb");
assert(status.ok());
// Read it back.
std::string value;
status = db->Get(leveldb::ReadOptions(), "name", &value);
if (status.ok()) {
std::cout << "name = " << value << std::endl;
}
// Close the database by deleting the handle.
delete db;
return 0;
}
Compile and run (assuming LevelDB is installed — see Installation):
c++ -std=c++17 quickstart.cc -lleveldb -o quickstart
./quickstart
name = leveldb
Important
A LevelDB "database" is a directory on disk, not a single file. The name you pass to
DB::Open(/tmp/testdbabove) is the path to that directory. LevelDB owns every file inside it.
Tip
Always check the returned
leveldb::Status. Almost every operation that can fail returns one. The idiomif (!status.ok()) { ... }should appear throughout your code — see Core Concepts.
2. Installation
LevelDB is distributed as source and built with CMake. It has no required third-party dependencies; compression and CRC acceleration libraries are detected and linked automatically if present.
2.1 Requirements
| Requirement | Detail |
|---|---|
| C++ compiler | C++17 required (CMAKE_CXX_STANDARD 17) |
| CMake | 3.22 or newer (cmake_minimum_required(VERSION 3.22)) |
| Build threads | POSIX threads (Threads::Threads) — found automatically |
| Platforms | POSIX (Linux, macOS) and Windows are the tested, supported platforms |
Optional libraries, auto-detected at configure time, that improve LevelDB if installed:
| Library | Effect when present |
|---|---|
snappy |
Enables Snappy block compression (the default compression) |
zstd |
Enables Zstandard block compression (kZstdCompression) |
crc32c |
Hardware-accelerated CRC32C checksums |
tcmalloc |
Faster memory allocation |
Note
If
snappyis not installed, LevelDB still builds and runs — blocks are simply stored uncompressed. TheOptions::compressiondefault ofkSnappyCompressionbecomes a no-op at runtime when Snappy support is absent.
2.2 Getting the source
The repository uses git submodules (GoogleTest and Google Benchmark, used only for tests and benchmarks). Clone recursively:
git clone --recurse-submodules https://github.com/google/leveldb.git
cd leveldb
2.3 Building
Linux / macOS
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
Windows (Visual Studio)
mkdir build
cd build
cmake -G "Visual Studio 15" ..
For a 64-bit build, generate with the Win64 generator instead:
cmake -G "Visual Studio 15 Win64" ..
Then build from the command line or open leveldb.sln in Visual Studio:
devenv /build Debug leveldb.sln
A successful build produces the static library (libleveldb.a / leveldb.lib) plus the leveldbutil tool. Expected tail of output:
[100%] Built target leveldb
[100%] Built target leveldbutil
2.4 Build options
These CMake cache options control what gets built. Pass them as -D<OPTION>=<VALUE> to cmake.
| Option | Default | Purpose |
|---|---|---|
LEVELDB_BUILD_TESTS |
ON |
Build the unit-test suite |
LEVELDB_BUILD_BENCHMARKS |
ON |
Build the db_bench benchmark programs |
LEVELDB_INSTALL |
ON |
Generate install rules for the headers + library |
BUILD_SHARED_LIBS |
OFF |
Build a shared library instead of a static one |
Example: build a release shared library, no tests or benchmarks:
cmake -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DLEVELDB_BUILD_TESTS=OFF \
-DLEVELDB_BUILD_BENCHMARKS=OFF ..
cmake --build .
Tip
Disabling
LEVELDB_BUILD_TESTSandLEVELDB_BUILD_BENCHMARKSremoves the need for the GoogleTest and Benchmark submodules and noticeably speeds up the build when you only need the library itself.
2.5 Installing
With LEVELDB_INSTALL=ON (the default), install the headers, library, and CMake package config:
cmake --build . --target install
This installs:
- The library to your platform's library directory.
- The public headers to
<prefix>/include/leveldb/. - A CMake package (
leveldbConfig.cmake) so downstream projects canfind_package(leveldb).
2.6 Using LevelDB from your CMake project
Once installed, link against the imported target leveldb::leveldb:
find_package(leveldb REQUIRED)
add_executable(myapp main.cc)
target_link_libraries(myapp PRIVATE leveldb::leveldb)
Caution
Include only the public headers under
include/leveldb/. Headers elsewhere in the source tree (e.g.db/,table/,util/) are internal and may change without warning between releases.
3. Core Concepts
A handful of small types appear throughout the API. Understanding them up front makes every later example obvious.
3.1 DB — the database handle
leveldb::DB is an abstract class. You obtain a concrete instance from the static DB::Open factory and you destroy it with delete. A DB is safe for concurrent use by multiple threads with no external locking.
3.2 Slice — a pointer + length
leveldb::Slice is a lightweight, non-owning view over a byte array: just a pointer and a size. It is how LevelDB avoids copying potentially large keys and values. Because LevelDB keys and values are arbitrary byte arrays (they may contain '\0'), the API uses Slice rather than C strings.
A Slice is constructible from both std::string and C strings, and convertible back:
leveldb::Slice s1 = "hello"; // from a C string
std::string str("world");
leveldb::Slice s2 = str; // from a std::string
std::string back = s1.ToString(); // back to std::string
Warning
A
Slicedoes not own its data. The caller must keep the backing byte array alive for as long as theSliceis used. This is buggy:leveldb::Slice slice; if (cond) { std::string str = ComputeKey(); slice = str; // slice points into str } // str is destroyed here Use(slice); // slice now dangles — undefined behavior
3.3 Status — success or error
leveldb::Status is the universal return type for fallible operations. Check ok(), and classify errors with the Is* predicates.
leveldb::Status s = db->Get(leveldb::ReadOptions(), "missing", &value);
if (s.ok()) {
// found
} else if (s.IsNotFound()) {
// key does not exist
} else {
std::cerr << s.ToString() << std::endl; // some other error
}
The error categories are: IsNotFound(), IsCorruption(), IsIOError(), IsNotSupportedError(), and IsInvalidArgument(). See the Status reference.
3.4 Options, ReadOptions, WriteOptions
Behavior is configured with three plain structs:
Options— passed once toDB::Open; controls the whole database (cache, compression, comparator, etc.).ReadOptions— passed per read; controls checksum verification, caching, and snapshots.WriteOptions— passed per write; controls durability (thesyncflag).
All three have sensible defaults; you only set the fields you care about. Full tables are in the Reference.
4. Everyday Usage
The three basic operations are Put, Get, and Delete. All keys and values are Slices.
4.1 Writing a value — Put
leveldb::Status s = db->Put(leveldb::WriteOptions(), key, value);
Put inserts or overwrites the entry for key.
4.2 Reading a value — Get
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), key, &value);
if (s.ok()) {
// value now holds the stored bytes
} else if (s.IsNotFound()) {
// no such key
}
4.3 Deleting a value — Delete
leveldb::Status s = db->Delete(leveldb::WriteOptions(), key);
It is not an error to delete a key that does not exist; Delete still returns OK.
4.4 A common pattern: move a value between keys
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
if (s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value);
if (s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);
Warning
The code above is not atomic. If the process dies after the
Putofkey2but before theDeleteofkey1, the value is left under both keys. For atomicity, use aWriteBatch.
4.5 Closing the database
There is no Close() method. You close a database by deleting its handle:
delete db;
Important
Delete all iterators, snapshots, and other handles obtained from the
DBbefore you delete theDBitself.
5. Iteration
A LevelDB iterator yields key/value pairs in sorted key order. Get one with NewIterator, and always delete it when done.
5.1 Forward iteration over the whole database
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
std::cout << it->key().ToString() << ": " << it->value().ToString() << "\n";
}
assert(it->status().ok()); // check for errors found during the scan
delete it;
5.2 Iterating a key range [start, limit)
for (it->Seek(start);
it->Valid() && it->key().ToString() < limit;
it->Next()) {
// process it->key(), it->value()
}
5.3 Reverse iteration
for (it->SeekToLast(); it->Valid(); it->Prev()) {
// process entries from largest key to smallest
}
Note
Reverse iteration may be somewhat slower than forward iteration, by design.
Caution
The
Slices returned byit->key()andit->value()are only valid until the next mutation of the iterator. Copy them withToString()if you need to keep them.
The full iterator interface — Valid(), SeekToFirst(), SeekToLast(), Seek(), Next(), Prev(), key(), value(), status() — is in the Reference.
6. Atomic Updates with WriteBatch
A WriteBatch collects a sequence of Put and Delete operations and applies them to the database atomically — all or nothing.
#include "leveldb/write_batch.h"
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
if (s.ok()) {
leveldb::WriteBatch batch;
batch.Delete(key1);
batch.Put(key2, value);
s = db->Write(leveldb::WriteOptions(), &batch);
}
Operations within a batch are applied in order. In the example, Delete(key1) is ordered before Put(key2) so that even if key1 == key2, the value is preserved rather than dropped.
WriteBatch has two benefits:
- Atomicity — partial application can never be observed, even across a crash.
- Throughput — bundling many mutations into one batch is faster than issuing them individually, because the cost of the write (and an optional sync) is amortized.
leveldb::WriteBatch batch;
for (const auto& [k, v] : pending) {
batch.Put(k, v);
}
db->Write(leveldb::WriteOptions(), &batch);
Tip
To make a bulk update both atomic and durable, set
write_options.sync = trueon the singleWritecall. The sync cost is paid once for the whole batch.
7. Snapshots
A snapshot is a consistent, read-only view of the entire database at a point in time. Reads against a snapshot ignore any writes made after the snapshot was taken.
leveldb::ReadOptions options;
options.snapshot = db->GetSnapshot();
// ... apply some updates to db ...
leveldb::Iterator* iter = db->NewIterator(options);
// ... iter sees the DB exactly as it was when the snapshot was created ...
delete iter;
db->ReleaseSnapshot(options.snapshot);
If ReadOptions::snapshot is nullptr (the default), each read operates on an implicit snapshot of the state at the moment the read begins.
Important
Always call
DB::ReleaseSnapshotwhen you are finished with a snapshot. Holding a snapshot prevents LevelDB from discarding superseded data, so a long-lived snapshot increases disk usage.
8. Advanced Usage
8.1 Custom comparators
By default, keys are ordered lexicographically by byte value (BytewiseComparator). To impose a different order, subclass leveldb::Comparator and pass it via Options::comparator.
class TwoPartComparator : public leveldb::Comparator {
public:
// Sort by first number, break ties by second number.
int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
int a1, a2, b1, b2;
ParseKey(a, &a1, &a2);
ParseKey(b, &b1, &b2);
if (a1 != b1) return a1 < b1 ? -1 : +1;
if (a2 != b2) return a2 < b2 ? -1 : +1;
return 0;
}
const char* Name() const override { return "TwoPartComparator"; }
void FindShortestSeparator(std::string*, const leveldb::Slice&) const override {}
void FindShortSuccessor(std::string*) const override {}
};
TwoPartComparator cmp;
leveldb::Options options;
options.create_if_missing = true;
options.comparator = &cmp;
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
Caution
The comparator's
Name()is recorded when the database is created and verified on every open. Changing the name makes existing databases unopenable. Change the name only when the new ordering is genuinely incompatible with old data. To evolve a key format safely, keep the name and embed a version byte in your keys.
8.2 Bloom filters
A FilterPolicy reduces the number of disk reads per Get. The built-in Bloom filter is the common choice.
#include "leveldb/filter_policy.h"
leveldb::Options options;
options.filter_policy = leveldb::NewBloomFilterPolicy(10); // 10 bits per key
leveldb::DB* db;
leveldb::DB::Open(options, "/tmp/testdb", &db);
// ... use the database ...
delete db;
delete options.filter_policy; // delete AFTER the db is closed
NewBloomFilterPolicy(10) keeps ~10 bits per key in memory and cuts unnecessary disk reads for point lookups by roughly 100×. More bits per key = fewer disk reads, at the cost of more memory.
Tip
Set a filter policy if your working set does not fit in memory and you do many random reads. It is one of the highest-impact tuning knobs.
Warning
If you use a custom comparator that ignores parts of keys (e.g. trailing spaces), you must not use
NewBloomFilterPolicydirectly — supply a matching custom filter policy that ignores the same parts.
8.3 Custom environments (Env)
Every filesystem and OS interaction goes through a leveldb::Env. The default, Env::Default(), is suitable for normal use. Advanced clients can subclass Env (or EnvWrapper, which forwards all calls to another Env) to intercept I/O — for rate limiting, fault injection, or in-memory storage.
class SlowEnv : public leveldb::EnvWrapper {
public:
explicit SlowEnv(leveldb::Env* base) : leveldb::EnvWrapper(base) {}
// override only the methods you want to change
};
SlowEnv env(leveldb::Env::Default());
leveldb::Options options;
options.env = &env;
leveldb::DB::Open(options, "/tmp/testdb", &db);
Note
The library ships an in-memory
Envhelper (helpers/memenv) that backs a database entirely in RAM — useful for tests. It is not part of the stable public interface.
8.4 Estimating storage size
GetApproximateSizes returns the approximate on-disk bytes used by one or more key ranges.
leveldb::Range ranges[2];
ranges[0] = leveldb::Range("a", "c");
ranges[1] = leveldb::Range("x", "z");
uint64_t sizes[2];
db->GetApproximateSizes(ranges, 2, sizes);
// sizes[0] = approx bytes for [a, c); sizes[1] = approx bytes for [x, z)
Because the sizes reflect compressed file-system space, they will be smaller than the raw user-data size when compression is effective.
8.5 Querying database properties
GetProperty exposes internal statistics as human-readable strings.
std::string stats;
if (db->GetProperty("leveldb.stats", &stats)) {
std::cout << stats << std::endl;
}
| Property | Returns |
|---|---|
leveldb.num-files-at-level<N> |
Number of files at level <N> (e.g. leveldb.num-files-at-level0) |
leveldb.stats |
Multi-line table of per-level statistics |
leveldb.sstables |
Multi-line description of all SSTables in the DB |
leveldb.approximate-memory-usage |
Approximate bytes of memory in use by the DB |
GetProperty returns false for an unknown property name.
8.6 Manual compaction
LevelDB compacts in the background automatically. You rarely need to trigger it, but CompactRange forces compaction of a key range, discarding deleted/overwritten versions and reorganizing data for faster access.
db->CompactRange(nullptr, nullptr); // compact the entire database
begin == nullptr means "before all keys"; end == nullptr means "after all keys."
Note
Manual compaction is an advanced operation intended for callers who understand the storage layout. Most applications should rely on automatic background compaction.
8.7 Destroying and repairing a database
These free functions operate on a closed database directory:
#include "leveldb/db.h"
leveldb::Status s = leveldb::DestroyDB("/tmp/testdb", leveldb::Options());
leveldb::Status s = leveldb::RepairDB("/tmp/testdb", leveldb::Options());
DestroyDB deletes the database's contents — use with care. RepairDB attempts to recover as much data as possible from a database that will not open; some data may be lost, so only use it on a database you cannot otherwise open.
9. Performance Tuning
LevelDB's defaults are reasonable, but several Options fields meaningfully affect throughput and latency.
9.1 Synchronous vs. asynchronous writes
By default writes are asynchronous: Put/Write return after handing the data to the operating system, before it reaches persistent storage.
leveldb::WriteOptions write_options;
write_options.sync = true; // force the write all the way to disk
db->Put(write_options, key, value);
| Mode | Speed | Crash behavior |
|---|---|---|
sync = false (default) |
Often >1000× faster | A machine reboot/power loss may lose the last few updates. A process crash loses nothing. |
sync = true |
Much slower (one disk flush per write) | Data is durable once the call returns. |
Tip
Asynchronous writes are safe for many workloads. For bulk loads, use
sync = falseand restart the load from a checkpoint after a crash. Or batch many updates into oneWriteBatchand issue a single synchronousWriteto amortize the flush cost.
9.2 Block size
options.block_size = 4 * 1024; // default ~4 KB (uncompressed)
- Increase for workloads dominated by bulk scans.
- Consider decreasing for workloads dominated by point reads of small values (measure first).
- There's little benefit below ~1 KB or above a few MB. Larger blocks compress better.
9.3 Compression
Block compression is on by default (kSnappyCompression), which is fast and self-disables on incompressible data. Disable it only if benchmarks justify it:
options.compression = leveldb::kNoCompression;
Zstandard is also available where the zstd library is present:
options.compression = leveldb::kZstdCompression;
options.zstd_compression_level = 1; // supported range [-5, 22], default 1
9.4 Block cache
Provide an explicit LRU cache to keep frequently used uncompressed blocks in memory.
#include "leveldb/cache.h"
leveldb::Options options;
options.block_cache = leveldb::NewLRUCache(100 * 1048576); // 100 MB
leveldb::DB* db;
leveldb::DB::Open(options, name, &db);
// ... use the db ...
delete db;
delete options.block_cache; // delete AFTER the db
If block_cache is null, LevelDB creates an internal 8 MB cache automatically. Size the cache to your application-level data sizes (it holds uncompressed data).
Tip
When performing a one-off bulk read, set
ReadOptions::fill_cache = falseso the scan doesn't evict your hot working set from the cache:leveldb::ReadOptions ro; ro.fill_cache = false; leveldb::Iterator* it = db->NewIterator(ro);
9.5 Key layout
The unit of disk transfer and caching is a block, and adjacent keys (by sort order) usually land in the same block. Place keys accessed together near each other. For example, when building a file system on LevelDB, prefix metadata keys (/name) and block-data keys (0blockid) with different leading bytes so a metadata scan doesn't drag bulky file contents through the cache.
9.6 Other capacity knobs
| Option | Default | Tune when |
|---|---|---|
write_buffer_size |
4 MB | Larger speeds bulk loads but uses more memory and lengthens recovery |
max_open_files |
1000 | Increase for large working sets (budget ~1 open file per 2 MB) |
max_file_size |
2 MB | Increase if your filesystem prefers larger files |
9.7 Checksums and integrity
LevelDB stores checksums for all data. Two controls govern verification:
leveldb::ReadOptions ro;
ro.verify_checksums = true; // verify on every read (default: false)
leveldb::Options options;
options.paranoid_checks = true; // fail fast on detected corruption (default: false)
Caution
With
paranoid_checks = true, a single corrupted entry can make a large portion of the database — or the whole database — unreadable, because LevelDB stops at the first detected error. Leave it off if you prefer best-effort availability over fail-fast strictness.
10. Using the C API
LevelDB ships a complete C binding (leveldb/c.h) that exposes opaque struct pointers and functions. It's intended as a stable ABI for shared-library users and for foreign-function interfaces (e.g. JNI).
Conventions of the C API:
- Errors are reported through a trailing
char** errptr. On success*errptris left unchanged; on failure it is set to amalloc-ed, null-terminated message you must free. - There is no
Slicetype — pass pointer and length as separate arguments. - Booleans are
uint8_t(0= false, non-zero = true). - All pointer arguments must be non-NULL.
A minimal open / put / get / close sequence:
#include "leveldb/c.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char* err = NULL;
leveldb_options_t* options = leveldb_options_create();
leveldb_options_set_create_if_missing(options, 1);
leveldb_t* db = leveldb_open(options, "/tmp/c_testdb", &err);
if (err != NULL) { fprintf(stderr, "open: %s\n", err); leveldb_free(err); return 1; }
leveldb_writeoptions_t* woptions = leveldb_writeoptions_create();
leveldb_put(db, woptions, "foo", 3, "hello", 5, &err);
if (err != NULL) { fprintf(stderr, "put: %s\n", err); leveldb_free(err); return 1; }
leveldb_readoptions_t* roptions = leveldb_readoptions_create();
size_t vlen;
char* val = leveldb_get(db, roptions, "foo", 3, &vlen, &err);
if (err == NULL && val != NULL) {
printf("foo = %.*s\n", (int)vlen, val);
leveldb_free(val);
}
leveldb_writeoptions_destroy(woptions);
leveldb_readoptions_destroy(roptions);
leveldb_options_destroy(options);
leveldb_close(db);
return 0;
}
foo = hello
The C API mirrors the C++ one: write batches (leveldb_writebatch_*), iterators (leveldb_iter_*), snapshots (leveldb_create_snapshot), custom comparators (leveldb_comparator_create), filter policies (leveldb_filterpolicy_create_bloom), LRU caches (leveldb_cache_create_lru), and management calls (leveldb_destroy_db, leveldb_repair_db, leveldb_compact_range).
Important
Free every buffer the C API returns with
leveldb_free(not plainfree). On some platforms — notably Windows — the library and your program may use different allocators, andleveldb_freeroutes the deallocation correctly.
The C compression enum differs from C++: leveldb_no_compression = 0, leveldb_snappy_compression = 1.
Note
The C API does not support: option getters, custom comparators that implement key shortening, or custom iterator/DB/Env/Cache implementations written purely through the C bindings.
11. The leveldbutil Command-Line Tool
Building LevelDB also produces leveldbutil, a small command-line utility. It currently supports exactly one command.
11.1 Usage
leveldbutil command...
Usage: leveldbutil command...
dump files... -- dump contents of specified files
11.2 dump — print the contents of LevelDB files
leveldbutil dump /path/to/db/000005.ldb /path/to/db/MANIFEST-000002
dump prints, in text form, the contents of each named LevelDB storage file (SSTables, log files, and MANIFEST/descriptor files) to standard output. It accepts one or more file paths. If a file cannot be read or is not a recognized LevelDB file, an error message is printed to standard error and the tool exits with a non-zero status.
Note
dumpoperates on the individual files inside a database directory, not on the directory as a whole. Point it at specific.ldb,.log, orMANIFEST-*files. This is a diagnostic and inspection aid, not a backup or export format.
Important
dumpis the only subcommandleveldbutilprovides. There are no flags, noget/put/repairsubcommands, and no interactive mode. To manipulate data, use the library API.
12. Troubleshooting
Errors surface as leveldb::Status values (or *errptr strings in the C API). The patterns below cover the situations you are most likely to hit.
Error: database open fails with "exists" when error_if_exists is set
Cause: Options::error_if_exists = true and the target directory already contains a database.
Solution: Either remove the existing database (DestroyDB) or set error_if_exists = false to open the existing one.
Prevention: Only set error_if_exists = true when you specifically intend to fail on a pre-existing database.
Error: open returns NotFound / "does not exist"
Cause: The database directory does not exist and create_if_missing is false (the default).
Solution: Set options.create_if_missing = true before calling DB::Open.
Prevention: Decide explicitly whether your code should create databases on demand.
Error: Corruption: ... on open or read
Cause: On-disk data failed a checksum or structural check (bad sectors, a partially written file after a power loss, or a bug).
Solution: Try leveldb::RepairDB(name, options) to salvage what is recoverable, then reopen. Inspect suspect files with leveldbutil dump.
Prevention: Use WriteOptions::sync = true for writes that must survive power loss; consider verify_checksums = true for early detection.
Warning
RepairDBcan lose data. Take a copy of the database directory before repairing if the data is important.
Error: comparator name mismatch — open fails
Cause: The database was created with one comparator name and is being opened with a different one (Options::comparator->Name() differs from the recorded name).
Solution: Open with the same comparator (and name) used to create the database.
Prevention: Never rename a comparator unless you intend to abandon existing databases. Embed a version byte in keys to evolve formats without renaming. See Custom comparators.
Error: "too many open files" / IOError under heavy load
Cause: The working set requires more file descriptors than max_open_files (or the OS limit) allows.
Solution: Increase options.max_open_files (budget ~1 open file per 2 MB of working set) and/or raise the OS file-descriptor limit (ulimit -n).
Prevention: Size max_open_files to your dataset; modern LevelDB also caps level-0 files to mitigate this.
Error: second process cannot open the database (lock held)
Cause: A LevelDB database may be opened by only one process at a time; LevelDB holds an OS lock on the directory.
Solution: Ensure no other process has the database open. Within one process, share the single DB object across threads instead of opening twice.
Prevention: Architect for a single owning process; wrap your own server around LevelDB if multiple clients need access. See Limitations.
Error: recent writes lost after a power failure
Cause: Writes used the default sync = false, so the last updates were still in the OS buffer cache when power was lost.
Solution: Re-run from your last checkpoint.
Prevention: Use WriteOptions::sync = true for durability-critical writes, or checkpoint periodically with a synchronous marker write.
Symptom: high read latency right after many random writes
Cause: Many level-0 files accumulate during bursty writes; reads must merge across them until background compaction catches up.
Solution: Allow compaction to complete, or trigger CompactRange(nullptr, nullptr). Add a block cache and a Bloom filter policy.
Prevention: Provide a FilterPolicy and a generously sized block_cache; avoid sustained write bursts far above your disk's compaction throughput.
Symptom: dangling data / crash when using Slice
Cause: A Slice outlived the byte array it pointed to.
Solution: Copy with Slice::ToString() when you need to retain key/value bytes beyond the current scope or the next iterator mutation.
Prevention: Treat every Slice as a borrow, never as an owner. See the Slice warning.
13. Frequently Asked Questions
Q: Is LevelDB a database server I can connect to over the network? No. LevelDB is an in-process library with no built-in client-server support. If you need remote access, wrap your own server around the library.
Q: Can multiple processes share one database?
No. Only a single process (which may be multi-threaded) can have a given database open at a time; LevelDB enforces this with a file lock. Within that process, the DB object is safe to share across threads.
Q: Does LevelDB support SQL, tables, or secondary indexes? No. It is a sorted key-value store only — no relational model, no SQL, no indexes. You build higher-level structures on top of it.
Q: Are my keys and values restricted to text?
No. Keys and values are arbitrary byte arrays and may contain '\0'. That is why the API uses Slice (pointer + length) rather than C strings.
Q: How are keys ordered?
By default, lexicographically by byte value (BytewiseComparator). Supply a custom Comparator to change the order.
Q: Is compression required? What if Snappy isn't installed? Compression is optional and on by default. If the Snappy library isn't present at build time, blocks are stored uncompressed and the default compression setting is simply a no-op.
Q: How do I make a write durable against power loss?
Set WriteOptions::sync = true. By default writes are asynchronous and a reboot may lose the most recent updates (a mere process crash will not).
Q: How do I take a consistent backup? Snapshots give a consistent read-only view for live reads/iteration. For a file backup, ensure writes are quiesced or use a snapshot-aware copy of the directory; LevelDB's design (immutable SSTables) makes copying the directory feasible. See the Admin Guide for operational backup guidance.
Q: How big can a LevelDB database get?
LevelDB has been tested with very large datasets (on the order of a billion ~100-byte entries, ~100 GB). Practical limits depend on your hardware, max_open_files, and workload.
Q: How do I delete an entire database?
Call leveldb::DestroyDB(name, options) on a closed database. To recover an un-openable one, try leveldb::RepairDB.
Q: Why is there no Close() method?
You close the database by delete-ing the DB* handle. Delete all iterators and release all snapshots first.
Q: What is the current version?
This documentation reflects LevelDB 1.23 (kMajorVersion = 1, kMinorVersion = 23).
Q: Which headers am I allowed to include?
Only those under include/leveldb/. Everything else is internal and may change without notice.
Q: Does leveldbutil let me read or edit values from the shell?
Only indirectly. leveldbutil dump prints the textual contents of LevelDB files; there is no command-line get/put. Use the library API to manipulate data.
14. Reference
14.1 Options fields
Defaults as defined in include/leveldb/options.h.
| Field | Type | Default | Description |
|---|---|---|---|
comparator |
const Comparator* |
BytewiseComparator() |
Key ordering. Must match the DB's recorded comparator. |
create_if_missing |
bool |
false |
Create the DB if it doesn't exist. |
error_if_exists |
bool |
false |
Fail to open if the DB already exists. |
paranoid_checks |
bool |
false |
Stop early on any detected corruption. |
env |
Env* |
Env::Default() |
OS abstraction for file/thread operations. |
info_log |
Logger* |
nullptr |
Where progress/error logs go (null = a file in the DB dir). |
write_buffer_size |
size_t |
4 * 1024 * 1024 |
In-memory buffer before flushing to an SSTable. |
max_open_files |
int |
1000 |
Max files the DB may keep open. |
block_cache |
Cache* |
nullptr |
Uncompressed-block cache (null = internal 8 MB cache). |
block_size |
size_t |
4 * 1024 |
Approx uncompressed bytes per block. |
block_restart_interval |
int |
16 |
Keys between restart points for key delta-encoding. |
max_file_size |
size_t |
2 * 1024 * 1024 |
Max bytes per table file. |
compression |
CompressionType |
kSnappyCompression |
Block compression algorithm. |
zstd_compression_level |
int |
1 |
Zstd level, range [-5, 22]. |
reuse_logs |
bool |
false |
(Experimental) append to existing MANIFEST/log on open for faster startup. |
filter_policy |
const FilterPolicy* |
nullptr |
Optional filter (e.g. Bloom) to cut disk reads. |
14.2 ReadOptions fields
| Field | Type | Default | Description |
|---|---|---|---|
verify_checksums |
bool |
false |
Verify checksums of all data read. |
fill_cache |
bool |
true |
Cache the data read by this operation. |
snapshot |
const Snapshot* |
nullptr |
Read as of this snapshot (null = implicit current). |
14.3 WriteOptions fields
| Field | Type | Default | Description |
|---|---|---|---|
sync |
bool |
false |
Flush to persistent storage before returning. |
14.4 Status codes
| Predicate | Meaning |
|---|---|
ok() |
Success |
IsNotFound() |
Key not present |
IsCorruption() |
Data corruption detected |
IsIOError() |
Underlying I/O failure |
IsNotSupportedError() |
Operation not supported |
IsInvalidArgument() |
Bad argument |
Use Status::ToString() for a printable description.
14.5 DB methods
| Method | Purpose |
|---|---|
static Status Open(options, name, &db) |
Open or create a database. |
Status Put(wopts, key, value) |
Insert/overwrite an entry. |
Status Delete(wopts, key) |
Remove an entry (OK if absent). |
Status Write(wopts, batch) |
Apply a WriteBatch atomically. |
Status Get(ropts, key, &value) |
Look up a key. |
Iterator* NewIterator(ropts) |
Create a sorted iterator (caller deletes). |
const Snapshot* GetSnapshot() |
Take a consistent read snapshot. |
void ReleaseSnapshot(snap) |
Release a snapshot. |
bool GetProperty(name, &value) |
Read an internal property. |
void GetApproximateSizes(ranges, n, sizes) |
Estimate on-disk size of key ranges. |
void CompactRange(begin, end) |
Force compaction of a key range. |
Free functions: DestroyDB(name, options), RepairDB(name, options).
14.6 Iterator methods
| Method | Purpose |
|---|---|
bool Valid() |
Is the iterator positioned at an entry? |
void SeekToFirst() |
Position at the first key. |
void SeekToLast() |
Position at the last key. |
void Seek(target) |
Position at the first key ≥ target. |
void Next() |
Advance forward (requires Valid()). |
void Prev() |
Move backward (requires Valid()). |
Slice key() |
Current key (valid until next mutation). |
Slice value() |
Current value (valid until next mutation). |
Status status() |
Error status encountered during iteration. |
14.7 Public headers
c.h, cache.h, comparator.h, db.h, dumpfile.h, env.h, export.h, filter_policy.h, iterator.h, options.h, slice.h, status.h, table.h, table_builder.h, write_batch.h. Start with db.h.
Limitations
- Not a SQL database — no relational model, no SQL queries, no indexes.
- Single process — only one (possibly multi-threaded) process may access a database at a time.
- No client-server — no built-in network access; wrap your own server if needed.