Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

### Improvements

- L2 index updates and cold-record reclamation skip atomic writes for heat bits that are already clear.
- A zero-capacity L1 bypasses shard locking and directory work for lookups, fills, promotions, and deletes.
- Memory accounting skips redundant atomic updates when transient reads stay within the recorded peak or L1 transfers leave the charged byte total unchanged.
- L2 index updates and cold-record reclamation avoid redundant atomic writes for already-clear heat bits and validated index pages.
- Hot-record reclamation reuses the checksum computed during payload validation for its rewrite, avoiding a second payload scan while preserving record validation and the on-disk format.
- L1 replacement and eviction reuse detached, exclusively owned allocations of the same size under CLOCK and S3-FIFO, reducing repeated allocation during fixed-size turnover without an additional buffer pool. Caller-held values remain immutable and retain their separate memory charge. When activity statistics are enabled, mutations with no eviction also avoid a shared zero-value counter update.

Expand Down
3 changes: 3 additions & 0 deletions cache2/src/cache/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2506,6 +2506,9 @@ mod tests {
store.close_warm().unwrap();
std::fs::remove_dir_all(path).unwrap();
assert_eq!(snapshot.l1_hits, 0);
if l1_capacity == 0 {
assert_eq!(snapshot.l1_bypasses, 2);
}
assert_eq!(snapshot.l2_hits, u64::from(activity_counters));
assert_eq!(
snapshot.served_bytes,
Expand Down
6 changes: 5 additions & 1 deletion cache2/src/managed_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,11 @@ fn align_up(value: usize, alignment: usize) -> Option<usize> {
}

fn update_peak(peak: &AtomicUsize, candidate: usize) {
peak.fetch_max(candidate, Ordering::Relaxed);
// The peak only grows; a stale lower observation can cause an extra RMW,
// but cannot suppress a new maximum.
if candidate > peak.load(Ordering::Relaxed) {
peak.fetch_max(candidate, Ordering::Relaxed);
}
}

const MAX_ATOMIC_UPDATE_ATTEMPTS: usize = 8;
Expand Down
45 changes: 32 additions & 13 deletions cache2/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ impl MemoryBudget {
.saturating_sub(self.used_bytes.load(Ordering::Acquire))
}

/// Atomically transfers exclusive victim charges to one candidate before
/// any resident entry is removed. A failed CAS leaves both accounting and
/// the resident set unchanged.
/// Transfers exclusive victim charges to one candidate before any resident
/// entry is removed. Net byte changes use CAS; failure leaves both
/// accounting and the resident set unchanged.
fn try_transfer_charge(
self: &Arc<Self>,
released: usize,
Expand All @@ -135,18 +135,23 @@ impl MemoryBudget {
if next > self.capacity_bytes {
return MemoryChargeAttempt::Full;
}
match self
.used_bytes
.compare_exchange(used, next, Ordering::AcqRel, Ordering::Acquire)
// Exclusive victims keep these bytes charged until the caller
// disarms them. A net-zero transfer needs no counter update.
if next != used
&& let Err(observed) = self.used_bytes.compare_exchange(
used,
next,
Ordering::AcqRel,
Ordering::Acquire,
)
{
Ok(_) => {
return MemoryChargeAttempt::Charged(MemoryCharge {
budget: Some(Arc::clone(self)),
bytes: required,
});
}
Err(observed) => used = observed,
used = observed;
continue;
}
return MemoryChargeAttempt::Charged(MemoryCharge {
budget: Some(Arc::clone(self)),
bytes: required,
});
}
MemoryChargeAttempt::Contended
}
Expand Down Expand Up @@ -921,6 +926,10 @@ impl MemoryStore {
}

pub fn publish(&self, hash: u64, key: &[u8], value: &[u8], seqno: u64) -> bool {
if self.entry_capacity == 0 {
self.record_insert(MemoryInsertResult::bypassed());
return false;
}
let Some(charged_bytes) = memory_entry_bytes(key, value) else {
self.record_insert(MemoryInsertResult::bypassed());
return false;
Expand Down Expand Up @@ -955,6 +964,9 @@ impl MemoryStore {
/// Best-effort exact-key cleanup for a sequenced delete. Contention
/// bypasses L1, and a newer resident value is never removed.
pub fn delete(&self, hash: u64, key: &[u8], seqno: u64) -> bool {
if self.entry_capacity == 0 {
return true;
}
let shard_id = self.route(hash);
let Some(mut shard) = self.try_lock_shard(shard_id) else {
self.record_insert(MemoryInsertResult::bypassed());
Expand All @@ -975,6 +987,9 @@ impl MemoryStore {

pub fn lookup(&self, hash: u64, key: &[u8]) -> MemoryLookup {
let shard_id = self.route(hash);
if self.entry_capacity == 0 {
return MemoryLookup::Miss(MemoryReadToken { shard_id });
}
let mut attempts = 1;
let mut shard = loop {
match self.shards[shard_id].try_lock() {
Expand Down Expand Up @@ -1010,6 +1025,10 @@ impl MemoryStore {
if token.shard_id != self.route(hash) {
return None;
}
if self.entry_capacity == 0 {
self.record_insert(MemoryInsertResult::bypassed());
return None;
}
let Some(charged_bytes) = memory_entry_bytes(key, value) else {
self.record_insert(MemoryInsertResult::bypassed());
return None;
Expand Down
10 changes: 4 additions & 6 deletions cache2/src/region/index/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ pub mod page_format;
/// does not issue one positioned syscall per page.
pub const WARM_IMAGE_WRITE_BATCH_BYTES: usize = 1024 * 1024;

// Page states track source-image validation. Partition locks publish runtime
// mutations, and warm close regenerates every page and its checksum.
const PAGE_STATE_UNCHECKED: u8 = 0;
const PAGE_STATE_VALIDATING: u8 = 1;
const PAGE_STATE_VALID: u8 = 2;
const PAGE_STATE_DIRTY: u8 = 3;
const PAGE_STATE_REJECTED: u8 = 4;
const PAGE_STATE_REJECTED: u8 = 3;
const IMAGE_STATE_USABLE: u8 = 0;
const IMAGE_STATE_REJECTED: u8 = 1;

Expand Down Expand Up @@ -443,7 +444,6 @@ pub enum PageValidationState {
Unchecked,
Validating,
Valid,
Dirty,
Rejected,
}

Expand Down Expand Up @@ -947,7 +947,6 @@ impl IndexStorageCore {
PAGE_STATE_UNCHECKED => PageValidationState::Unchecked,
PAGE_STATE_VALIDATING => PageValidationState::Validating,
PAGE_STATE_VALID => PageValidationState::Valid,
PAGE_STATE_DIRTY => PageValidationState::Dirty,
PAGE_STATE_REJECTED => PageValidationState::Rejected,
_ => PageValidationState::Rejected,
})
Expand Down Expand Up @@ -978,7 +977,6 @@ impl IndexStorageCore {
INDEX_IMAGE_SLOT_SIZE,
);
}
self.page_states[page].store(PAGE_STATE_DIRTY, Ordering::Release);
self.ensure_image_usable(page)
}

Expand Down Expand Up @@ -1009,7 +1007,7 @@ impl IndexStorageCore {
loop {
self.ensure_image_usable(page)?;
return match state.load(Ordering::Acquire) {
PAGE_STATE_VALID | PAGE_STATE_DIRTY => Ok(()),
PAGE_STATE_VALID => Ok(()),
PAGE_STATE_REJECTED => Err(IndexStorageError::CorruptPage {
page_index: page,
reason: CorruptPageReason::PreviouslyRejected,
Expand Down
Loading