Skip to content

Commit bfc99ce

Browse files
markbtfacebook-github-bot
authored andcommitted
implement bincode-based cache
Summary: Implement a variant of `cachelib::abomonation_cache` that uses `bincode2` instead of `abomonation`. This switches away from the potentially unsound abomonation serialization to something that is sound and stable. Microbenchmarks show that the serialization/deserialization performation is only reduced by between 2% and 7%. Reviewed By: RajivTS Differential Revision: D72465917 fbshipit-source-id: bb871e976b456f9798534ff60abf2ac2a6a29351
1 parent e20e2c7 commit bfc99ce

3 files changed

Lines changed: 208 additions & 0 deletions

File tree

cachelib/rust/src/bincode_cache.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
use std::io::Read;
18+
use std::io::Write;
19+
use std::time::Duration;
20+
21+
use anyhow::Error;
22+
use anyhow::Result;
23+
use bytes::Buf;
24+
25+
use crate::lrucache::VolatileLruCachePool;
26+
27+
impl<'a> bincode::de::read::Reader for crate::lrucache::LruCacheHandleReader<'a> {
28+
fn read(&mut self, bytes: &mut [u8]) -> Result<(), bincode::error::DecodeError> {
29+
self.read_exact(bytes)
30+
.map_err(|inner| bincode::error::DecodeError::Io {
31+
inner,
32+
additional: bytes.len() - self.remaining(),
33+
})?;
34+
Ok(())
35+
}
36+
37+
fn peek_read(&mut self, cnt: usize) -> Option<&[u8]> {
38+
self.peek(cnt)
39+
}
40+
fn consume(&mut self, cnt: usize) {
41+
self.advance(cnt);
42+
}
43+
}
44+
45+
impl<'a> bincode::enc::write::Writer for crate::lrucache::LruCacheHandleWriter<'a> {
46+
fn write(&mut self, bytes: &[u8]) -> Result<(), bincode::error::EncodeError> {
47+
self.write_all(bytes)
48+
.map_err(|inner| bincode::error::EncodeError::Io {
49+
inner,
50+
index: self.position(),
51+
})?;
52+
Ok(())
53+
}
54+
}
55+
56+
const CONFIG: bincode::config::Configuration<
57+
bincode::config::LittleEndian,
58+
bincode::config::Fixint,
59+
> = bincode::config::standard().with_fixed_int_encoding();
60+
61+
/// Utility function for in-memory caching via bincode and a volatile pool
62+
/// Will attempt to fetch and decode a cached item
63+
pub fn get_cached<T>(cache_pool: &VolatileLruCachePool, cache_key: &str) -> Result<Option<T>>
64+
where
65+
T: bincode::Decode<()> + Clone + Send + 'static,
66+
{
67+
if let Some(cache_handle) = cache_pool.get_handle(cache_key)? {
68+
let reader = cache_handle.get_reader()?;
69+
let buffer_size = reader.remaining();
70+
let (obj, size) = bincode::decode_from_slice(reader.buffer(), CONFIG)?;
71+
anyhow::ensure!(
72+
size == buffer_size,
73+
"Buffer underrun decoding cache item '{cache_key}': {size} < {buffer_size}"
74+
);
75+
Ok(Some(obj))
76+
} else {
77+
Ok(None)
78+
}
79+
}
80+
81+
struct SizeOnlyWriter<'a> {
82+
bytes_written: &'a mut usize,
83+
}
84+
85+
impl<'a> bincode::enc::write::Writer for SizeOnlyWriter<'a> {
86+
fn write(&mut self, bytes: &[u8]) -> Result<(), bincode::error::EncodeError> {
87+
*self.bytes_written += bytes.len();
88+
Ok(())
89+
}
90+
}
91+
92+
/// Compute the encoded size of an entry.
93+
fn encoded_size<T, C>(entry: &T, config: C) -> Result<usize, bincode::error::EncodeError>
94+
where
95+
T: bincode::Encode,
96+
C: bincode::config::Config,
97+
{
98+
let mut size = 0usize;
99+
let writer = SizeOnlyWriter {
100+
bytes_written: &mut size,
101+
};
102+
let mut ei = bincode::enc::EncoderImpl::new(writer, config);
103+
entry.encode(&mut ei)?;
104+
Ok(size)
105+
}
106+
107+
/// Encodes an item via bincode, and then inserts it into a volatile pool
108+
///
109+
/// Returns `false` if the entry could not be inserted (e.g. another entry with the same
110+
/// key was inserted first)
111+
pub fn set_cached<T>(
112+
cache_pool: &VolatileLruCachePool,
113+
cache_key: &str,
114+
entry: &T,
115+
ttl: Option<Duration>,
116+
) -> Result<bool>
117+
where
118+
T: bincode::Encode + Clone + Send + 'static,
119+
{
120+
let size = encoded_size(entry, CONFIG)?;
121+
let handle = cache_pool.allocate_with_ttl(cache_key, size, ttl)?;
122+
let mut handle = handle.ok_or_else(|| Error::msg("cannot allocate cachelib handle"))?;
123+
let mut writer = handle.get_writer()?;
124+
let written = bincode::encode_into_slice(entry, writer.buffer(), CONFIG)?;
125+
anyhow::ensure!(
126+
written == size,
127+
"Buffer underrun encoding cache item '{cache_key}': {written} < {size}"
128+
);
129+
cache_pool.insert_handle(handle)
130+
}
131+
132+
#[cfg(test)]
133+
mod test {
134+
use fbinit::FacebookInit;
135+
136+
use super::*;
137+
use crate::lrucache::*;
138+
139+
fn create_cache(fb: FacebookInit) {
140+
let config = LruCacheConfig::new(16 * 1024 * 1024);
141+
142+
if let Err(e) = init_cache(fb, config) {
143+
panic!("{}", e);
144+
}
145+
}
146+
147+
#[fbinit::test]
148+
fn get_repeatedly(fb: FacebookInit) {
149+
create_cache(fb);
150+
151+
let pool = get_or_create_volatile_pool("get_twice", 4 * 1024 * 1024).unwrap();
152+
153+
assert!(set_cached(&pool, "key", &"hello_world".to_string(), None).unwrap());
154+
assert_eq!(
155+
get_cached::<String>(&pool, "key").unwrap().unwrap(),
156+
"hello_world"
157+
);
158+
159+
assert!(
160+
!set_cached(
161+
&pool,
162+
"key",
163+
&"goodbye world".to_string(),
164+
Some(Duration::from_secs(100))
165+
)
166+
.unwrap()
167+
);
168+
assert_eq!(
169+
get_cached::<String>(&pool, "key").unwrap().unwrap(),
170+
"hello_world"
171+
);
172+
173+
assert!(set_cached(&pool, "key2", &"hello_world2".to_string(), None).unwrap());
174+
assert_eq!(
175+
get_cached::<String>(&pool, "key2").unwrap().unwrap(),
176+
"hello_world2"
177+
);
178+
}
179+
}

