refactor(cache): redesign cache system with structured keys and improved performance
- Add repo_path parameter to cached_response and cached_vec_response functions - Implement structured cache key format with namespace, repo_path, and request proto - Replace global cache with Moka in-memory cache using weight-based eviction - Set 256MB memory cap with 10-minute TTL and 2-minute TTI policy - Add metrics collection for cache operations and evictions - Implement efficient repo-scoped invalidation using key structure - Add detailed documentation comments explaining cache architecture - Remove outdated dependencies and update dependency versions - Add error handling for encoding failures in cache operations - Optimize Vec responses with length-delimited encoding and pre-allocation
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
pub mod ops;
|
||||
pub mod storage;
|
||||
pub mod sync;
|
||||
|
||||
pub use ops::{create_snapshot, restore_snapshot, verify_snapshot};
|
||||
pub use storage::{LocalSnapshotStorage, SnapshotInfo, SnapshotStorageBackend};
|
||||
|
||||
+2
-2
@@ -81,7 +81,7 @@ pub fn create_and_store_snapshot(
|
||||
pub fn restore_snapshot(repo_path: &Path, data: &[u8]) -> GitResult<()> {
|
||||
tracing::info!(path = %repo_path.display(), size_bytes = data.len(), "restoring snapshot");
|
||||
|
||||
let applicator = crate::actor::sync::BundleApplicator::new(repo_path.to_path_buf());
|
||||
let applicator = crate::snapshot::sync::BundleApplicator::new(repo_path.to_path_buf());
|
||||
applicator.apply_bundle(data).map_err(GitError::Internal)?;
|
||||
|
||||
tracing::info!(path = %repo_path.display(), "snapshot restored");
|
||||
@@ -163,7 +163,7 @@ fn generate_snapshot_id(relative_path: &str, head_oid: &str) -> String {
|
||||
let mut s = String::with_capacity(16);
|
||||
for byte in &hash[..8] {
|
||||
use std::fmt::Write;
|
||||
write!(s, "{byte:02x}").unwrap();
|
||||
let _ = write!(s, "{byte:02x}");
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
+18
-1
@@ -40,8 +40,22 @@ impl LocalSnapshotStorage {
|
||||
Self { base_dir }
|
||||
}
|
||||
|
||||
fn validate_snapshot_id(snapshot_id: &str) -> Result<(), String> {
|
||||
if snapshot_id.is_empty() {
|
||||
return Err("snapshot_id cannot be empty".into());
|
||||
}
|
||||
if snapshot_id.len() > 64
|
||||
|| !snapshot_id
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
||||
{
|
||||
return Err(format!("invalid snapshot_id: {snapshot_id}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn snapshot_dir(&self, snapshot_id: &str) -> PathBuf {
|
||||
let prefix = &snapshot_id[..2.min(snapshot_id.len())];
|
||||
let prefix = snapshot_id.get(..2).unwrap_or(snapshot_id);
|
||||
self.base_dir.join(prefix).join(snapshot_id)
|
||||
}
|
||||
|
||||
@@ -62,6 +76,7 @@ impl SnapshotStorageBackend for LocalSnapshotStorage {
|
||||
head_oid: &str,
|
||||
data: &[u8],
|
||||
) -> Result<(), String> {
|
||||
Self::validate_snapshot_id(snapshot_id)?;
|
||||
let dir = self.snapshot_dir(snapshot_id);
|
||||
std::fs::create_dir_all(&dir).map_err(|e| format!("create dir: {e}"))?;
|
||||
|
||||
@@ -95,6 +110,7 @@ impl SnapshotStorageBackend for LocalSnapshotStorage {
|
||||
}
|
||||
|
||||
fn read_snapshot(&self, snapshot_id: &str) -> Result<Vec<u8>, String> {
|
||||
Self::validate_snapshot_id(snapshot_id)?;
|
||||
let path = self.data_path(snapshot_id);
|
||||
if !path.exists() {
|
||||
return Err(format!("snapshot not found: {snapshot_id}"));
|
||||
@@ -155,6 +171,7 @@ impl SnapshotStorageBackend for LocalSnapshotStorage {
|
||||
}
|
||||
|
||||
fn delete_snapshot(&self, snapshot_id: &str) -> Result<(), String> {
|
||||
Self::validate_snapshot_id(snapshot_id)?;
|
||||
let dir = self.snapshot_dir(snapshot_id);
|
||||
if !dir.exists() {
|
||||
return Err(format!("snapshot not found: {snapshot_id}"));
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
//! Bundle applicator for restoring snapshots to git repositories.
|
||||
//!
|
||||
//! Uses `git bundle unbundle` to apply pack data to a bare repository.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct BundleApplicator {
|
||||
pub repo_path: PathBuf,
|
||||
}
|
||||
|
||||
impl BundleApplicator {
|
||||
pub fn new(repo_path: PathBuf) -> Self {
|
||||
Self { repo_path }
|
||||
}
|
||||
|
||||
pub fn apply_bundle(&self, data: &[u8]) -> Result<(), String> {
|
||||
let mut child = std::process::Command::new("git")
|
||||
.args([
|
||||
"--git-dir",
|
||||
&self.repo_path.to_string_lossy(),
|
||||
"bundle",
|
||||
"unbundle",
|
||||
"-",
|
||||
])
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("spawn git bundle unbundle: {e}"))?;
|
||||
use std::io::Write;
|
||||
if let Some(ref mut stdin) = child.stdin {
|
||||
stdin
|
||||
.write_all(data)
|
||||
.map_err(|e| format!("write bundle: {e}"))?;
|
||||
}
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.map_err(|e| format!("wait bundle: {e}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(String::from_utf8_lossy(&output.stderr).into_owned());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply bundle from a file path (for streaming writes).
|
||||
pub fn apply_bundle_from_file(&self, path: &Path) -> Result<(), String> {
|
||||
let file = std::fs::File::open(path).map_err(|e| format!("open bundle file: {e}"))?;
|
||||
let mut child = std::process::Command::new("git")
|
||||
.args([
|
||||
"--git-dir",
|
||||
&self.repo_path.to_string_lossy(),
|
||||
"bundle",
|
||||
"unbundle",
|
||||
"-",
|
||||
])
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("spawn git bundle unbundle: {e}"))?;
|
||||
|
||||
// Stream file contents to stdin in a background thread
|
||||
let mut stdin = child.stdin.take().ok_or("no stdin")?;
|
||||
let file_handle = file;
|
||||
let writer = std::thread::spawn(move || -> Result<(), String> {
|
||||
use std::io::{Read, Write};
|
||||
let mut reader = std::io::BufReader::new(file_handle);
|
||||
let mut buf = vec![0u8; 65536];
|
||||
loop {
|
||||
match reader.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
stdin
|
||||
.write_all(&buf[..n])
|
||||
.map_err(|e| format!("write to stdin: {e}"))?;
|
||||
}
|
||||
Err(e) => return Err(format!("read bundle file: {e}")),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.map_err(|e| format!("wait bundle: {e}"))?;
|
||||
|
||||
// Wait for writer thread
|
||||
let _ = writer.join().map_err(|_| "writer thread panicked")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(String::from_utf8_lossy(&output.stderr).into_owned());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user