Skip to content

Commit 6facb61

Browse files
committed
Use Bytes in get_raw/put_raw for zero-copy
1 parent 036fa7a commit 6facb61

5 files changed

Lines changed: 41 additions & 36 deletions

File tree

src/cache/cache.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ use crate::compiler::PreprocessorCacheEntry;
4949
use crate::config::Config;
5050
use crate::config::{self, CacheType, PreprocessorCacheModeConfig};
5151
use async_trait::async_trait;
52+
use bytes::Bytes;
5253

5354
use std::io;
5455
use std::sync::Arc;
@@ -77,14 +78,14 @@ pub trait Storage: Send + Sync {
7778
/// Get raw serialized cache entry bytes by `key` (for multi-level backfill).
7879
/// Returns `None` if the entry is not found, or if the implementation doesn't support raw access.
7980
/// This is used by multi-level caches to backfill faster levels.
80-
async fn get_raw(&self, _key: &str) -> Result<Option<Vec<u8>>> {
81+
async fn get_raw(&self, _key: &str) -> Result<Option<Bytes>> {
8182
Ok(None)
8283
}
8384

8485
/// Put raw serialized cache entry bytes under `key` (for multi-level backfill).
8586
/// Returns an error if the implementation doesn't support raw access.
8687
/// This is used by multi-level caches to backfill faster levels.
87-
async fn put_raw(&self, _key: &str, _data: Vec<u8>) -> Result<Duration> {
88+
async fn put_raw(&self, _key: &str, _data: Bytes) -> Result<Duration> {
8889
Err(anyhow!("put_raw not implemented for this storage backend"))
8990
}
9091

@@ -220,7 +221,7 @@ impl Storage for RemoteStorage {
220221
trace!("RemoteStorage::put({})", key);
221222
// Delegate to put_raw after serializing the entry
222223
let data = entry.finish()?;
223-
self.put_raw(key, data).await
224+
self.put_raw(key, data.into()).await
224225
}
225226

226227
async fn check(&self) -> Result<CacheMode> {
@@ -297,18 +298,17 @@ impl Storage for RemoteStorage {
297298
&self.basedirs
298299
}
299300

300-
/// Get raw bytes from remote storage without any transformations.
301+
/// Get raw bytes from remote storage for multi-level backfill.
301302
///
302-
/// Uses `to_vec()` instead of `to_bytes()` to preserve raw data unchanged.
303-
/// This is critical for multi-level caching: when backfilling from remote to local,
304-
/// we need the exact bytes without OpenDAL layer transformations (e.g., decompression).
305-
/// If compression layers are configured, `to_bytes()` would decompress the data,
306-
/// which would corrupt the cache entry when written to another level.
307-
async fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>> {
303+
/// Unlike `get()` which parses bytes into `CacheRead` (a `ZipArchive<Box<dyn ReadSeek>>`),
304+
/// this returns the raw bytes without parsing. `CacheRead` is a one-way transformation —
305+
/// there is no way to extract the original bytes back from the parsed ZIP archive.
306+
/// For backfill we need the raw bytes to write directly to another cache level.
307+
async fn get_raw(&self, key: &str) -> Result<Option<Bytes>> {
308308
trace!("opendal::Operator::get_raw({})", key);
309309
match self.operator.read(&normalize_key(key)).await {
310310
Ok(res) => {
311-
let data = res.to_vec();
311+
let data = res.to_bytes();
312312
trace!(
313313
"opendal::Operator::get_raw({}): Found {} bytes",
314314
key,
@@ -328,12 +328,12 @@ impl Storage for RemoteStorage {
328328
}
329329
}
330330

331-
/// Write raw bytes to remote storage.
331+
/// Write raw bytes to remote storage for multi-level backfill.
332332
///
333-
/// This is the primitive write operation used by both `put()` and multi-level backfill.
334-
/// For backfill operations, raw bytes are passed directly from one cache level to another
335-
/// to preserve the exact data format (including any compression applied by OpenDAL layers).
336-
async fn put_raw(&self, key: &str, data: Vec<u8>) -> Result<Duration> {
333+
/// Unlike `put()` which takes a `CacheWrite` and serializes it, this writes
334+
/// pre-serialized bytes directly. Paired with `get_raw()` for efficient
335+
/// level-to-level data transfer without a deserialize/reserialize round-trip.
336+
async fn put_raw(&self, key: &str, data: Bytes) -> Result<Duration> {
337337
trace!("opendal::Operator::put_raw({}, {} bytes)", key, data.len());
338338
let start = std::time::Instant::now();
339339

src/cache/disk.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::cache::{Cache, CacheMode, CacheRead, CacheWrite, Storage};
1616
use crate::compiler::PreprocessorCacheEntry;
1717
use crate::lru_disk_cache::{Error as LruError, ReadSeek};
1818
use async_trait::async_trait;
19+
use bytes::Bytes;
1920
use std::ffi::OsStr;
2021
use std::io::{BufWriter, Read, Write};
2122
use std::path::{Path, PathBuf};
@@ -102,7 +103,7 @@ impl Storage for DiskCache {
102103
.await?
103104
}
104105

105-
async fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>> {
106+
async fn get_raw(&self, key: &str) -> Result<Option<Bytes>> {
106107
trace!("DiskCache::get_raw({})", key);
107108
let path = make_key_path(key);
108109
let lru = self.lru.clone();
@@ -115,7 +116,7 @@ impl Storage for DiskCache {
115116
let mut data = Vec::new();
116117
io.read_to_end(&mut data)?;
117118
trace!("DiskCache::get_raw({}): Found {} bytes", key, data.len());
118-
Ok(Some(data))
119+
Ok(Some(Bytes::from(data)))
119120
}
120121
Err(LruError::FileNotInCache) => {
121122
trace!("DiskCache::get_raw({}): FileNotInCache", key);
@@ -135,10 +136,10 @@ impl Storage for DiskCache {
135136
trace!("DiskCache::put({})", key);
136137
// Delegate to put_raw after serializing the entry
137138
let data = entry.finish()?;
138-
self.put_raw(key, data).await
139+
self.put_raw(key, data.into()).await
139140
}
140141

141-
async fn put_raw(&self, key: &str, data: Vec<u8>) -> Result<Duration> {
142+
async fn put_raw(&self, key: &str, data: Bytes) -> Result<Duration> {
142143
trace!("DiskCache::put_raw({}, {} bytes)", key, data.len());
143144

144145
if self.rw_mode == CacheMode::ReadOnly {

src/cache/multilevel.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use bytes::Bytes;
1516
use std::sync::Arc;
1617
use std::sync::atomic::{AtomicU64, Ordering};
1718
use std::time::{Duration, Instant};
@@ -519,23 +520,23 @@ impl MultiLevelStorage {
519520
async fn write_entry_from_bytes(
520521
level: &Arc<dyn Storage>,
521522
key: &str,
522-
data: &Arc<Vec<u8>>,
523+
data: &Bytes,
523524
) -> Result<()> {
524-
// Try to use put_raw for direct bytes write (most efficient)
525-
level.put_raw(key, (**data).clone()).await?;
525+
// Bytes::clone() is a cheap ref-count bump, no data copy
526+
level.put_raw(key, data.clone()).await?;
526527
Ok(())
527528
}
528529

529530
/// Write to levels starting from `start_idx` asynchronously
530-
async fn write_remaining_levels_async(&self, key: &str, data: &Arc<Vec<u8>>, start_idx: usize) {
531+
async fn write_remaining_levels_async(&self, key: &str, data: &Bytes, start_idx: usize) {
531532
for (idx, level) in self.levels.iter().enumerate().skip(start_idx) {
532533
// Check if level is read-only before spawning task
533534
if matches!(level.check().await, Ok(CacheMode::ReadOnly)) {
534535
debug!("Level {} is read-only, skipping write", idx);
535536
continue;
536537
}
537538

538-
let data = Arc::clone(data);
539+
let data = data.clone();
539540
let key = key.to_string();
540541
let level = Arc::clone(level);
541542
let stats_arc = self.atomic_stats.get(idx).map(Arc::clone);
@@ -597,8 +598,6 @@ impl Storage for MultiLevelStorage {
597598
// Try to get raw bytes for backfilling
598599
match level.get_raw(key).await {
599600
Ok(Some(raw_bytes)) => {
600-
let raw_bytes = Arc::new(raw_bytes);
601-
602601
// Update backfill stats
603602
if let Some(stats) = self.atomic_stats.get(hit_level) {
604603
stats
@@ -610,7 +609,7 @@ impl Storage for MultiLevelStorage {
610609
// Iterate slice directly instead of creating Vec
611610
for backfill_idx in 0..idx {
612611
let key_bf = key_str.clone();
613-
let bytes_bf = Arc::clone(&raw_bytes);
612+
let bytes_bf = raw_bytes.clone();
614613
let level_bf = Arc::clone(&self.levels[backfill_idx]);
615614
let stats_arc =
616615
self.atomic_stats.get(backfill_idx).map(Arc::clone);
@@ -694,7 +693,7 @@ impl Storage for MultiLevelStorage {
694693
}
695694

696695
// Serialize cache entry once
697-
let data = Arc::new(entry.finish()?);
696+
let data: Bytes = entry.finish()?.into();
698697
let key_str = key.to_string();
699698

700699
match self.write_policy {
@@ -738,7 +737,7 @@ impl Storage for MultiLevelStorage {
738737
let (tx, mut rx) = mpsc::channel(self.levels.len());
739738

740739
for (idx, level) in self.levels.iter().enumerate() {
741-
let data = Arc::clone(&data);
740+
let data = data.clone();
742741
let key_str = key_str.clone();
743742
let level = Arc::clone(level);
744743
let tx = tx.clone();

src/cache/multilevel_test.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::cache::disk::DiskCache;
1818
use crate::cache::readonly::ReadOnlyStorage;
1919
use crate::config::Config;
2020
use crate::config::PreprocessorCacheModeConfig;
21+
use bytes::Bytes;
2122
use std::collections::HashMap;
2223
use std::env;
2324
use std::fs;
@@ -256,13 +257,16 @@ impl Storage for InMemoryStorage {
256257
/// Implement get_raw() to enable backfill testing with remote-like backends.
257258
/// This simulates the behavior of real remote backends (S3, Redis, etc.) that
258259
/// can efficiently return raw serialized cache entries for backfilling.
259-
async fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>> {
260-
Ok(self.data.lock().await.get(key).cloned())
260+
async fn get_raw(&self, key: &str) -> Result<Option<Bytes>> {
261+
Ok(self.data.lock().await.get(key).cloned().map(Bytes::from))
261262
}
262263

263264
/// Implement put_raw() to enable backfill writes during testing.
264-
async fn put_raw(&self, key: &str, data: Vec<u8>) -> Result<Duration> {
265-
self.data.lock().await.insert(key.to_string(), data);
265+
async fn put_raw(&self, key: &str, data: Bytes) -> Result<Duration> {
266+
self.data
267+
.lock()
268+
.await
269+
.insert(key.to_string(), data.to_vec());
266270
Ok(Duration::ZERO)
267271
}
268272
}
@@ -1087,7 +1091,7 @@ impl Storage for FailingStorage {
10871091
Err(anyhow!("Intentional failure for testing"))
10881092
}
10891093

1090-
async fn put_raw(&self, _key: &str, _entry: Vec<u8>) -> Result<Duration> {
1094+
async fn put_raw(&self, _key: &str, _entry: Bytes) -> Result<Duration> {
10911095
Err(anyhow!("Intentional failure for testing"))
10921096
}
10931097

src/cache/readonly.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::cache::{Cache, CacheMode, CacheWrite, Storage};
1919
use crate::compiler::PreprocessorCacheEntry;
2020
use crate::config::PreprocessorCacheModeConfig;
2121
use crate::errors::*;
22+
use bytes::Bytes;
2223

2324
pub struct ReadOnlyStorage(pub Arc<dyn Storage>);
2425

@@ -95,7 +96,7 @@ impl Storage for ReadOnlyStorage {
9596
}
9697

9798
/// Get raw serialized cache entry bytes (forwarded to inner storage)
98-
async fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>> {
99+
async fn get_raw(&self, key: &str) -> Result<Option<Bytes>> {
99100
self.0.get_raw(key).await
100101
}
101102
}

0 commit comments

Comments
 (0)