Skip to content

Commit 8300d64

Browse files
Introduce the caching of contains_key operations. (#3599)
## Motivation The LRU caching is very minimal and can be extended. Here we extend the caching to caching the absence/existence of values. ## Proposal The following was done: * The `Option<Vec<u8>>` is replaced by a `CacheEntry` that encodes the status if missing. * The lifetime is removed from the `fn query`, but that does not matter since it was cloned just after. * The cache success/fault is split with read_value/contains_key. ## Test Plan The CI. ## Release Plan Normal release plan. ## Links None.
1 parent 94b1399 commit 8300d64

File tree

1 file changed

+127
-38
lines changed

1 file changed

+127
-38
lines changed

linera-views/src/backends/lru_caching.rs

Lines changed: 127 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,64 @@ use crate::{
2727
use crate::{memory::MemoryStore, store::TestKeyValueStore};
2828

2929
#[cfg(with_metrics)]
30-
/// The total number of cache faults
31-
static NUM_CACHE_FAULT: LazyLock<IntCounterVec> =
32-
LazyLock::new(|| register_int_counter_vec("num_cache_fault", "Number of cache faults", &[]));
30+
/// The total number of cache read value misses
31+
static READ_VALUE_CACHE_MISS_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
32+
register_int_counter_vec(
33+
"num_read_value_cache_miss",
34+
"Number of read value cache misses",
35+
&[],
36+
)
37+
});
3338

3439
#[cfg(with_metrics)]
35-
/// The total number of cache successes
36-
static NUM_CACHE_SUCCESS: LazyLock<IntCounterVec> =
37-
LazyLock::new(|| register_int_counter_vec("num_cache_success", "Number of cache success", &[]));
40+
/// The total number of read value cache hits
41+
static READ_VALUE_CACHE_HIT_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
42+
register_int_counter_vec(
43+
"num_read_value_cache_hits",
44+
"Number of read value cache hits",
45+
&[],
46+
)
47+
});
48+
49+
#[cfg(with_metrics)]
50+
/// The total number of contains key cache misses
51+
static CONTAINS_KEY_CACHE_MISS_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
52+
register_int_counter_vec(
53+
"num_contains_key_cache_miss",
54+
"Number of contains key cache misses",
55+
&[],
56+
)
57+
});
58+
59+
#[cfg(with_metrics)]
60+
/// The total number of contains key cache hits
61+
static CONTAINS_KEY_CACHE_HIT_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
62+
register_int_counter_vec(
63+
"num_contains_key_cache_hit",
64+
"Number of contains key cache hits",
65+
&[],
66+
)
67+
});
68+
69+
enum CacheEntry {
70+
DoesNotExist,
71+
Exists,
72+
Value(Vec<u8>),
73+
}
3874

3975
/// Stores the data for simple `read_values` queries.
4076
///
4177
/// This data structure is inspired by the crate `lru-cache` but was modified to support
4278
/// range deletions.
4379
struct LruPrefixCache {
44-
map: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
80+
map: BTreeMap<Vec<u8>, CacheEntry>,
4581
queue: LinkedHashMap<Vec<u8>, (), RandomState>,
4682
max_cache_size: usize,
4783
/// Whether we have exclusive R/W access to the keys under the root key of the store.
4884
has_exclusive_access: bool,
4985
}
5086

