lithos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/ext/lithos/extconf.rb +20 -0
- data/ext/lithos/lithos.cpp +1026 -0
- data/lib/lithos/version.rb +5 -0
- data/lib/lithos.rb +170 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7741f8dffd9dd5d4d0f710b18fca0a7b1a0d574e893d60c9f5735c36a93954d6
|
|
4
|
+
data.tar.gz: 6d4eb49a96a6cb44a97587f997905cc77c4e93bd4c33f64579db48754180b7d8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6d24765cfbfdc9d6394eacd84081eeba1891f9f8423d2c3665cb7270e417e0cfe4d3e147b79e97607cc1ed6c56d96894805c13a88db2957de37fe350401f60bb
|
|
7
|
+
data.tar.gz: 4af63db49e563dc16b3411f44a238b86174336b532f1caa6ab478c8e7d68b8282e3ec8d291eeba821177bdda0d0ab654eeaca2a61b4eaf4d735797d40d244883
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-05-30
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `Lithos.open(path, sync:, memtable_bytes:)` with block (auto-close) and
|
|
13
|
+
non-block forms; `#close`, `#closed?`, `#path`.
|
|
14
|
+
- `#put`/`#[]=`, `#get`/`#[]`, `#delete`, `#key?` (+ `include?`/`has_key?`),
|
|
15
|
+
`#fetch` (with default/block, raises `KeyError`).
|
|
16
|
+
- Ordered iteration: `#each`, `#each_key`, `#scan(gte:/gt:/lte:/lt:)`,
|
|
17
|
+
`#range(from, to)`; `Enumerable` mixed in; `#size`/`#length`, `#empty?`, `#to_h`.
|
|
18
|
+
- `#flush` (seal the memtable into an SSTable) and `#compact` (merge SSTables,
|
|
19
|
+
dropping shadowed keys and tombstones).
|
|
20
|
+
- LSM engine: CRC32 write-ahead log (durable per write in sync mode), sorted
|
|
21
|
+
memtable, immutable memory-mapped SSTables with bloom filters + sparse index,
|
|
22
|
+
atomically-published MANIFEST catalog, crash recovery via WAL replay (torn /
|
|
23
|
+
bad-CRC tail discarded), and an exclusive single-writer directory lock.
|
|
24
|
+
- Arbitrary binary keys and values (embedded NULs); lexicographic unsigned-byte
|
|
25
|
+
key ordering.
|
|
26
|
+
|
|
27
|
+
[Unreleased]: https://github.com/main-path/lithos/compare/v0.1.0...HEAD
|
|
28
|
+
[0.1.0]: https://github.com/main-path/lithos/releases/tag/v0.1.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ned
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# lithos
|
|
2
|
+
|
|
3
|
+
**A small embedded, ordered, crash-safe key-value store for Ruby — no external database.**
|
|
4
|
+
|
|
5
|
+
`lithos` is a from-scratch storage engine: a **log-structured merge (LSM) tree**,
|
|
6
|
+
the same design behind LevelDB/RocksDB, written as a native Ruby extension with
|
|
7
|
+
**zero external dependency**. You get:
|
|
8
|
+
|
|
9
|
+
- **Durable writes** — every `put`/`delete` is appended to a CRC-checked
|
|
10
|
+
write-ahead log and `fsync`'d before it returns (in the default `sync:` mode).
|
|
11
|
+
- **Crash recovery** — on open, the WAL is replayed; a torn or corrupted tail is
|
|
12
|
+
detected and discarded.
|
|
13
|
+
- **Ordered keys** — keys are stored sorted (unsigned-byte order), so you get
|
|
14
|
+
`each`, `each_key`, and range `scan`s for free.
|
|
15
|
+
- **Binary safe** — keys and values are arbitrary byte strings (embedded NULs OK).
|
|
16
|
+
|
|
17
|
+
The niche it fills: every other ordered + crash-safe Ruby KV store is a binding
|
|
18
|
+
to an external C/C++ library (LMDB/LevelDB/RocksDB) that's a pain to build on a
|
|
19
|
+
native-MSVC Windows Ruby. `lithos` is self-contained and builds with
|
|
20
|
+
[`vcvars`](https://rubygems.org/gems/vcvars) — no prebuilt database needed.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- **Windows** with a native **MSVC (mswin)** Ruby. Not supported on MinGW/UCRT.
|
|
25
|
+
- Visual Studio 2017+ / Build Tools with the **Desktop development with C++** workload.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
gem install lithos
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "lithos"
|
|
37
|
+
|
|
38
|
+
Lithos.open("data/mydb") do |db|
|
|
39
|
+
db["alpha"] = "one"
|
|
40
|
+
db.put("beta", "two")
|
|
41
|
+
db.put("\x00raw\xff", "binary ok") # arbitrary binary keys + values
|
|
42
|
+
|
|
43
|
+
db["alpha"] # => "one"
|
|
44
|
+
db["missing"] # => nil
|
|
45
|
+
db.key?("beta") # => true
|
|
46
|
+
db.fetch("nope", "default") # => "default" (or raises KeyError)
|
|
47
|
+
db.delete("beta") # => true (existed)
|
|
48
|
+
|
|
49
|
+
# ordered iteration (ascending unsigned-byte key order)
|
|
50
|
+
db.each { |k, v| puts "#{k}=#{v}" }
|
|
51
|
+
db.each_key { |k| puts k }
|
|
52
|
+
|
|
53
|
+
# ordered range scans
|
|
54
|
+
db.scan(gte: "a", lt: "m") { |k, v| ... } # gt/lt exclusive, gte/lte inclusive
|
|
55
|
+
db.range("a", "m") { |k, v| ... } # half-open [a, m)
|
|
56
|
+
db.scan(gte: "a").to_a # Enumerator without a block
|
|
57
|
+
|
|
58
|
+
db.size # live key count
|
|
59
|
+
db.flush # seal the memtable into an SSTable
|
|
60
|
+
db.compact # merge SSTables, dropping shadowed keys + tombstones
|
|
61
|
+
end # closed automatically (flush + fsync)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Open without a block when you want to manage the lifetime yourself:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
db = Lithos.open("data/mydb", sync: true)
|
|
68
|
+
db["k"] = "v"
|
|
69
|
+
db.close
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Data persists across reopens, and survives a crash (process kill, power loss at
|
|
73
|
+
the OS-flush boundary): reopening replays the WAL and discards any partial tail.
|
|
74
|
+
|
|
75
|
+
## How it works
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
put/delete ──> WAL (append + CRC + fsync) ──> memtable (sorted std::map)
|
|
79
|
+
│ (size threshold)
|
|
80
|
+
▼
|
|
81
|
+
SSTable (immutable, mmap'd:
|
|
82
|
+
sorted data + bloom + sparse index)
|
|
83
|
+
get ──> memtable ──> SSTables newest→oldest (bloom-filtered)
|
|
84
|
+
compact ──> k-way merge of all SSTables ──> one SSTable (drops tombstones)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The set of live SSTables + the active WAL is recorded in a `MANIFEST` that is
|
|
88
|
+
rewritten atomically (temp file + `FlushFileBuffers` + atomic rename), so the
|
|
89
|
+
on-disk catalog is never observed half-updated.
|
|
90
|
+
|
|
91
|
+
## Notes
|
|
92
|
+
|
|
93
|
+
- **Durability vs speed:** `sync: true` (default) `fsync`s every write. For bulk
|
|
94
|
+
loads, `Lithos.open(path, sync: false)` skips per-write `fsync` (call `#flush`
|
|
95
|
+
or `#close` to make data durable) — faster, but a crash can lose recent writes.
|
|
96
|
+
- **Single writer:** a store takes an exclusive directory lock on open; it is not
|
|
97
|
+
safe to share one store across threads (give each thread its own, or use a Mutex).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
[MIT](LICENSE.txt).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# extconf.rb for the lithos LSM key-value store extension.
|
|
4
|
+
|
|
5
|
+
require "mkmf"
|
|
6
|
+
|
|
7
|
+
unless RbConfig::CONFIG["target_os"] =~ /mswin/
|
|
8
|
+
abort <<~MSG
|
|
9
|
+
lithos requires a native Windows MSVC (mswin) Ruby — its storage engine uses
|
|
10
|
+
Win32 file APIs (memory-mapped SSTables, FlushFileBuffers, atomic rename) and
|
|
11
|
+
is built with cl.exe. Your Ruby is "#{RbConfig['arch']}".
|
|
12
|
+
MSG
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# C++ engine (std::map/std::string/std::vector/std::priority_queue) — needs C++ exceptions.
|
|
16
|
+
$CXXFLAGS << " -EHsc"
|
|
17
|
+
|
|
18
|
+
# kernel32 (file APIs / mapping) is linked by default; no extra import libs needed.
|
|
19
|
+
|
|
20
|
+
create_makefile("lithos/lithos")
|