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

Commit bbbb6f1

Browse files
authored
Merge pull request #31 from k-jingyang/main
cache file descriptor
2 parents 1075697 + 5091a37 commit bbbb6f1

20 files changed

Lines changed: 271 additions & 52 deletions

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/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/value_log.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
scanner::{Scanner, SizeMap},
1212
segment::merge::MergeReader,
1313
version::Version,
14-
BlobCache, Compressor, Config, GcStrategy, IndexReader, SegmentReader, SegmentWriter,
14+
BlobCache, Compressor, Config, FDCache, GcStrategy, IndexReader, SegmentReader, SegmentWriter,
1515
UserValue, ValueHandle,
1616
};
1717
use std::{
@@ -43,30 +43,35 @@ fn unlink_blob_files(base_path: &Path, ids: &[SegmentId]) {
4343

4444
/// A disk-resident value log
4545
#[derive(Clone)]
46-
pub struct ValueLog<BC: BlobCache, C: Compressor + Clone>(Arc<ValueLogInner<BC, C>>);
46+
pub struct ValueLog<BC: BlobCache, FDC: FDCache, C: Compressor + Clone>(
47+
Arc<ValueLogInner<BC, FDC, C>>,
48+
);
4749

48-
impl<BC: BlobCache, C: Compressor + Clone> std::ops::Deref for ValueLog<BC, C> {
49-
type Target = ValueLogInner<BC, C>;
50+
impl<BC: BlobCache, C: Compressor + Clone, FDC: FDCache> std::ops::Deref for ValueLog<BC, FDC, C> {
51+
type Target = ValueLogInner<BC, FDC, C>;
5052

5153
fn deref(&self) -> &Self::Target {
5254
&self.0
5355
}
5456
}
5557

5658
#[allow(clippy::module_name_repetitions)]
57-
pub struct ValueLogInner<BC: BlobCache, C: Compressor + Clone> {
59+
pub struct ValueLogInner<BC: BlobCache, FDC: FDCache, C: Compressor + Clone> {
5860
/// Unique value log ID
5961
id: u64,
6062

6163
/// Base folder
6264
pub path: PathBuf,
6365

6466
/// Value log configuration
65-
config: Config<BC, C>,
67+
config: Config<BC, FDC, C>,
6668

6769
/// In-memory blob cache
6870
blob_cache: BC,
6971

72+
/// In-memory FD cache
73+
fd_cache: FDC,
74+
7075
/// Segment manifest
7176
#[doc(hidden)]
7277
pub manifest: SegmentManifest<C>,
@@ -80,15 +85,15 @@ pub struct ValueLogInner<BC: BlobCache, C: Compressor + Clone> {
8085
pub rollover_guard: Mutex<()>,
8186
}
8287

83-
impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
88+
impl<BC: BlobCache, C: Compressor + Clone, FDC: FDCache> ValueLog<BC, FDC, C> {
8489
/// Creates or recovers a value log in the given directory.
8590
///
8691
/// # Errors
8792
///
8893
/// Will return `Err` if an IO error occurs.
8994
pub fn open<P: Into<PathBuf>>(
9095
path: P, // TODO: move path into config?
91-
config: Config<BC, C>,
96+
config: Config<BC, FDC, C>,
9297
) -> crate::Result<Self> {
9398
let path = path.into();
9499

@@ -143,7 +148,7 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
143148
/// Creates a new empty value log in a directory.
144149
pub(crate) fn create_new<P: Into<PathBuf>>(
145150
path: P,
146-
config: Config<BC, C>,
151+
config: Config<BC, FDC, C>,
147152
) -> crate::Result<Self> {
148153
let path = absolute_path(path.into());
149154
log::trace!("Creating value-log at {}", path.display());
@@ -174,20 +179,25 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
174179
}
175180

176181
let blob_cache = config.blob_cache.clone();
182+
let fd_cache = config.fd_cache.clone();
177183
let manifest = SegmentManifest::create_new(&path)?;
178184

179185
Ok(Self(Arc::new(ValueLogInner {
180186
id: get_next_vlog_id(),
181187
config,
182188
path,
183189
blob_cache,
190+
fd_cache,
184191
manifest,
185192
id_generator: IdGenerator::default(),
186193
rollover_guard: Mutex::new(()),
187194
})))
188195
}
189196

190-
pub(crate) fn recover<P: Into<PathBuf>>(path: P, config: Config<BC, C>) -> crate::Result<Self> {
197+
pub(crate) fn recover<P: Into<PathBuf>>(
198+
path: P,
199+
config: Config<BC, FDC, C>,
200+
) -> crate::Result<Self> {
191201
let path = path.into();
192202
log::info!("Recovering vLog at {}", path.display());
193203

@@ -204,6 +214,7 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
204214
}
205215

206216
let blob_cache = config.blob_cache.clone();
217+
let fd_cache = config.fd_cache.clone();
207218
let manifest = SegmentManifest::recover(&path)?;
208219

209220
let highest_id = manifest
@@ -220,6 +231,7 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
220231
config,
221232
path,
222233
blob_cache,
234+
fd_cache,
223235
manifest,
224236
id_generator: IdGenerator::new(highest_id + 1),
225237
rollover_guard: Mutex::new(()),
@@ -270,7 +282,11 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
270282
return Ok(None);
271283
};
272284

273-
let mut reader = BufReader::new(File::open(&segment.path)?);
285+
let mut reader = match self.fd_cache.get(self.id, vhandle.segment_id) {
286+
Some(fd) => fd,
287+
None => BufReader::new(File::open(&segment.path)?),
288+
};
289+
274290
reader.seek(std::io::SeekFrom::Start(vhandle.offset))?;
275291
let mut reader = SegmentReader::with_reader(vhandle.segment_id, reader)
276292
.use_compression(self.config.compression.clone());
@@ -302,6 +318,10 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
302318
self.blob_cache.insert(self.id, &value_handle, val);
303319
}
304320

321+
// cache the BufReader for future use, must ensure to always use SeekFrom::Start when using it from cache
322+
self.fd_cache
323+
.insert(self.id, vhandle.segment_id, reader.into_inner());
324+
305325
Ok(Some(val))
306326
}
307327

@@ -499,7 +519,7 @@ impl<BC: BlobCache, C: Compressor + Clone> ValueLog<BC, C> {
499519
/// Will return `Err` if an IO error occurs.
500520
pub fn apply_gc_strategy<R: IndexReader, W: IndexWriter>(
501521
&self,
502-
strategy: &impl GcStrategy<BC, C>,
522+
strategy: &impl GcStrategy<BC, FDC, C>,
503523
index_reader: &R,
504524
index_writer: W,
505525
) -> crate::Result<u64> {

tests/accidental_drop_rc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ fn accidental_drop_rc() -> value_log::Result<()> {
1717

1818
let index = MockIndex::default();
1919

20-
let value_log = ValueLog::open(vl_path, Config::<_, NoCompressor>::new(NoCacher))?;
20+
let value_log = ValueLog::open(
21+
vl_path,
22+
Config::<_, _, NoCompressor>::new(NoCacher, NoCacher),
23+
)?;
2124

2225
for key in ["a", "b"] {
2326
let value = &key;

tests/basic_gc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ fn basic_gc() -> value_log::Result<()> {
1111

1212
let index = MockIndex::default();
1313

14-
let value_log = ValueLog::open(vl_path, Config::<_, NoCompressor>::new(NoCacher))?;
14+
let value_log = ValueLog::open(
15+
vl_path,
16+
Config::<_, _, NoCompressor>::new(NoCacher, NoCacher),
17+
)?;
1518

1619
{
1720
let items = ["a", "b", "c", "d", "e"];

0 commit comments

Comments
 (0)