Skip to content
This repository was archived by the owner on Sep 5, 2025. It is now read-only.

Commit 6a180d0

Browse files
authored
Merge pull request #42 from fjall-rs/v2
V2
2 parents 7ac144f + 27d9856 commit 6a180d0

24 files changed

Lines changed: 307 additions & 72 deletions

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ bytes = ["dep:bytes"]
2424
[dependencies]
2525
bytes = { version = "1", optional = true }
2626
byteorder = "1.5.0"
27-
byteview = { version = "0.6.1" }
27+
byteview = { version = "~0.7.0" }
2828
interval-heap = "0.0.5"
2929
log = "0.4.22"
3030
path-absolutize = "3.1.1"

benches/value_log.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use criterion::{criterion_group, criterion_main, Criterion};
22
use rand::{Rng, RngCore};
33
use std::{
44
collections::BTreeMap,
5+
fs::File,
6+
io::BufReader,
57
sync::{Arc, RwLock},
68
};
79
use value_log::{
8-
BlobCache, Compressor, Config, IndexReader, IndexWriter, UserKey, UserValue, ValueHandle,
9-
ValueLog, ValueLogId,
10+
BlobCache, BlobFileId, Compressor, Config, FDCache, IndexReader, IndexWriter, UserKey,
11+
UserValue, ValueHandle, ValueLog, ValueLogId,
1012
};
1113

1214
type MockIndexInner = RwLock<BTreeMap<UserKey, (ValueHandle, u32)>>;
@@ -89,6 +91,13 @@ impl BlobCache for NoCacher {
8991
fn insert(&self, _: ValueLogId, _: &ValueHandle, _: UserValue) {}
9092
}
9193

