{"id":20548216,"url":"https://github.com/reesporte/bugfruit","last_synced_at":"2026-04-16T16:06:22.296Z","repository":{"id":108399389,"uuid":"494545073","full_name":"reesporte/bugfruit","owner":"reesporte","description":"bugfruit is an embedded key-value store.","archived":false,"fork":false,"pushed_at":"2022-06-02T02:56:30.000Z","size":326,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-16T16:24:08.062Z","etag":null,"topics":["database","go","key-value"],"latest_commit_sha":null,"homepage":"https://dbdb.io/db/bugfruit","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reesporte.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2022-05-20T17:02:22.000Z","updated_at":"2023-03-10T19:49:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"0bb6e445-5e00-407f-bdf2-b35cc6b6380f","html_url":"https://github.com/reesporte/bugfruit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reesporte%2Fbugfruit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reesporte%2Fbugfruit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reesporte%2Fbugfruit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reesporte%2Fbugfruit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reesporte","download_url":"https://codeload.github.com/reesporte/bugfruit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242151994,"owners_count":20080212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","go","key-value"],"created_at":"2024-11-16T02:12:39.424Z","updated_at":"2026-04-16T16:06:17.245Z","avatar_url":"https://github.com/reesporte.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![bugfruit logo](logo/bugfruit.svg)\n\n# bugfruit\n\nbugfruit is a simple embedded key-value store.\n\n## About\n- Supports single writer, multiple concurrent readers.\n- Configurable `fsync` and garbage collection batch size.\n- Point-in-time snapshots.\n\n## Usage\nTo use in your application: `go get github.com/reesporte/bugfruit`\n\n```go\ns, err := bugfruit.NewStorage(\n    \"mealtime.db\",\n    0644,\n    \u0026bugfruit.Config{\n        VacuumBatch: 3600, // 3600 writes before garbage collection\n        FsyncBatch:  2000, // 2000 writes before fsync\n    },\n)\nif err != nil {\n    log.Fatalf(\"couldn't create storage: %v\", err)\n}\n\nerr = s.Set(\n    \"Meriadoc\",\n    []byte(\"I don't think he knows about second breakfast, Pip.\"),\n)\nif err != nil {\n    log.Fatalf(\"couldn't set: %v\", err)\n}\n\nif val, ok := s.Get(\"Meriadoc\"); ok {\n    fmt.Printf(\"\\\"%s\\\", said Meriadoc.\\n\", val)\n}\n\nif err = s.Delete(\"Meriadoc\"); err != nil {\n    log.Fatalf(\"couldn't delete: %v\", err)\n}\n\nerr = s.Set(\n    \"Pippin\",\n    []byte(\"What about elevenses? Luncheon? Afternoon tea? Dinner? Supper?\"),\n)\nif err != nil {\n    log.Fatalf(\"couldn't set: %v\", err)\n}\n\nif err = s.Snapshot(\"mealtime-snapshot.db\", 0644); err != nil {\n    log.Fatalf(\"couldn't take snapshot: %v\", err)\n}\n```\n\n## Performance\n### Benchmarks\nI approximately replicated some baseline LMDB microbenchmarks found\n[here](http://www.lmdb.tech/bench/microbench/) for random reads and random writes.\n\nI ran these benchmarks on an M1 Mac with 8GB of RAM and GOMAXPROCS set to 4 to\nreplicate the 4 core setup the LMDB microbenchmarks used. Like in the LMDB\nbenchmarks, `fsync` and garbage collection were turned off. \n\nKeep in mind that there's\n[3 kinds of lies](https://en.wikipedia.org/wiki/Lies,_damned_lies,_and_statistics):\nlies, damned lies, and benchmarks. I am running on more modern hardware than the\nbenchmarks I've linked to. The LMDB benchmarks are from September 2012 running on\ndevices built in 2012, whereas mine are running on a 2020 M1 Macbook Air.\n\nAlso, while somewhat useful for comparing databases, these\nbenchmarks don't necessarily reflect performance in a real production environment on\nyour hardware. They should be taken with that healthy grain of salt.\n\nBenchmarking code can be found in `benchmarks/benchmark.go`.\n\n#### Small values: random 16 byte keys, random 100 byte values\nFor comparison, the relevant LMDB microbenchmarks are\n[here](http://www.lmdb.tech/bench/microbench/#sec2).\n\n##### 1,000,000 ops per iteration\nI ran 1,000,000 operations per iteration, the same as the LMDB microbenchmarks.\nThe output file is `benchmarks/bench-x-small.log`.\n\n|Type|Average ops/sec| Std. Dev. ops/sec|\n|---|---|---|\n|Set|417,070|7,953|\n|Delete|583,134|7,283|\n|Get|7,891,966|210,693|\n\n##### 10,000,000 ops per iteration\nFor the sake of completeness (and curiosity), I also ran 10,000,000 operations per iteration.\nThe output file is `benchmarks/bench-small.log`.\n\n|Type|Average ops/sec| Std. Dev. ops/sec|\n|---|---|---|\n|Set|360,777|3,100|\n|Delete|466,481|4,168|\n|Get|6,171,403|174,074|\n\n#### Large values: random 16 byte keys, random 100,000 byte values\nFor comparison, the relevant LMDB microbenchmarks are\n[here](http://www.lmdb.tech/bench/microbench/#sec4).\n\n##### 10,000 ops per iteration\nI ran 10,000 operations per iteration, the same as the LMDB microbenchmarks.\nThe output file is `benchmarks/bench-large.log`.\n\n|Type|Average ops/sec| Std. Dev. ops/sec|\n|---|---|---|\n|Set|10,783|318|\n|Delete|153,036|81,142|\n|Get|20,864,490|809,940|\n\n##### 100,000 ops per iteration\nI also ran 100,000 operations per iteration. Unsurprisingly, the increased number of\noperations per iteration decreased performance, especially since the total database\nsize ended up being 2GB larger than my RAM (~10GB vs 8GB).\n\nDespite the decrease in performance, it was still fairly on par with other key-value\ndatabases in the less intensive LMDB microbenchmarks. \n\nYou can see the benchmark output files in `benchmarks/bench-x-large.log`.\n\n|Type|Average ops/sec| Std. Dev. ops/sec|\n|---|---|---|\n|Set|4,500|1,019|\n|Delete|5,194|411|\n|Get|11,641,740|2,572,501|\n\n### Disk Usage\nbugfruit does not compress data, which can result in a large database file,\nespecially if you don't run garbage collection.\n\nTo calculate how large your database file will be, sum the size of your\nkey/value pair in bytes with 9 (the size of a datum's metadata). If you delete a\ndatum but don't run garbage collection, that datum's size should still be included\nin the total size of the database. Likewise, if you overwrite a datum but don't run\ngarbage collection, the old datum's size should be included in the total size of the\ndatabase.\n\n### Limitations\nbugfruit has various limitations that may exclude it from being a viable choice for\nyour application. These include:\n- All data should fit in RAM. If your machine does not have enough RAM to comfortably\n  fit all your data, you may notice performance issues.\n- All operations are individual transactions. Any get/set/delete/snapshot operation\n  happens atomically. There is no support for rollbacks, batched transactions, or\n  reads/writes within the same transaction.\n- bugfruit is optimized for read performance over write performance. While it can\n  perform fairly well for a moderate number of writes, it is not intended for use\n  with predominantly write-heavy applications.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freesporte%2Fbugfruit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freesporte%2Fbugfruit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freesporte%2Fbugfruit/lists"}