20260418 优化去掉冗余存储repo object,mix quick最快170s

This commit is contained in:
2026-04-18 14:10:47 +08:00
parent f485786470
commit 417c82bef6
34 changed files with 1482 additions and 328 deletions

View File

@@ -16,6 +16,26 @@ fn raw_blob_key(sha256_hex: &str) -> String {
format!("{RAW_BLOB_KEY_PREFIX}{sha256_hex}")
}
fn validate_blob_sha256_hex(sha256_hex: &str) -> StorageResult<()> {
if sha256_hex.len() != 64 || !sha256_hex.as_bytes().iter().all(u8::is_ascii_hexdigit) {
return Err(StorageError::InvalidData {
entity: "raw_blob",
detail: format!("invalid sha256 hex: {sha256_hex}"),
});
}
Ok(())
}
fn validate_blob_bytes(bytes: &[u8]) -> StorageResult<()> {
if bytes.is_empty() {
return Err(StorageError::InvalidData {
entity: "raw_blob",
detail: "bytes must not be empty".to_string(),
});
}
Ok(())
}
pub trait RawObjectStore {
fn get_raw_entry(&self, sha256_hex: &str) -> StorageResult<Option<RawByHashEntry>>;
@@ -103,6 +123,23 @@ impl ExternalRawStoreDb {
Ok(())
}
pub fn put_blob_bytes_batch(&self, blobs: &[(String, Vec<u8>)]) -> StorageResult<()> {
if blobs.is_empty() {
return Ok(());
}
let mut batch = WriteBatch::default();
for (sha256_hex, bytes) in blobs {
validate_blob_sha256_hex(sha256_hex)?;
validate_blob_bytes(bytes)?;
let blob_key = raw_blob_key(sha256_hex);
batch.put(blob_key.as_bytes(), bytes.as_slice());
}
self.db
.write(batch)
.map_err(|e| StorageError::RocksDb(e.to_string()))?;
Ok(())
}
pub fn delete_raw_entry(&self, sha256_hex: &str) -> StorageResult<()> {
let key = raw_by_hash_key(sha256_hex);
let blob_key = raw_blob_key(sha256_hex);
@@ -134,23 +171,11 @@ impl RawObjectStore for RocksStore {
}
fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult<Option<Vec<u8>>> {
if let Some(raw_store) = self.external_raw_store_ref() {
return raw_store.get_blob_bytes(sha256_hex);
}
self.get_raw_entry(sha256_hex)
.map(|entry| entry.map(|entry| entry.bytes))
RocksStore::get_blob_bytes(self, sha256_hex)
}
fn get_blob_bytes_batch(&self, sha256_hexes: &[String]) -> StorageResult<Vec<Option<Vec<u8>>>> {
if let Some(raw_store) = self.external_raw_store_ref() {
return raw_store.get_blob_bytes_batch(sha256_hexes);
}
self.get_raw_entries_batch(sha256_hexes).map(|entries| {
entries
.into_iter()
.map(|entry| entry.map(|entry| entry.bytes))
.collect()
})
RocksStore::get_blob_bytes_batch(self, sha256_hexes)
}
}
@@ -431,6 +456,47 @@ mod tests {
);
}
#[test]
fn put_blob_bytes_batch_round_trips_without_raw_entry() {
let td = tempfile::tempdir().expect("tempdir");
let raw_store =
ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store");
let a = (sha256_hex(b"blob-a"), b"blob-a".to_vec());
let b = (sha256_hex(b"blob-b"), b"blob-b".to_vec());
raw_store
.put_blob_bytes_batch(&[a.clone(), b.clone()])
.expect("put blobs");
assert_eq!(
raw_store.get_blob_bytes(&a.0).expect("get blob a"),
Some(a.1.clone())
);
assert_eq!(
raw_store.get_blob_bytes(&b.0).expect("get blob b"),
Some(b.1.clone())
);
assert!(raw_store.get_raw_entry(&a.0).expect("get raw a").is_none());
assert!(raw_store.get_raw_entry(&b.0).expect("get raw b").is_none());
}
#[test]
fn put_blob_bytes_batch_rejects_invalid_inputs() {
let td = tempfile::tempdir().expect("tempdir");
let raw_store =
ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store");
let err = raw_store
.put_blob_bytes_batch(&[("zz".repeat(32), b"blob".to_vec())])
.expect_err("invalid hash should fail");
assert!(matches!(err, StorageError::InvalidData { .. }));
let err = raw_store
.put_blob_bytes_batch(&[(sha256_hex(b"blob"), Vec::new())])
.expect_err("empty bytes should fail");
assert!(matches!(err, StorageError::InvalidData { .. }));
}
#[test]
fn external_raw_store_db_rejects_invalid_entry_on_put() {
let td = tempfile::tempdir().expect("tempdir");