#[cfg(feature = "cloud-vfs")]
use object_store::{ObjectStore, path::Path as ObjectPath};
use crate::async_io::AsyncVfs;
use crate::storage::{Page, PAGE_SIZE};
use crate::types::{PageId, Result, VelociError};
use async_trait::async_trait;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
#[cfg(feature = "cloud-vfs")]
use bytes::Bytes;
#[cfg(feature = "cloud-vfs")]
pub struct CloudVfs {
store: Arc<dyn ObjectStore>,
db_path: ObjectPath,
cache: Arc<RwLock<HashMap<PageId, Page>>>,
cache_capacity: usize,
num_pages: Arc<RwLock<u64>>,
pending_writes: Arc<RwLock<HashMap<PageId, Page>>>,
}
#[cfg(feature = "cloud-vfs")]
impl CloudVfs {
pub async fn new(
store: Arc<dyn ObjectStore>,
db_path: impl Into<ObjectPath>,
cache_capacity: usize,
) -> Result<Self> {
let db_path = db_path.into();
let num_pages = match store.head(&db_path).await {
Ok(meta) => {
let file_size = meta.size;
(file_size + PAGE_SIZE - 1) / PAGE_SIZE
}
Err(_) => {
0
}
};
Ok(Self {
store,
db_path,
cache: Arc::new(RwLock::new(HashMap::new())),
cache_capacity,
num_pages: Arc::new(RwLock::new(num_pages as u64)),
pending_writes: Arc::new(RwLock::new(HashMap::new())),
})
}
async fn fetch_page_from_remote(&self, page_id: PageId) -> Result<Page> {
let offset = page_id * PAGE_SIZE as u64;
let range = offset..(offset + PAGE_SIZE as u64);
let result = self.store.get_range(&self.db_path, range).await
.map_err(|e| VelociError::IoError(format!("Cloud fetch error: {}", e)))?;
let mut page = Page::new();
let bytes_read = result.len().min(PAGE_SIZE);
page.data_mut()[..bytes_read].copy_from_slice(&result[..bytes_read]);
Ok(page)
}
async fn flush_page_to_remote(&self, page_id: PageId, page: &Page) -> Result<()> {
let offset = page_id * PAGE_SIZE as u64;
let file_data = self.store.get(&self.db_path).await
.map(|r| r.bytes().to_vec())
.unwrap_or_else(|_| Vec::new());
let required_size = (offset + PAGE_SIZE as u64) as usize;
let mut new_data = if file_data.len() < required_size {
let mut v = file_data;
v.resize(required_size, 0);
v
} else {
file_data
};
new_data[offset as usize..(offset as usize + PAGE_SIZE)]
.copy_from_slice(page.data());
let bytes = Bytes::from(new_data);
self.store.put(&self.db_path, bytes).await
.map_err(|e| VelociError::IoError(format!("Cloud write error: {}", e)))?;
Ok(())
}
fn evict_lru(&self) -> Result<()> {
let mut cache = self.cache.write();
if cache.len() >= self.cache_capacity {
if let Some(&page_id) = cache.keys().next() {
cache.remove(&page_id);
}
}
Ok(())
}
}
#[cfg(feature = "cloud-vfs")]
#[async_trait]
impl AsyncVfs for CloudVfs {
async fn read_page(&self, page_id: PageId) -> Result<Page> {
{
let pending = self.pending_writes.read();
if let Some(page) = pending.get(&page_id) {
return Ok(page.clone());
}
}
{
let cache = self.cache.read();
if let Some(page) = cache.get(&page_id) {
return Ok(page.clone());
}
}
let page = self.fetch_page_from_remote(page_id).await?;
self.evict_lru()?;
self.cache.write().insert(page_id, page.clone());
Ok(page)
}
async fn write_page(&self, page_id: PageId, page: &Page) -> Result<()> {
self.pending_writes.write().insert(page_id, page.clone());
self.cache.write().insert(page_id, page.clone());
let mut num_pages = self.num_pages.write();
if page_id >= *num_pages {
*num_pages = page_id + 1;
}
Ok(())
}
async fn allocate_page(&self) -> Result<PageId> {
let mut num_pages = self.num_pages.write();
let page_id = *num_pages;
*num_pages += 1;
let page = Page::new();
drop(num_pages);
self.write_page(page_id, &page).await?;
Ok(page_id)
}
async fn flush(&self) -> Result<()> {
let pending = self.pending_writes.read().clone();
drop(pending);
let mut pending_write = self.pending_writes.write();
for (page_id, page) in pending_write.drain() {
self.flush_page_to_remote(page_id, &page).await?;
}
Ok(())
}
async fn num_pages(&self) -> u64 {
*self.num_pages.read()
}
async fn sync(&self) -> Result<()> {
self.flush().await
}
}
#[cfg(feature = "cloud-vfs")]
pub struct CloudPrefetcher {
vfs: Arc<CloudVfs>,
}
#[cfg(feature = "cloud-vfs")]
impl CloudPrefetcher {
pub fn new(vfs: Arc<CloudVfs>) -> Self {
Self { vfs }
}
pub async fn prefetch_range(&self, start_page: PageId, count: usize) -> Result<()> {
let mut handles = Vec::new();
for i in 0..count {
let page_id = start_page + i as u64;
let vfs = Arc::clone(&self.vfs);
let handle = tokio::spawn(async move {
vfs.read_page(page_id).await
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await;
}
Ok(())
}
pub async fn prefetch_sequential(&self, current_page: PageId, window: usize) -> Result<()> {
self.prefetch_range(current_page + 1, window).await
}
}
#[cfg(feature = "cloud-vfs")]
#[derive(Debug, Clone)]
pub struct CloudVfsConfig {
pub cache_capacity: usize,
pub prefetch_window: usize,
pub write_back_cache: bool,
pub multipart_batch_size: usize,
}
#[cfg(feature = "cloud-vfs")]
impl Default for CloudVfsConfig {
fn default() -> Self {
Self {
cache_capacity: 1024,
prefetch_window: 16,
write_back_cache: true,
multipart_batch_size: 64,
}
}
}
#[cfg(not(feature = "cloud-vfs"))]
pub struct CloudVfs;
#[cfg(not(feature = "cloud-vfs"))]
impl CloudVfs {
pub fn new() -> Self {
Self
}
}
#[cfg(not(feature = "cloud-vfs"))]
pub struct CloudPrefetcher;
#[cfg(not(feature = "cloud-vfs"))]
impl CloudPrefetcher {
pub fn new(_vfs: &CloudVfs) -> Self {
Self
}
}
#[cfg(not(feature = "cloud-vfs"))]
#[derive(Debug, Clone)]
pub struct CloudVfsConfig;
#[cfg(not(feature = "cloud-vfs"))]
impl Default for CloudVfsConfig {
fn default() -> Self {
Self
}
}
#[cfg(test)]
#[cfg(feature = "cloud-vfs")]
mod tests {
use super::*;
#[tokio::test]
async fn test_cloud_vfs_placeholder() {
assert!(true);
}
}