cachelib/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
mod abomonation_cache;
18+
pub mod bincode_cache;
1819
mod errors;
1920
mod lrucache;
2021

cachelib/rust/src/lrucache.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,23 @@ pub struct LruCacheHandleReader<'a> {
634634
buffer: Cursor<&'a [u8]>,
635635
}
636636

637+
impl<'a> LruCacheHandleReader<'a> {
638+
pub fn peek(&mut self, cnt: usize) -> Option<&[u8]> {
639+
let start = self.buffer.position() as usize;
640+
let data = self.buffer.get_ref();
641+
if start + cnt < data.len() {
642+
Some(&data[start..start + cnt])
643+
} else {
644+
None
645+
}
646+
}
647+
648+
/// Get direct access to the underlying buffer.
649+
pub fn buffer(&self) -> &[u8] {
650+
self.buffer.get_ref()
651+
}
652+
}
653+
637654
impl<'a> Buf for LruCacheHandleReader<'a> {
638655
fn remaining(&self) -> usize {
639656
self.buffer.remaining()
@@ -664,6 +681,17 @@ pub struct LruCacheHandleWriter<'a> {
664681
buffer: Cursor<&'a mut [u8]>,
665682
}
666683

684+
impl<'a> LruCacheHandleWriter<'a> {
685+
pub fn position(&self) -> usize {
686+
self.buffer.position() as usize
687+
}
688+
689+
/// Get direct access to the underlying buffer.
690+
pub fn buffer(&mut self) -> &mut [u8] {
691+
self.buffer.get_mut()
692+
}
693+
}
694+
667695
// SAFETY: Only calls to advance_mut modify the current position.
668696
unsafe impl<'a> BufMut for LruCacheHandleWriter<'a> {
669697
#[inline]

0 commit comments

Comments
 (0)