This commit is contained in:
Mateusz Gruszczyński committed 2026-10-07 22:22:54 +02:00
1 parent e84df445c6
commit e87a382a7e
109 files changed
+1092 -2925

No files matched your search

+1 -2
View File
@@ -243,8 +243,7 @@ impl Cache {
entry.ttl,
entry.stale_while_revalidate,
entry.stale_if_error,
entry.origin_etag.is_some()
|| entry.origin_last_modified.is_some(),
entry.has_validator,
)
});
if should_remove {
+15 -18
View File
@@ -99,19 +99,19 @@ impl Cache {
if path.file_stem().and_then(|v| v.to_str()) != Some(file.key.as_str()) {
continue;
}
let Some(headers) = pairs_to_headers(&file.headers) else {
if pairs_to_headers(&file.headers).is_none() {
continue;
};
}
if body_path.parent() != Some(shard.as_path()) {
continue;
}
let Ok(body_meta) = std::fs::metadata(&body_path) else {
continue;
};
if body_meta.len() != file.body_bytes as u64
|| file.body_sha256.len() != 64
|| !file.body_sha256.bytes().all(|b| b.is_ascii_hexdigit())
{
let Some(body_sha256) = parse_key_hex(&file.body_sha256) else {
continue;
};
if body_meta.len() != file.body_bytes as u64 {
continue;
}
let origin_etag = file
@@ -138,8 +138,7 @@ impl Cache {
let _ = std::fs::remove_file(&body_path);
continue;
}
loaded.push(ColdEntry {
key,
loaded.push((key, ColdEntry {
base_key,
vary,
revision,
@@ -163,11 +162,10 @@ impl Cache {
} else {
system_time_ms(file.created_at_ms)
},
headers,
body_path,
meta_path: path,
body_bytes: file.body_bytes,
body_sha256: file.body_sha256,
body_sha256,
disk_bytes: body_meta.len().saturating_add(bytes.len() as u64),
stored_at,
last_access: if file.last_access_ms == 0 {
@@ -182,8 +180,7 @@ impl Cache {
stale_if_error: file.stale_if_error,
must_revalidate: file.must_revalidate,
proxy_revalidate: file.proxy_revalidate,
origin_etag,
origin_last_modified,
has_validator: origin_etag.is_some() || origin_last_modified.is_some(),
hits: file.hits,
transformed: file.transformed,
sequence: 0,
@@ -191,12 +188,12 @@ impl Cache {
skip_compression: file.skip_compression,
requires_revalidation: file.requires_revalidation,
pinned_reads: 0,
});
}));
}
}
loaded.sort_by_key(|entry| entry.last_access);
loaded.sort_by_key(|(_, entry)| entry.last_access);
let mut cold = self.cold.lock().unwrap_or_else(|e| e.into_inner());
for mut entry in loaded {
for (key, mut entry) in loaded {
for key in incompatible_cold_variants(&cold, &entry.base_key, &entry.vary) {
if let Some(old) = cold.remove(&key) {
let _ = std::fs::remove_file(old.meta_path);
@@ -218,9 +215,9 @@ impl Cache {
entry.sequence = cold.next_sequence();
entry.generation = entry.sequence;
cold.bytes = cold.bytes.saturating_add(entry.disk_bytes);
cold.lru.insert(entry.sequence, entry.key);
cold.index_variant(entry.base_key, entry.key);
cold.entries.insert(entry.key, entry);
cold.lru.insert(entry.sequence, key);
cold.index_variant(entry.base_key, key);
cold.entries.insert(key, entry);
}
}
}
+6 -6
View File
@@ -114,6 +114,7 @@ impl Cache {
.saturating_add(draft.entry.received.elapsed())
.as_secs();
let resident_age_at_store = hot_resident_age(&draft.entry).as_secs();
let body_sha256 = body_sha256_bytes(&draft.entry.body);
let file = ColdFile {
format: COLD_FORMAT_VERSION,
key: key.clone(),
@@ -137,7 +138,7 @@ impl Cache {
headers,
body_file: body_name,
body_bytes: draft.entry.body.len(),
body_sha256: body_sha256(&draft.entry.body),
body_sha256: key_hex(&body_sha256),
stored_at_ms: now_ms(),
age_at_store_seconds: age_at_store,
resident_age_at_store_seconds: resident_age_at_store,
@@ -236,8 +237,9 @@ impl Cache {
delete.extend([meta_path.clone(), body_path.clone()]);
} else {
let sequence = cold.next_sequence();
let has_validator =
draft.entry.origin_etag.is_some() || draft.entry.origin_last_modified.is_some();
let entry = ColdEntry {
key: draft.key,
base_key: draft.entry.base_key,
vary: draft.entry.vary,
revision: draft.revision,
@@ -249,11 +251,10 @@ impl Cache {
target: draft.entry.target,
tags: draft.entry.tags,
created_at: draft.entry.created_at,
headers: draft.entry.headers,
body_path: body_path.clone(),
meta_path: meta_path.clone(),
body_bytes: draft.entry.body.len(),
body_sha256: file.body_sha256,
body_sha256,
disk_bytes,
stored_at: SystemTime::now(),
age_at_store_seconds: age_at_store,
@@ -263,8 +264,7 @@ impl Cache {
stale_if_error: draft.entry.stale_if_error,
must_revalidate: draft.entry.must_revalidate,
proxy_revalidate: draft.entry.proxy_revalidate,
origin_etag: draft.entry.origin_etag,
origin_last_modified: draft.entry.origin_last_modified,
has_validator,
last_access: SystemTime::now(),
hits: draft.entry.hits,
transformed: draft.entry.transformed,
+14 -7
View File
@@ -150,6 +150,13 @@ impl Cache {
key: storage_key,
generation: entry.generation,
};
let Some(metadata) = read_cold_metadata(&storage_key, &entry).await else {
self.counters.cold_errors.fetch_add(1, Ordering::Relaxed);
if let Some(entry) = self.take_cold(&storage_key) {
self.delete_cold_entry(entry);
}
return None;
};
if entry.body_bytes > settings.max_object_bytes {
self.counters
.memory_pressure_rejections
@@ -203,7 +210,7 @@ impl Cache {
let hits = entry.hits.saturating_add(1);
let cost = hot_entry_cost(
body_allocated,
&entry.headers,
&metadata.headers,
&entry.vary,
&entry.scope,
&entry.scheme,
@@ -212,8 +219,8 @@ impl Cache {
&entry.path,
&entry.target,
&entry.tags,
entry.origin_etag.as_ref(),
entry.origin_last_modified.as_ref(),
metadata.origin_etag.as_ref(),
metadata.origin_last_modified.as_ref(),
);
let resident_age_seconds = cold_resident_age(&entry);
let hot_entry = Entry {
@@ -227,7 +234,7 @@ impl Cache {
target: entry.target.clone(),
tags: entry.tags.clone(),
created_at: entry.created_at,
headers: entry.headers.clone(),
headers: metadata.headers.clone(),
body: body.clone(),
body_allocated,
received: Instant::now(),
@@ -240,8 +247,8 @@ impl Cache {
stale_if_error: entry.stale_if_error,
must_revalidate: entry.must_revalidate,
proxy_revalidate: entry.proxy_revalidate,
origin_etag: entry.origin_etag.clone(),
origin_last_modified: entry.origin_last_modified.clone(),
origin_etag: metadata.origin_etag.clone(),
origin_last_modified: metadata.origin_last_modified.clone(),
cost,
sequence: 0,
hits,
@@ -253,7 +260,7 @@ impl Cache {
return None;
}
let mut response = Response::new(Body::from(body.clone()));
*response.headers_mut() = entry.headers;
*response.headers_mut() = metadata.headers;
if entry.transformed {
response
.extensions_mut()
+1
View File
@@ -2,4 +2,5 @@ include!("model/settings.rs");
include!("model/entry.rs");
include!("model/cache.rs");
include!("model/maintenance.rs");
include!("model/cold_metadata.rs");
include!("model/views.rs");
+2 -5
View File
@@ -246,7 +246,6 @@ struct ColdFile {
#[derive(Clone)]
struct ColdEntry {
key: Key,
base_key: Key,
vary: Vec<String>,
revision: u64,
@@ -258,11 +257,10 @@ struct ColdEntry {
target: String,
tags: Vec<String>,
created_at: SystemTime,
headers: HeaderMap,
body_path: PathBuf,
meta_path: PathBuf,
body_bytes: usize,
body_sha256: String,
body_sha256: Key,
disk_bytes: u64,
stored_at: SystemTime,
last_access: SystemTime,
@@ -273,8 +271,7 @@ struct ColdEntry {
stale_if_error: u64,
must_revalidate: bool,
proxy_revalidate: bool,
origin_etag: Option<HeaderValue>,
origin_last_modified: Option<HeaderValue>,
has_validator: bool,
hits: u64,
transformed: bool,
sequence: u64,
+69
View File
@@ -0,0 +1,69 @@
struct ColdMetadata {
headers: HeaderMap,
origin_etag: Option<HeaderValue>,
origin_last_modified: Option<HeaderValue>,
}
async fn read_cold_metadata(storage_key: &Key, entry: &ColdEntry) -> Option<ColdMetadata> {
let bytes = tokio::fs::read(&entry.meta_path).await.ok()?;
let file: ColdFile = serde_json::from_slice(&bytes).ok()?;
let key = parse_key_hex(&file.key)?;
let base_key = parse_key_hex(&file.base_key)?;
let body_sha256 = parse_key_hex(&file.body_sha256)?;
let mut vary = file.vary.clone();
vary.sort();
vary.dedup();
let authority = if file.authority.is_empty() {
file.domain.as_str()
} else {
file.authority.as_str()
};
let target = if file.target.is_empty() {
file.path.as_str()
} else {
file.target.as_str()
};
if file.format != COLD_FORMAT_VERSION
|| key != *storage_key
|| base_key != entry.base_key
|| vary != entry.vary
|| file.revision != entry.revision
|| file.scope != entry.scope
|| file.scheme != entry.scheme
|| authority != entry.authority
|| file.domain != entry.domain
|| file.path != entry.path
|| target != entry.target
|| file.tags != entry.tags
|| file.body_bytes != entry.body_bytes
|| body_sha256 != entry.body_sha256
|| file.ttl != entry.ttl
|| file.stale_while_revalidate != entry.stale_while_revalidate
|| file.stale_if_error != entry.stale_if_error
|| file.must_revalidate != entry.must_revalidate
|| file.proxy_revalidate != entry.proxy_revalidate
|| file.transformed != entry.transformed
|| file.skip_compression != entry.skip_compression
|| file.requires_revalidation != entry.requires_revalidation
|| entry.body_path.file_name().and_then(|name| name.to_str()) != Some(file.body_file.as_str())
{
return None;
}
let headers = pairs_to_headers(&file.headers)?;
let origin_etag = file
.origin_etag
.as_deref()
.and_then(|value| HeaderValue::from_str(value).ok());
let origin_last_modified = file
.origin_last_modified
.as_deref()
.and_then(|value| HeaderValue::from_str(value).ok());
if entry.has_validator != (origin_etag.is_some() || origin_last_modified.is_some()) {
return None;
}
Some(ColdMetadata {
headers,
origin_etag,
origin_last_modified,
})
}
+4 -7
View File
@@ -404,18 +404,15 @@ fn incompatible_cold_variants(store: &ColdStore, base_key: &Key, vary: &[String]
.collect()
}
fn body_sha256(body: &[u8]) -> String {
fn body_sha256_bytes(body: &[u8]) -> Key {
let digest = Sha256::digest(body);
let mut out = String::with_capacity(64);
for byte in digest {
use std::fmt::Write as _;
let _ = write!(&mut out, "{byte:02x}");
}
let mut out = [0u8; 32];
out.copy_from_slice(&digest);
out
}
fn cold_body_matches(entry: &ColdEntry, body: &[u8]) -> bool {
body.len() == entry.body_bytes && body_sha256(body).as_str() == entry.body_sha256.as_str()
body.len() == entry.body_bytes && body_sha256_bytes(body) == entry.body_sha256
}
fn key_hex(key: &Key) -> String {
+2 -5
View File
@@ -256,7 +256,6 @@ fn old_cold_read_guard_does_not_unpin_a_replacement_with_the_same_key() {
let cache = Cache::new(1);
let key = [7_u8; 32];
let template = ColdEntry {
key,
base_key: key,
vary: Vec::new(),
revision: 1,
@@ -268,11 +267,10 @@ fn old_cold_read_guard_does_not_unpin_a_replacement_with_the_same_key() {
target: "https://example.test/asset".to_owned(),
tags: Vec::new(),
created_at: SystemTime::now(),
headers: HeaderMap::new(),
body_path: PathBuf::from("old.body"),
meta_path: PathBuf::from("old.json"),
body_bytes: 1,
body_sha256: "00".repeat(32),
body_sha256: [0u8; 32],
disk_bytes: 2,
stored_at: SystemTime::now(),
last_access: SystemTime::now(),
@@ -283,8 +281,7 @@ fn old_cold_read_guard_does_not_unpin_a_replacement_with_the_same_key() {
stale_if_error: 0,
must_revalidate: false,
proxy_revalidate: false,
origin_etag: None,
origin_last_modified: None,
has_validator: false,
hits: 0,
transformed: false,
sequence: 1,
+11 -4
View File
@@ -158,7 +158,7 @@ impl Cache {
entry.ttl,
entry.stale_while_revalidate,
entry.stale_if_error,
entry.origin_etag.is_some() || entry.origin_last_modified.is_some(),
entry.has_validator,
)
{
return None;
@@ -179,6 +179,13 @@ impl Cache {
key: storage_key,
generation: entry.generation,
};
let Some(metadata) = read_cold_metadata(&storage_key, &entry).await else {
self.counters.cold_errors.fetch_add(1, Ordering::Relaxed);
if let Some(entry) = self.take_cold(&storage_key) {
self.delete_cold_entry(entry);
}
return None;
};
let settings = self
.tier_settings
.lock()
@@ -219,7 +226,7 @@ impl Cache {
let age = cold_age(&entry);
let resident_age_at_lookup = Duration::from_secs(cold_resident_age(&entry));
Some(Revalidation {
headers: entry.headers,
headers: metadata.headers,
body,
body_allocated,
_transient_leases: extra_lease
@@ -237,8 +244,8 @@ impl Cache {
stale_if_error: entry.stale_if_error,
must_revalidate: entry.must_revalidate,
proxy_revalidate: entry.proxy_revalidate,
origin_etag: entry.origin_etag,
origin_last_modified: entry.origin_last_modified,
origin_etag: metadata.origin_etag,
origin_last_modified: metadata.origin_last_modified,
})
}
pub async fn revalidation_tiered(&self, ticket: &Ticket) -> Option<Revalidation> {