{"id":49264265,"url":"https://github.com/jamesgober/emdb-rs","last_synced_at":"2026-05-04T04:07:00.678Z","repository":{"id":353669798,"uuid":"1220330572","full_name":"jamesgober/emdb-rs","owner":"jamesgober","description":"A lightweight, high-performance embedded database for Rust.","archived":false,"fork":false,"pushed_at":"2026-05-04T02:40:15.000Z","size":574,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-04T03:18:37.877Z","etag":null,"topics":["database","embedded","embedded-database","kv","rust","storage"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/emdb","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T19:37:43.000Z","updated_at":"2026-05-04T02:40:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/emdb-rs","commit_stats":null,"previous_names":["jamesgober/emdb-rs"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/emdb-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Femdb-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Femdb-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Femdb-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Femdb-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/emdb-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Femdb-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32594004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"online","status_checked_at":"2026-05-04T02:00:06.625Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","embedded","embedded-database","kv","rust","storage"],"created_at":"2026-04-25T09:07:15.578Z","updated_at":"2026-05-04T04:07:00.671Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cstrong\u003eemdb\u003c/strong\u003e\n    \u003cbr\u003e\n    \u003csup\u003e\u003csub\u003eEMBEDDED DATABASE FOR RUST\u003c/sub\u003e\u003c/sup\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/emdb\"\u003e\u003cimg alt=\"crates.io\" src=\"https://img.shields.io/crates/v/emdb.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/emdb\" alt=\"Download emdb\"\u003e\u003cimg alt=\"Crates.io Downloads\" src=\"https://img.shields.io/crates/d/emdb?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/emdb\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://docs.rs/emdb/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/emdb-rs/actions\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/jamesgober/emdb-rs/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    A lightweight, high-performance embedded key-value database for Rust.\n\u003c/p\u003e\n\n---\n\n## Why emdb\n\nBitcask-style architecture: one mmap-backed append-only file, sharded\nin-memory hash index, single-writer with multi-reader. Same shape that\nLMDB and redb use for reads; same shape that Riak/HaloDB use for writes.\n\n### Performance vs. peers\n\n5 M records, 24-byte random keys, 150-byte random values — same workload\nshape as redb's published bench. Lower is better; numbers in\nmilliseconds. Run on a Windows 11 NVMe consumer box. Reproduce with\n`cargo bench --bench lmdb_style --features ttl,bench-compare`.\n\n| phase                       |        emdb |    redb  |    sled  |  emdb vs redb     |\n|-----------------------------|------------:|---------:|---------:|------------------:|\n| bulk load                   |    **3086** |    68231 |    39506 |     22.1× faster  |\n| batch writes                |    **2616** |     6656 |     1370 |      2.5× faster  |\n| nosync writes               |     **131** |     1063 |      697 |      8.1× faster  |\n| random reads (1M)           |     **332** |     2814 |     6201 |      8.5× faster  |\n| random reads (4 threads)    |     **817** |    11945 |    22813 |     14.6× faster  |\n| random reads (8 threads)    |     **511** |    12838 |    22891 | **25.1× faster**  |\n| removals                    |    **6161** |    32840 |    25271 |      5.3× faster  |\n| compaction                  |    **6513** |    14163 |      N/A |      2.2× faster  |\n| uncompacted size            |    1.08 GiB | 4.00 GiB | 2.15 GiB |     3.7× smaller  |\n| compacted size              | **498 MiB** | 1.64 GiB |      N/A |     3.4× smaller  |\n| individual writes (fsync/op)|       25281 |  **644** |  **452** | see note 1        |\n| random range reads          |       opt-in|     2329 |     6247 | see note 2        |\n\nemdb wins every aggregate-throughput column at 5 M scale, often by\n**order-of-magnitude margins**. Two notes on the columns where the\npicture is more nuanced:\n\n1. **`individual writes` is fsync-bound.** This phase calls\n   `db.insert(); db.flush();` per record from a single thread. Each\n   `db.flush()` is one `fdatasync` (one `FlushFileBuffers` on\n   Windows) and that syscall is the floor — ~27 ms / call on the\n   reference NVMe consumer box, regardless of how few bytes were\n   dirtied. redb and sled win this single-threaded column because\n   their commit machinery folds adjacent writes into a single sync.\n   For multi-threaded per-record-durability workloads, opt into\n   `FlushPolicy::Group` — the [group-commit benchmark\n   below](#group-commit-multi-threaded-per-record-durability) shows\n   it converting 8 concurrent flushers into one shared fsync for\n   a **7× write-throughput win**. Single-thread workloads should\n   still batch through `db.transaction(|tx| ...)` or\n   `db.insert_many(...)`, both of which already dominate redb in\n   the aggregate columns above.\n2. **Range reads are opt-in, not unsupported.** emdb's primary\n   index is hash-keyed, so the default open does not pay the memory\n   tax for sorted iteration. Set\n   `EmdbBuilder::enable_range_scans(true)` to maintain a parallel\n   `BTreeMap` secondary index per namespace — see the\n   [Range scans](#range-scans) section below for the API and the\n   memory-cost trade-off. v0.8 also adds streaming\n   `Emdb::range_iter` / `range_prefix_iter` so consumers that only\n   read the first few elements pay only for what they consume.\n\n### Read scaling under fan-out\n\nThe MT random-read columns above show emdb scaling to **9.94 M\nreads/sec aggregate at 8 threads** on a 4-core consumer box, while\nredb stalls near 347 K/sec past one thread. The lock-free `Arc\u003cMmap\u003e`\nread path plus the 64-shard hash index keep the hot path contention-\nfree; past core count, shared memory bandwidth is the only cap.\n\nFor more thread-count granularity, run\n`cargo bench --bench concurrent_reads`.\n\n### Group commit: multi-threaded per-record durability\n\n`FlushPolicy::Group` lets concurrent `flush()` calls share a single\n`fdatasync`. The shape that motivates it is N independent producer\nthreads each writing one record then calling `flush` for per-record\ndurability — a pattern where `OnEachFlush` pays N syncs even though\none would do.\n\nRun with `cargo bench --bench group_commit --features ttl`. Default\nworkload is 8 threads × 200 writes/thread:\n\n| policy         | wall time (ms) |   writes/sec |    speedup |\n|----------------|---------------:|-------------:|-----------:|\n| OnEachFlush    |          2192  |         730  |      1.00× |\n| Group          |       **272**  |    **5 880** |  **8.06×** |\n\n`max_batch` should be set close to the expected concurrent flusher\ncount (typically `num_cpus`). Setting it higher means the leader\nwaits the full `max_wait` for followers that can never arrive,\nturning batching into pure tail latency.\n\n```rust,no_run\nuse std::time::Duration;\nuse emdb::{Emdb, FlushPolicy};\n\nlet db = Emdb::builder()\n    .flush_policy(FlushPolicy::Group {\n        max_wait: Duration::from_micros(500),\n        max_batch: 8,\n    })\n    .build()?;\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n### `FlushPolicy::WriteThrough`: opt-in per-pwrite durability\n\nFor workloads where `OnEachFlush`'s per-`flush()` cost is dominated\nby `FlushFileBuffers` latency (the canonical Windows\nsingle-thread-per-record-durability pain), v0.8.5 adds\n`FlushPolicy::WriteThrough` as a third policy. The file is opened\nwith `FILE_FLAG_WRITE_THROUGH` (Windows) / `O_SYNC` (Unix) so every\n`pwrite` is durable on return; `flush()` becomes near-free.\n\nThe trade-off is real: bulk loads under `WriteThrough` are slower\nbecause every individual `pwrite` waits for disk instead of\nbenefiting from the OS write-back cache. Whether `WriteThrough`\nbeats `OnEachFlush` depends on the workload, the file's existing\nsize, and the OS's `FlushFileBuffers` cost on that file. Benchmark\non your actual data to decide.\n\nReproduce on your machine:\n\n```powershell\ncargo bench --bench write_through --features ttl\n```\n\n```rust\nuse emdb::{Emdb, FlushPolicy};\n\nlet db = Emdb::builder()\n    .flush_policy(FlushPolicy::WriteThrough)\n    .build()?;\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\nSee [docs/BENCH.md](docs/BENCH.md) for full run instructions and\ntuning notes.\n\n## Status\n\n**v0.8.5 (beta).** The storage engine is a Bitcask-style mmap-backed\nappend-only log with a sharded in-memory hash index. Single-writer,\nmulti-reader. Optional at-rest encryption (AES-256-GCM or\nChaCha20-Poly1305, raw key or Argon2id passphrase). Optional\nsorted-iteration secondary index via\n`EmdbBuilder::enable_range_scans(true)`. Three flush-policy\nvariants (`OnEachFlush`, `Group`, `WriteThrough`) for picking the\nright durability cost model for the workload. Streaming `iter` /\n`keys` / `range` plus cursor-style `iter_from` / `iter_after`. A\nzero-copy `get_zerocopy` read API. Atomic `backup_to(path)`\nsnapshots, point-in-time `stats()` introspection, and stale-\nlockfile recovery via `lock_holder` + `break_lock`. Pre-1.0; the\nAPI may still change before 1.0.\n\nThe remaining work for v1.0 is API stabilisation: an audit pass\nfor `pub` vs `pub(crate)`, full doc coverage on every public item,\na `cargo-fuzz` target for the record decoder, and a\n`docs/stability.md` SemVer commitment. No further architectural\nchanges are planned before 1.0.\n\n## Installation\n\n```toml\n[dependencies]\nemdb = \"0.8.5\"\n```\n\n## Quick start\n\n```rust\nuse emdb::Emdb;\n\nlet db = Emdb::open_in_memory();\ndb.insert(\"name\", \"emdb\")?;\nassert_eq!(db.get(\"name\")?, Some(b\"emdb\".to_vec()));\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n## Persistence\n\n```rust\nuse emdb::Emdb;\n\nlet path = std::env::temp_dir().join(\"app.emdb\");\n\n{\n    let db = Emdb::open(\u0026path)?;\n    db.insert(\"user:1\", \"james\")?;\n    db.flush()?;\n}\n\nlet reopened = Emdb::open(\u0026path)?;\nassert_eq!(reopened.get(\"user:1\")?, Some(b\"james\".to_vec()));\n# let _cleanup = std::fs::remove_file(path);\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n`flush()` durably writes the record bytes; it does not rewrite the\nfile header. The header carries a `tail_hint` that lets the next\nopen skip past the bulk of the log instead of scanning from byte\n4096. Call `checkpoint()` at quiescent points (after a bulk load,\non graceful shutdown) to update that hint and pay one extra fsync\nin exchange for fast reopens. The drop of the last handle attempts\na checkpoint as a backstop; explicit calls are recommended for\nlong-lived processes that care about reopen latency.\n\n## Storage path resolution\n\n`Emdb::open(path)` is the simplest entry point. For library / app\nauthors who want platform-aware path resolution, set both `app_name`\nand `database_name` so your project gets a clearly-scoped subdirectory\nunder the platform data root.\n\n```rust\nuse emdb::Emdb;\n\n// Resolves to:\n//   Linux:   $XDG_DATA_HOME/hivedb-kv/sessions.emdb\n//   macOS:   ~/Library/Application Support/hivedb-kv/sessions.emdb\n//   Windows: %LOCALAPPDATA%\\hivedb-kv\\sessions.emdb\nlet db = Emdb::builder()\n    .app_name(\"hivedb-kv\")\n    .database_name(\"sessions.emdb\")\n    .build()?;\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n| builder method        | default if unset      | notes                                            |\n|-----------------------|-----------------------|--------------------------------------------------|\n| `app_name(name)`      | `\"emdb\"`              | Single folder name under the platform data root. |\n| `database_name(name)` | `\"emdb-default.emdb\"` | Bare filename; no extension auto-added.          |\n| `data_root(path)`     | platform default      | Escape hatch for tests / containers / sandboxes. |\n\n`app_name` is a single folder name by design — path separators (`/`,\n`\\`), `..` components, and the empty string are rejected at build time.\nMixing `path()` with any of the OS-resolution methods returns\n`Error::InvalidConfig`.\n\n## Bulk loading\n\nFor high-volume inserts, prefer `insert_many` — it packs every record\ninto a single buffer and does one `pwrite`, which is the path that beats\nredb 2.4× in the bench above.\n\n```rust\nuse emdb::Emdb;\n\nlet db = Emdb::open_in_memory();\nlet items: Vec\u003c(String, String)\u003e = (0..1000)\n    .map(|i| (format!(\"k{i}\"), format!(\"v{i}\")))\n    .collect();\ndb.insert_many(items.iter().map(|(k, v)| (k.as_str(), v.as_str())))?;\ndb.flush()?;\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n## Transactions\n\n```rust\nuse emdb::Emdb;\n\nlet db = Emdb::open_in_memory();\ndb.transaction(|tx| {\n    tx.insert(\"user:1\", \"james\")?;\n    tx.insert(\"user:2\", \"alex\")?;\n    Ok(())\n})?;\n\nassert_eq!(db.get(\"user:1\")?, Some(b\"james\".to_vec()));\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\nTransactions buffer writes and commit them as one bulk insert on\nsuccess. `Err` from the closure drops the buffered writes — nothing\nhits disk.\n\n```rust\nuse emdb::{Emdb, Error};\n\nlet db = Emdb::open_in_memory();\nlet failed = db.transaction::\u003c_, ()\u003e(|tx| {\n    tx.insert(\"temp\", \"value\")?;\n    Err(Error::TransactionAborted(\"rollback\"))\n});\n\nassert!(failed.is_err());\nassert_eq!(db.get(\"temp\")?, None);\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n### Durability model\n\nEach record is framed with a CRC32. On crash recovery the engine walks\nrecords from `header.tail_hint` and treats the first bad CRC as the\ntruncation point. Per-record atomicity is guaranteed; **batch\natomicity across a transaction is not** — a crash mid-commit leaves a\nprefix of the batch durable. Callers that need true all-or-nothing\nacross N records must layer that on top.\n\n## Compaction\n\nThe append-only log accumulates tombstoned and superseded records over\ntime. `Emdb::compact()` rewrites the live records into a sibling file,\ntruncates to logical size, and atomically swaps it in.\n\n```rust\nuse emdb::Emdb;\n\nlet path = std::env::temp_dir().join(\"compact.emdb\");\nlet db = Emdb::open(\u0026path)?;\ndb.insert(\"k\", \"v\")?;\ndb.remove(\"k\")?;            // tombstone added to log\ndb.compact()?;              // log now holds only the live records\ndb.flush()?;\n# let _cleanup = std::fs::remove_file(\u0026path);\n# let _cleanup2 = std::fs::remove_file(format!(\"{}.lock\", path.display()));\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\nCompaction is a heavier operation than `flush` — call it on maintenance\nwindows, not on every write. Existing readers holding `Arc\u003cMmap\u003e`\nsnapshots from before the compaction continue reading from the old\ninode until they release; new reads see the compacted layout.\n\n## Range scans\n\nemdb's primary index is a sharded hash, so unsorted iteration is the\ndefault. To support range / prefix queries, opt in at open time with\n`EmdbBuilder::enable_range_scans(true)`. The engine maintains a\nparallel `BTreeMap\u003cVec\u003cu8\u003e, u64\u003e` secondary index per namespace; range\nqueries hit the BTreeMap and resolve values through the mmap.\n\n```rust\nuse emdb::Emdb;\n\nlet db = Emdb::builder()\n    .enable_range_scans(true)\n    .build()?;\n\ndb.insert(\"user:001\", \"alice\")?;\ndb.insert(\"user:002\", \"bob\")?;\ndb.insert(\"session:abc\", \"token\")?;\n\n// Half-open range: [\"user:\", \"user;\").\nlet users = db.range(b\"user:\".to_vec()..b\"user;\".to_vec())?;\nassert_eq!(users.len(), 2);\nassert_eq!(users[0].0, b\"user:001\");\nassert_eq!(users[1].0, b\"user:002\");\n\n// Prefix shorthand: builds the half-open `[prefix, prefix++)` range.\nlet same = db.range_prefix(b\"user:\")?;\nassert_eq!(users.len(), same.len());\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\nCost: one `Vec\u003cu8\u003e` clone of the key per insert plus the `BTreeMap`\nnode overhead — roughly doubles in-memory index size for a typical\nworkload. Calling `db.range(...)` without enabling this at open time\nreturns `Error::InvalidConfig`.\n\n`Namespace::range` and `Namespace::range_prefix` give the same view\nscoped to a named namespace.\n\n## Cargo features\n\n- `ttl` *(default)* — per-record expiration and `default_ttl`.\n- `nested` — dotted-prefix group operations and `Focus` handles.\n- `encrypt` — AES-256-GCM + ChaCha20-Poly1305 at-rest encryption with\n  raw-key or Argon2id-derived passphrase. Pulls in `aes-gcm`,\n  `chacha20poly1305`, `argon2`, `rand_core`.\n- `bench-compare` — pulls in `redb` and `sled` for the comparative\n  bench (dev-only; not for production builds).\n- `bench-rocksdb` / `bench-redis` — additional comparative bench peers.\n\n## Concurrency\n\n`Emdb` is `Send + Sync` and cheap to clone — clones share the same\nunderlying engine via `Arc`. Pass clones across threads instead of\nsynchronising access to a single handle.\n\n**Reads scale.** A 64-shard sharded `RwLock\u003cHashMap\u003e` index plus\nzero-copy slices from a shared `Arc\u003cMmap\u003e` keep the hot path\ncontention-free: the comparative bench above hits 7.66 M reads/sec\naggregate at 8 threads on a 4-core consumer box.\n\n**Writes are single-writer.** All writers serialise on one mutex that\ncovers the encode-and-pwrite step. This matches the model used by\nLMDB, redb, BoltDB, and most of the embedded-KV ecosystem (multi-writer\nconcurrency requires either a recovery model with sentinel records or\nper-thread log segments — both queued for v1.0). High-throughput\nproducer workloads should batch through `db.insert_many(...)` or\n`db.transaction(|tx| ...)`, which amortise the writer-mutex acquire\nacross many records.\n\n```rust\nuse std::sync::Arc;\nuse std::thread;\n\nuse emdb::Emdb;\n\nlet db = Arc::new(Emdb::open_in_memory());\ndb.insert(\"counter\", \"0\")?;\n\nlet mut workers = Vec::new();\nfor i in 0_u32..4 {\n    let db = Arc::clone(\u0026db);\n    workers.push(thread::spawn(move || {\n        let _ = db.insert(format!(\"k{i}\"), format!(\"v{i}\"));\n    }));\n}\n\nfor worker in workers {\n    let _ = worker.join();\n}\n\nassert!(db.len()? \u003e= 4);\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n## TTL example\n\n```rust\n# #[cfg(feature = \"ttl\")]\n# {\nuse std::time::Duration;\n\nuse emdb::{Emdb, Ttl};\n\nlet db = Emdb::builder()\n    .default_ttl(Duration::from_secs(30))\n    .build()?;\ndb.insert_with_ttl(\"session\", \"token\", Ttl::Default)?;\nassert!(db.ttl(\"session\")?.is_some());\n# }\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n## Nested example\n\n```rust\n# #[cfg(feature = \"nested\")]\n# {\nuse emdb::Emdb;\n\nlet db = Emdb::open_in_memory();\nlet product = db.focus(\"product\");\nproduct.set(\"name\", \"phone\")?;\nproduct.set(\"price\", \"799\")?;\n\nassert_eq!(product.get(\"name\")?, Some(b\"phone\".to_vec()));\nassert_eq!(db.group(\"product\")?.count(), 2);\n# }\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\n## Encryption\n\n```rust\n# #[cfg(feature = \"encrypt\")]\n# {\nuse emdb::Emdb;\n\nlet path = std::env::temp_dir().join(\"encrypted.emdb\");\nlet _ = std::fs::remove_file(\u0026path);\nlet _ = std::fs::remove_file(format!(\"{}.lock\", path.display()));\n\nlet db = Emdb::builder()\n    .path(path.clone())\n    .encryption_passphrase(\"correct horse battery staple\")\n    .build()?;\ndb.insert(\"k\", \"v\")?;\ndb.flush()?;\ndrop(db);\n\nlet reopened = Emdb::builder()\n    .path(path.clone())\n    .encryption_passphrase(\"correct horse battery staple\")\n    .build()?;\nassert_eq!(reopened.get(\"k\")?, Some(b\"v\".to_vec()));\n\n# drop(reopened);\n# let _ = std::fs::remove_file(\u0026path);\n# let _ = std::fs::remove_file(format!(\"{}.lock\", path.display()));\n# }\n# Ok::\u003c(), emdb::Error\u003e(())\n```\n\nThe cipher is creation-time-fixed and stored in the header — reopens\nauto-dispatch. Wrong passphrase surfaces as\n`Error::EncryptionKeyMismatch` from a verification block check, not\nfrom a corrupted-data read. Three offline admin functions\n(`Emdb::enable_encryption`, `disable_encryption`, `rotate_encryption_key`)\nlet you toggle encryption or rotate keys on an existing file via\natomic rewrite-then-rename, leaving an `.encbak` backup.\n\n## Goals\n\n- **Embedded-first** — runs in-process; no separate server, no network.\n- **High performance** — zero-copy reads, allocation-free hot paths,\n  cache-friendly layout, batched writes amortise lock and syscall costs.\n- **Safe** — strict `clippy` profile, no `unwrap` in library code,\n  every `unsafe` block documented with its invariant.\n- **Small footprint** — minimal dependency graph, fast compile times.\n- **Portable** — Linux, macOS, Windows on x86_64 and ARM64.\n\n## Non-goals\n\n- Client/server operation (use a dedicated DBMS for that).\n- SQL.\n- Distributed replication.\n- Range scans on a single namespace (the index is hash-based; insert a\n  prefix-sorted secondary structure on top if you need ranges).\n\n## Benchmarking\n\nemdb ships Criterion benches. The comparative bench can include `redb`,\n`sled`, optionally RocksDB, and optionally Redis.\n\n- Core: [benches/kv.rs](benches/kv.rs)\n- Comparative: [benches/comparative.rs](benches/comparative.rs)\n\n```powershell\n# Just emdb\ncargo bench --bench kv --features ttl\n\n# emdb vs sled vs redb\ncargo bench --bench comparative --features ttl,bench-compare\n\n# Add RocksDB\ncargo bench --bench comparative --features ttl,bench-compare,bench-rocksdb\n\n# Add Redis (set EMDB_REDIS_URL first)\n$env:EMDB_REDIS_URL = \"redis://127.0.0.1/\"\ncargo bench --bench comparative --features ttl,bench-compare,bench-redis\n```\n\nFull bench workflow and tuning notes: [docs/BENCH.md](docs/BENCH.md).\n\n## Related projects\n\n`emdb` is the Rust implementation. Implementations in other languages\n(Go, C, etc.) are planned and will live under their own repositories.\n\n## License\n\nLicensed under the [Apache License, Version 2.0](./LICENSE).\n\nCopyright \u0026copy; 2026 James Gober.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Femdb-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Femdb-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Femdb-rs/lists"}