commitkv 1.0.1__tar.gz → 1.1.1__tar.gz
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.
- {commitkv-1.0.1 → commitkv-1.1.1}/PKG-INFO +16 -1
- {commitkv-1.0.1 → commitkv-1.1.1}/README.md +15 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/pyproject.toml +3 -3
- {commitkv-1.0.1 → commitkv-1.1.1}/python/commitkv/_native.pyi +4 -1
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/src/store.rs +17 -7
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/tests/store.rs +15 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/LICENSE +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/python/commitkv/__init__.py +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/python/commitkv/py.typed +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/Cargo.lock +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/Cargo.toml +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/Cargo.toml +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/examples/lines.rs +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/src/cache.rs +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/src/codec.rs +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/src/error.rs +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/src/format.rs +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv/src/lib.rs +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv-python/Cargo.toml +0 -0
- {commitkv-1.0.1 → commitkv-1.1.1}/rust/crates/commitkv-python/src/lib.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: commitkv
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
License-File: LICENSE
|
|
5
5
|
Summary: Compressed key-value store for git-managed caches: append-only segments that never exceed a size limit
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -11,6 +11,13 @@ Project-URL: Repository, https://github.com/WillBooster/commitkv
|
|
|
11
11
|
|
|
12
12
|
# commitkv
|
|
13
13
|
|
|
14
|
+
[](https://www.npmjs.com/package/commitkv)
|
|
15
|
+
[](https://www.npmjs.com/package/commitkv)
|
|
16
|
+
[](https://github.com/WillBooster/commitkv/actions/workflows/test.yml)
|
|
17
|
+
[](https://github.com/WillBooster/commitkv/actions/workflows/test-rust.yml)
|
|
18
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
19
|
+
[](https://github.com/WillBooster/shared/tree/main/packages/wbfy)
|
|
20
|
+
|
|
14
21
|
A compressed key-value store for caches that are committed to git, usable from Python,
|
|
15
22
|
Node.js, and Bun.
|
|
16
23
|
|
|
@@ -80,6 +87,14 @@ store.refresh(); // pick up what other stores wrote to the directory
|
|
|
80
87
|
writing (the first `put`, and the first after a failed one); records that other stores write
|
|
81
88
|
stay invisible until one of those happens. Call `refresh` after
|
|
82
89
|
replacing segments under an open store, for example by checking out another branch.
|
|
90
|
+
- Stores that write to a directory at the same time each append to a segment of their own, and
|
|
91
|
+
the next run continues at most one of them. Open one store per directory in a process and share
|
|
92
|
+
it; a store per use leaves a small segment behind on every run, and a small segment
|
|
93
|
+
compresses worse because its values have few earlier ones to refer to.
|
|
94
|
+
- `keys` lists the keys in the order their current values lie in the segments, oldest segment
|
|
95
|
+
first. One store's writes are listed in the order it made them; the writes of several stores
|
|
96
|
+
are grouped by segment, so their order does not tell which was made first. Read every value
|
|
97
|
+
in that order: a scattered order decodes the surrounding values again for most of them.
|
|
83
98
|
- Among records of a key that stores wrote without seeing each other's, which one wins is
|
|
84
99
|
unspecified, but every reader picks the same one.
|
|
85
100
|
- `max_segment_bytes` / `maxSegmentBytes` must be between 1,024 and 100,000,000; the upper
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# commitkv
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/commitkv)
|
|
4
|
+
[](https://www.npmjs.com/package/commitkv)
|
|
5
|
+
[](https://github.com/WillBooster/commitkv/actions/workflows/test.yml)
|
|
6
|
+
[](https://github.com/WillBooster/commitkv/actions/workflows/test-rust.yml)
|
|
7
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
8
|
+
[](https://github.com/WillBooster/shared/tree/main/packages/wbfy)
|
|
9
|
+
|
|
3
10
|
A compressed key-value store for caches that are committed to git, usable from Python,
|
|
4
11
|
Node.js, and Bun.
|
|
5
12
|
|
|
@@ -69,6 +76,14 @@ store.refresh(); // pick up what other stores wrote to the directory
|
|
|
69
76
|
writing (the first `put`, and the first after a failed one); records that other stores write
|
|
70
77
|
stay invisible until one of those happens. Call `refresh` after
|
|
71
78
|
replacing segments under an open store, for example by checking out another branch.
|
|
79
|
+
- Stores that write to a directory at the same time each append to a segment of their own, and
|
|
80
|
+
the next run continues at most one of them. Open one store per directory in a process and share
|
|
81
|
+
it; a store per use leaves a small segment behind on every run, and a small segment
|
|
82
|
+
compresses worse because its values have few earlier ones to refer to.
|
|
83
|
+
- `keys` lists the keys in the order their current values lie in the segments, oldest segment
|
|
84
|
+
first. One store's writes are listed in the order it made them; the writes of several stores
|
|
85
|
+
are grouped by segment, so their order does not tell which was made first. Read every value
|
|
86
|
+
in that order: a scattered order decodes the surrounding values again for most of them.
|
|
72
87
|
- Among records of a key that stores wrote without seeing each other's, which one wins is
|
|
73
88
|
unspecified, but every reader picks the same one.
|
|
74
89
|
- `max_segment_bytes` / `maxSegmentBytes` must be between 1,024 and 100,000,000; the upper
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "commitkv"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.1.1"
|
|
4
4
|
description = "Compressed key-value store for git-managed caches: append-only segments that never exceed a size limit"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -11,10 +11,10 @@ Repository = "https://github.com/WillBooster/commitkv"
|
|
|
11
11
|
Issues = "https://github.com/WillBooster/commitkv/issues"
|
|
12
12
|
|
|
13
13
|
[dependency-groups]
|
|
14
|
-
dev = ["
|
|
14
|
+
dev = ["black>=26.10.1", "flake8>=7.4.1", "isort>=9.0.2", "pytest==9.1.1"]
|
|
15
15
|
|
|
16
16
|
[build-system]
|
|
17
|
-
requires = ["maturin
|
|
17
|
+
requires = ["maturin==1.15.0"]
|
|
18
18
|
build-backend = "maturin"
|
|
19
19
|
|
|
20
20
|
[tool.maturin]
|
|
@@ -25,6 +25,9 @@ class Store:
|
|
|
25
25
|
|
|
26
26
|
def __contains__(self, key: str | bytes) -> bool: ...
|
|
27
27
|
def __len__(self) -> int: ...
|
|
28
|
-
def keys(self) -> list[bytes]:
|
|
28
|
+
def keys(self) -> list[bytes]:
|
|
29
|
+
"""Lists the keys in the order their current values lie in the segments, oldest segment
|
|
30
|
+
first, which is the order one store wrote them; reading every value is fastest in this
|
|
31
|
+
order."""
|
|
29
32
|
def refresh(self) -> None:
|
|
30
33
|
"""Picks up records that other stores wrote to the directory."""
|
|
@@ -214,9 +214,14 @@ impl Store {
|
|
|
214
214
|
self.len() == 0
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/// The keys in the order their current records lie in the segments, oldest segment first,
|
|
218
|
+
/// so reading every value in this order decodes each group once. This is the order one
|
|
219
|
+
/// store wrote them; stores that write at the same time fill segments of their own.
|
|
217
220
|
pub fn keys(&self) -> Vec<Vec<u8>> {
|
|
218
221
|
let state = self.read_state();
|
|
219
|
-
state.index.
|
|
222
|
+
let mut entries: Vec<_> = state.index.iter().collect();
|
|
223
|
+
entries.sort_unstable_by_key(|(_, location)| state.position(location));
|
|
224
|
+
entries.into_iter().map(|(key, _)| key.to_vec()).collect()
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
/// Picks up records that other stores wrote to the directory since the last call, and
|
|
@@ -611,6 +616,12 @@ impl State {
|
|
|
611
616
|
index
|
|
612
617
|
}
|
|
613
618
|
|
|
619
|
+
/// Orders records by age: segment names order by creation time and offsets order within a
|
|
620
|
+
/// segment.
|
|
621
|
+
fn position(&self, location: &Location) -> (&str, u64) {
|
|
622
|
+
(&self.segments[location.segment].name, location.end)
|
|
623
|
+
}
|
|
624
|
+
|
|
614
625
|
/// Adds a record that follows the scanned part of its segment; `false` when the record
|
|
615
626
|
/// cannot be placed, which leaves the state unchanged.
|
|
616
627
|
fn apply(&mut self, segment_index: usize, record: &Record) -> bool {
|
|
@@ -648,12 +659,11 @@ impl State {
|
|
|
648
659
|
segment.scanned = location.end;
|
|
649
660
|
segment.group_raw = group_raw_end;
|
|
650
661
|
segment.raw_total = raw_total;
|
|
651
|
-
// The newest record of a key wins
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
(
|
|
655
|
-
|
|
656
|
-
});
|
|
662
|
+
// The newest record of a key wins.
|
|
663
|
+
let newer = self
|
|
664
|
+
.index
|
|
665
|
+
.get(record.key)
|
|
666
|
+
.is_none_or(|current| self.position(current) < self.position(&location));
|
|
657
667
|
if newer {
|
|
658
668
|
self.index.insert(record.key.into(), location);
|
|
659
669
|
}
|
|
@@ -55,6 +55,21 @@ fn values_survive_reopening_and_the_latest_value_of_a_key_wins() {
|
|
|
55
55
|
assert!(keys.binary_search(&b"large".to_vec()).is_ok());
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
#[test]
|
|
59
|
+
fn keys_are_listed_in_the_order_a_store_wrote_their_current_values() {
|
|
60
|
+
let dir = tempfile::tempdir().unwrap();
|
|
61
|
+
{
|
|
62
|
+
let store = Store::open(dir.path(), small_segments()).unwrap();
|
|
63
|
+
for i in 0..400 {
|
|
64
|
+
store.put(&key(i), &noise(i as u64, 1000)).unwrap();
|
|
65
|
+
}
|
|
66
|
+
store.put(&key(3), b"replaced").unwrap();
|
|
67
|
+
}
|
|
68
|
+
assert!(segment_paths(dir.path()).len() > 1);
|
|
69
|
+
let expected: Vec<Vec<u8>> = (0..400).filter(|&i| i != 3).chain([3]).map(key).collect();
|
|
70
|
+
assert_eq!(open(dir.path()).keys(), expected);
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
#[test]
|
|
59
74
|
fn no_segment_exceeds_the_limit() {
|
|
60
75
|
let dir = tempfile::tempdir().unwrap();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|