94+
impl FDCache for NoCacher {
95+
fn get(&self, _: ValueLogId, _: BlobFileId) -> Option<BufReader<File>> {
96+
None
97+
}
98+
fn insert(&self, _: ValueLogId, _: BlobFileId, _: BufReader<File>) {}
99+
}
100+
92101
fn prefetch(c: &mut Criterion) {
93102
let mut group = c.benchmark_group("prefetch range");
94103

@@ -101,7 +110,11 @@ fn prefetch(c: &mut Criterion) {
101110
let folder = tempfile::tempdir().unwrap();
102111
let vl_path = folder.path();
103112

104-
let value_log = ValueLog::open(vl_path, Config::<_, NoCompressor>::new(NoCacher)).unwrap();
113+
let value_log = ValueLog::open(
114+
vl_path,
115+
Config::<_, _, NoCompressor>::new(NoCacher, NoCacher),
116+
)
117+
.unwrap();
105118

106119
let mut writer = value_log.get_writer().unwrap();
107120

@@ -184,7 +197,11 @@ fn load_value(c: &mut Criterion) {
184197
let folder = tempfile::tempdir().unwrap();
185198
let vl_path = folder.path();
186199

187-
let value_log = ValueLog::open(vl_path, Config::<_, NoCompressor>::new(NoCacher)).unwrap();
200+
let value_log = ValueLog::open(
201+
vl_path,
202+
Config::<_, _, NoCompressor>::new(NoCacher, NoCacher),
203+
)
204+
.unwrap();
188205

189206
let mut writer = value_log.get_writer().unwrap();
190207

src/config.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,29 @@
22
// This source code is licensed under both the Apache 2.0 and MIT License
33
// (found in the LICENSE-* files in the repository)
44

5-
use crate::{blob_cache::BlobCache, compression::Compressor};
5+
use crate::{blob_cache::BlobCache, compression::Compressor, fd_cache::BlobFileId, FDCache};
66

77
/// Value log configuration
8-
pub struct Config<BC: BlobCache, C: Compressor + Clone> {
8+
pub struct Config<BC: BlobCache, FDC: FDCache, C: Compressor + Clone> {
99
/// Target size of vLog segments
1010
pub(crate) segment_size_bytes: u64,
1111

1212
/// Blob cache to use
1313
pub(crate) blob_cache: BC,
1414

15+
/// File descriptor cache to use
16+
pub(crate) fd_cache: FDC,
17+
1518
/// Compression to use
1619
pub(crate) compression: Option<C>,
1720
}
1821

19-
impl<BC: BlobCache, C: Compressor + Clone + Default> Config<BC, C> {
22+
impl<BC: BlobCache, FDC: FDCache, C: Compressor + Clone + Default> Config<BC, FDC, C> {
2023
/// Creates a new configuration builder.
21-
pub fn new(blob_cache: BC) -> Self {
24+
pub fn new(blob_cache: BC, fd_cache: FDC) -> Self {
2225
Self {
2326
blob_cache,
27+
fd_cache,
2428
compression: None,
2529
segment_size_bytes: 128 * 1_024 * 1_024,
2630
}

src/fd_cache.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) 2024-present, fjall-rs
2+
// This source code is licensed under both the Apache 2.0 and MIT License
3+
// (found in the LICENSE-* files in the repository)
4+
5+
use crate::ValueLogId;
6+
use std::{fs::File, io::BufReader};
7+
8+
/// The unique identifier for a value log blob file. Another name for SegmentId
9+
pub type BlobFileId = u64;
10+
11+
/// File descriptor cache, to cache file descriptors after an fopen().
12+
/// Reduces the number of fopen() needed when accessing the same blob file.
13+
pub trait FDCache: Clone {
14+
/// Caches a file descriptor
15+
fn insert(&self, vlog_id: ValueLogId, blob_file_id: BlobFileId, fd: BufReader<File>);
16+
17+
/// Retrieves a file descriptor from the cache, or `None` if it could not be found
18+
fn get(&self, vlog_id: ValueLogId, blob_file_id: BlobFileId) -> Option<BufReader<File>>;
19+
}

src/gc/mod.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
pub mod report;
66

7-
use crate::{id::SegmentId, BlobCache, Compressor, ValueLog};
7+
use crate::{id::SegmentId, BlobCache, Compressor, FDCache, ValueLog};
88

99
/// GC strategy
1010
#[allow(clippy::module_name_repetitions)]
11-
pub trait GcStrategy<BC: BlobCache, C: Compressor + Clone> {
11+
pub trait GcStrategy<BC: BlobCache, FD: FDCache, C: Compressor + Clone> {
1212
/// Picks segments based on a predicate.
13-
fn pick(&self, value_log: &ValueLog<BC, C>) -> Vec<SegmentId>;
13+
fn pick(&self, value_log: &ValueLog<BC, FD, C>) -> Vec<SegmentId>;
1414
}
1515

1616
/// Picks segments that have a certain percentage of stale blobs
@@ -32,8 +32,10 @@ impl StaleThresholdStrategy {
3232
}
3333
}
3434

35-
impl<BC: BlobCache, C: Compressor + Clone> GcStrategy<BC, C> for StaleThresholdStrategy {
36-
fn pick(&self, value_log: &ValueLog<BC, C>) -> Vec<SegmentId> {
35+
impl<BC: BlobCache, FDC: FDCache, C: Compressor + Clone> GcStrategy<BC, FDC, C>
36+
for StaleThresholdStrategy
37+
{
38+
fn pick(&self, value_log: &ValueLog<BC, FDC, C>) -> Vec<SegmentId> {
3739
value_log
3840
.manifest
3941
.segments
@@ -62,9 +64,11 @@ impl SpaceAmpStrategy {
6264
}
6365
}
6466

65-
impl<BC: BlobCache, C: Compressor + Clone> GcStrategy<BC, C> for SpaceAmpStrategy {
67+
impl<BC: BlobCache, FDC: FDCache, C: Compressor + Clone> GcStrategy<BC, FDC, C>
68+
for SpaceAmpStrategy
69+
{
6670
#[allow(clippy::cast_precision_loss, clippy::significant_drop_tightening)]
67-
fn pick(&self, value_log: &ValueLog<BC, C>) -> Vec<SegmentId> {
71+
fn pick(&self, value_log: &ValueLog<BC, FDC, C>) -> Vec<SegmentId> {
6872
let space_amp_target = self.0;
6973
let current_space_amp = value_log.space_amp();
7074

src/key_range.rs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl KeyRange {
9696

9797
/// Returns `true` if the ranges overlap partially or fully.
9898
#[must_use]
99-
pub fn overlaps_with_bounds(&self, bounds: &(Bound<UserKey>, Bound<UserKey>)) -> bool {
99+
pub fn overlaps_with_bounds(&self, bounds: &(Bound<&[u8]>, Bound<&[u8]>)) -> bool {
100100
let (lo, hi) = bounds;
101101
let (my_lo, my_hi) = self.as_tuple();
102102

@@ -294,30 +294,21 @@ mod tests {
294294
#[test]
295295
fn inclusive() {
296296
let key_range = KeyRange(UserKey::from("key1"), UserKey::from("key5"));
297-
let bounds = (
298-
Included(UserKey::from("key1")),
299-
Included(UserKey::from("key5")),
300-
);
297+
let bounds = (Included(b"key1" as &[u8]), Included(b"key5" as &[u8]));
301298
assert!(key_range.overlaps_with_bounds(&bounds));
302299
}
303300

304301
#[test]
305302
fn exclusive() {
306303
let key_range = KeyRange(UserKey::from("key1"), UserKey::from("key5"));
307-
let bounds = (
308-
Excluded(UserKey::from("key0")),
309-
Excluded(UserKey::from("key6")),
310-
);
304+
let bounds = (Excluded(b"key0" as &[u8]), Excluded(b"key6" as &[u8]));
311305
assert!(key_range.overlaps_with_bounds(&bounds));
312306
}
313307

314308
#[test]
315309
fn no_overlap() {
316310
let key_range = KeyRange(UserKey::from("key1"), UserKey::from("key5"));
317-
let bounds = (
318-
Excluded(UserKey::from("key5")),
319-
Excluded(UserKey::from("key6")),
320-
);
311+
let bounds = (Excluded(b"key5" as &[u8]), Excluded(b"key6" as &[u8]));
321312
assert!(!key_range.overlaps_with_bounds(&bounds));
322313
}
323314

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#![cfg_attr(not(feature = "bytes"), forbid(unsafe_code))]
5252

5353
mod blob_cache;
54+
mod fd_cache;
5455

5556
#[doc(hidden)]
5657
pub mod coding;
@@ -82,6 +83,7 @@ pub use {
8283
compression::Compressor,
8384
config::Config,
8485
error::{Error, Result},
86+
fd_cache::{BlobFileId, FDCache},
8587
gc::report::GcReport,
8688
gc::{GcStrategy, SpaceAmpStrategy, StaleThresholdStrategy},
8789
handle::ValueHandle,

src/segment/reader.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ impl<C: Compressor + Clone> Reader<C> {
5959
self.compression = compressor;
6060
self
6161
}
62+
63+
pub(crate) fn into_inner(self) -> BufReader<File> {
64+
self.inner
65+
}
6266
}
6367

6468
impl<C: Compressor + Clone> Iterator for Reader<C> {

src/slice/slice_bytes.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ impl Slice {
2323
Self(Bytes::from_static(&[]))
2424
}
2525

26+
#[must_use]
27+
#[doc(hidden)]
28+
pub fn with_size(len: usize) -> Self {
29+
let bytes = vec![0; len];
30+
Self(Bytes::from(bytes))
31+
}
32+
33+
#[must_use]
34+
#[doc(hidden)]
35+
pub fn with_size_unzeroed(len: usize) -> Self {
36+
Self(Self::get_unzeroed_builder(len).freeze())
37+
}
38+
2639
fn get_unzeroed_builder(len: usize) -> BytesMut {
2740
// Use `with_capacity` & `set_len`` to avoid zeroing the buffer
2841
let mut builder = BytesMut::with_capacity(len);
@@ -63,9 +76,10 @@ impl Slice {
6376

6477
#[must_use]
6578
#[doc(hidden)]
66-
pub fn with_size(len: usize) -> Self {
67-
let bytes = vec![0; len];
68-
Self(Bytes::from(bytes))
79+
pub fn get_mut(&mut self) -> Option<impl std::ops::DerefMut<Target = [u8]> + '_> {
80+
todo!();
81+
82+
Option::<&mut [u8]>::None
6983
}
7084

7185
/// Constructs a [`Slice`] from an I/O reader by pulling in `len` bytes.

src/slice/slice_default.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ impl Slice {
2323
Self(ByteView::new(&[]))
2424
}
2525

26+
#[must_use]
27+
#[doc(hidden)]
28+
pub fn with_size(len: usize) -> Self {
29+
Self(ByteView::with_size(len))
30+
}
31+
32+
#[must_use]
33+
#[doc(hidden)]
34+
pub fn with_size_unzeroed(len: usize) -> Self {
35+
Self(ByteView::with_size_unzeroed(len))
36+
}
37+
2638
#[doc(hidden)]
2739
#[must_use]
2840
pub fn slice(&self, range: impl std::ops::RangeBounds<usize>) -> Self {
@@ -35,11 +47,10 @@ impl Slice {
3547
Self(ByteView::fused(left, right))
3648
}
3749

38-
// TODO: change to unzeroed and provide a _zeroed method instead
3950
#[must_use]
4051
#[doc(hidden)]
41-
pub fn with_size(len: usize) -> Self {
42-
Self(ByteView::with_size(len))
52+
pub fn get_mut(&mut self) -> Option<impl std::ops::DerefMut<Target = [u8]> + '_> {
53+
self.0.get_mut()
4354
}
4455

4556
/// Constructs a [`Slice`] from an I/O reader by pulling in `len` bytes.

0 commit comments

Comments
 (0)