51-
impl<'a> LruPrefixCache {
87+
impl LruPrefixCache {
5288
/// Creates an `LruPrefixCache`.
5389
pub fn new(max_cache_size: usize) -> Self {
5490
Self {
@@ -60,39 +96,57 @@ impl<'a> LruPrefixCache {
6096
}
6197

6298
/// Inserts an entry into the cache.
63-
pub fn insert(&mut self, key: Vec<u8>, value: Option<Vec<u8>>) {
64-
if value.is_none() && !self.has_exclusive_access {
99+
pub fn insert(&mut self, key: Vec<u8>, cache_entry: CacheEntry) {
100+
if matches!(cache_entry, CacheEntry::DoesNotExist) && !self.has_exclusive_access {
65101
// Just forget about the entry.
66102
self.map.remove(&key);
67103
self.queue.remove(&key);
68104
return;
69105
}
70106
match self.map.entry(key.clone()) {
71107
btree_map::Entry::Occupied(mut entry) => {
72-
entry.insert(value);
108+
entry.insert(cache_entry);
73109
// Put it on first position for LRU
74110
self.queue.remove(&key);
75111
self.queue.insert(key, ());
76112
}
77113
btree_map::Entry::Vacant(entry) => {
78-
entry.insert(value);
114+
entry.insert(cache_entry);
79115
self.queue.insert(key, ());
80116
if self.queue.len() > self.max_cache_size {
81-
let Some(value) = self.queue.pop_front() else {
117+
let Some(key) = self.queue.pop_front() else {
82118
unreachable!()
83119
};
84-
self.map.remove(&value.0);
120+
self.map.remove(&key.0);
85121
}
86122
}
87123
}
88124
}
89125

126+
/// Inserts a read_value entry into the cache.
127+
pub fn insert_read_value(&mut self, key: Vec<u8>, value: &Option<Vec<u8>>) {
128+
let cache_entry = match value {
129+
None => CacheEntry::DoesNotExist,
130+
Some(vec) => CacheEntry::Value(vec.to_vec()),
131+
};
132+
self.insert(key, cache_entry)
133+
}
134+
135+
/// Inserts a read_value entry into the cache.
136+
pub fn insert_contains_key(&mut self, key: Vec<u8>, result: bool) {
137+
let cache_entry = match result {
138+
false => CacheEntry::DoesNotExist,
139+
true => CacheEntry::Exists,
140+
};
141+
self.insert(key, cache_entry)
142+
}
143+
90144
/// Marks cached keys that match the prefix as deleted. Importantly, this does not
91145
/// create new entries in the cache.
92146
pub fn delete_prefix(&mut self, key_prefix: &[u8]) {
93147
if self.has_exclusive_access {
94148
for (_, value) in self.map.range_mut(get_interval(key_prefix.to_vec())) {
95-
*value = None;
149+
*value = CacheEntry::DoesNotExist;
96150
}
97151
} else {
98152
// Just forget about the entries.
@@ -107,9 +161,26 @@ impl<'a> LruPrefixCache {
107161
}
108162
}
109163

110-
/// Gets the entry from the key.
111-
pub fn query(&'a self, key: &'a [u8]) -> Option<&'a Option<Vec<u8>>> {
112-
self.map.get(key)
164+
/// Returns the cached value, or `Some(None)` if the entry does not exist in the
165+
/// database. If `None` is returned, the entry might exist in the database but is
166+
/// not in the cache.
167+
pub fn query_read_value(&self, key: &[u8]) -> Option<Option<Vec<u8>>> {
168+
match self.map.get(key) {
169+
None => None,
170+
Some(entry) => match entry {
171+
CacheEntry::DoesNotExist => Some(None),
172+
CacheEntry::Exists => None,
173+
CacheEntry::Value(vec) => Some(Some(vec.clone())),
174+
},
175+
}
176+
}
177+
178+
/// Returns `Some(true)` or `Some(false)` if we know that the entry does or does not
179+
/// exist in the database. Returns `None` if that information is not in the cache.
180+
pub fn query_contains_key(&self, key: &[u8]) -> Option<bool> {
181+
self.map
182+
.get(key)
183+
.map(|entry| !matches!(entry, CacheEntry::DoesNotExist))
113184
}
114185
}
115186

@@ -149,28 +220,38 @@ where
149220
// First inquiring in the read_value_bytes LRU
150221
{
151222
let cache = cache.lock().unwrap();
152-
if let Some(value) = cache.query(key) {
223+
if let Some(value) = cache.query_read_value(key) {
153224
#[cfg(with_metrics)]
154-
NUM_CACHE_SUCCESS.with_label_values(&[]).inc();
155-
return Ok(value.clone());
225+
READ_VALUE_CACHE_HIT_COUNT.with_label_values(&[]).inc();
226+
return Ok(value);
156227
}
157228
}
158229
#[cfg(with_metrics)]
159-
NUM_CACHE_FAULT.with_label_values(&[]).inc();
230+
READ_VALUE_CACHE_MISS_COUNT.with_label_values(&[]).inc();
160231
let value = self.store.read_value_bytes(key).await?;
161232
let mut cache = cache.lock().unwrap();
162-
cache.insert(key.to_vec(), value.clone());
233+
cache.insert_read_value(key.to_vec(), &value);
163234
Ok(value)
164235
}
165236

166237
async fn contains_key(&self, key: &[u8]) -> Result<bool, Self::Error> {
167-
if let Some(cache) = &self.cache {
238+
let Some(cache) = &self.cache else {
239+
return self.store.contains_key(key).await;
240+
};
241+
{
168242
let cache = cache.lock().unwrap();
169-
if let Some(value) = cache.query(key) {
170-
return Ok(value.is_some());
243+
if let Some(value) = cache.query_contains_key(key) {
244+
#[cfg(with_metrics)]
245+
CONTAINS_KEY_CACHE_HIT_COUNT.with_label_values(&[]).inc();
246+
return Ok(value);
171247
}
172248
}
173-
self.store.contains_key(key).await
249+
#[cfg(with_metrics)]
250+
CONTAINS_KEY_CACHE_MISS_COUNT.with_label_values(&[]).inc();
251+
let result = self.store.contains_key(key).await?;
252+
let mut cache = cache.lock().unwrap();
253+
cache.insert_contains_key(key.to_vec(), result);
254+
Ok(result)
174255
}
175256

176257
async fn contains_keys(&self, keys: Vec<Vec<u8>>) -> Result<Vec<bool>, Self::Error> {
@@ -184,18 +265,24 @@ where
184265
{
185266
let cache = cache.lock().unwrap();
186267
for i in 0..size {
187-
if let Some(value) = cache.query(&keys[i]) {
188-
results[i] = value.is_some();
268+
if let Some(value) = cache.query_contains_key(&keys[i]) {
269+
#[cfg(with_metrics)]
270+
CONTAINS_KEY_CACHE_HIT_COUNT.with_label_values(&[]).inc();
271+
results[i] = value;
189272
} else {
273+
#[cfg(with_metrics)]
274+
CONTAINS_KEY_CACHE_MISS_COUNT.with_label_values(&[]).inc();
190275
indices.push(i);
191276
key_requests.push(keys[i].clone());
192277
}
193278
}
194279
}
195280
if !key_requests.is_empty() {
196-
let key_results = self.store.contains_keys(key_requests).await?;
197-
for (index, result) in indices.into_iter().zip(key_results) {
281+
let key_results = self.store.contains_keys(key_requests.clone()).await?;
282+
let mut cache = cache.lock().unwrap();
283+
for ((index, result), key) in indices.into_iter().zip(key_results).zip(key_requests) {
198284
results[index] = result;
285+
cache.insert_contains_key(key, result);
199286
}
200287
}
201288
Ok(results)
@@ -215,13 +302,13 @@ where
215302
{
216303
let cache = cache.lock().unwrap();
217304
for (i, key) in keys.into_iter().enumerate() {
218-
if let Some(value) = cache.query(&key) {
305+
if let Some(value) = cache.query_read_value(&key) {
219306
#[cfg(with_metrics)]
220-
NUM_CACHE_SUCCESS.with_label_values(&[]).inc();
221-
result.push(value.clone());
307+
READ_VALUE_CACHE_HIT_COUNT.with_label_values(&[]).inc();
308+
result.push(value);
222309
} else {
223310
#[cfg(with_metrics)]
224-
NUM_CACHE_FAULT.with_label_values(&[]).inc();
311+
READ_VALUE_CACHE_MISS_COUNT.with_label_values(&[]).inc();
225312
result.push(None);
226313
cache_miss_indices.push(i);
227314
miss_keys.push(key);
@@ -238,7 +325,7 @@ where
238325
.into_iter()
239326
.zip(miss_keys.into_iter().zip(values))
240327
{
241-
cache.insert(key, value.clone());
328+
cache.insert_read_value(key, &value);
242329
result[i] = value;
243330
}
244331
}
@@ -274,10 +361,12 @@ where
274361
for operation in &batch.operations {
275362
match operation {
276363
WriteOperation::Put { key, value } => {
277-
cache.insert(key.to_vec(), Some(value.to_vec()));
364+
let cache_entry = CacheEntry::Value(value.to_vec());
365+
cache.insert(key.to_vec(), cache_entry);
278366
}
279367
WriteOperation::Delete { key } => {
280-
cache.insert(key.to_vec(), None);
368+
let cache_entry = CacheEntry::DoesNotExist;
369+
cache.insert(key.to_vec(), cache_entry);
281370
}
282371
WriteOperation::DeletePrefix { key_prefix } => {
283372
cache.delete_prefix(key_prefix);

0 commit comments

Comments
 (0)