Skip to content
This repository was archived by the owner on Aug 9, 2026. It is now read-only.

Commit 44b9b6d

Browse files
authored
Merge pull request #53 from johnramsden/john/locking
Reduce lock contention for promo
2 parents eb5ae3b + 142db6a commit 44b9b6d

5 files changed

Lines changed: 162 additions & 28 deletions

File tree

oxcache/src/cache/bucket.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ pub struct Chunk {
1111
pub size: Byte,
1212
}
1313

14+
// // Only hash and compare based on uuid - offset/size are just read parameters
15+
// impl PartialEq for Chunk {
16+
// fn eq(&self, other: &Self) -> bool {
17+
// self.uuid == other.uuid
18+
// }
19+
// }
20+
//
21+
// impl Eq for Chunk {}
22+
//
23+
// impl std::hash::Hash for Chunk {
24+
// fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
25+
// self.uuid.hash(state);
26+
// }
27+
// }
28+
1429
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
1530
pub struct ChunkLocation {
1631
pub zone: Zone,

oxcache/src/device.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,17 @@ impl Device for Zoned {
707707
let mut zones = zone_mtx.lock().unwrap();
708708
zones.reset_zones(&zones_to_evict, &*self)?;
709709

710+
// Reset atomic counters for evicted zones
711+
let mut policy = eviction_policy.lock().unwrap();
712+
if let EvictionPolicyWrapper::Promotional(p) = &*policy {
713+
for &zone in &zones_to_evict {
714+
if (zone as usize) < p.zone_chunk_counts.len() {
715+
p.zone_chunk_counts[zone as usize].store(0, Ordering::Relaxed);
716+
}
717+
}
718+
}
719+
drop(policy);
720+
710721
Ok(())
711722
}
712723
}
@@ -1051,6 +1062,18 @@ impl Device for BlockInterface {
10511062
let state_mtx = Arc::clone(&self.state);
10521063
let mut state = state_mtx.lock().unwrap();
10531064
state.active_zones.reset_zones(&locations, &*self)?;
1065+
1066+
// Reset atomic counters for evicted zones
1067+
let mut policy = eviction_policy.lock().unwrap();
1068+
if let EvictionPolicyWrapper::Promotional(p) = &*policy {
1069+
for &zone in &locations {
1070+
if (zone as usize) < p.zone_chunk_counts.len() {
1071+
p.zone_chunk_counts[zone as usize].store(0, Ordering::Relaxed);
1072+
}
1073+
}
1074+
}
1075+
drop(policy);
1076+
10541077
Ok(())
10551078
}
10561079
}

oxcache/src/eviction.rs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use lru_mem::{LruCache, MemSize};
88
use nvme::types::{Chunk, Zone};
99
use std::sync::{
1010
Arc, Mutex,
11-
atomic::{AtomicBool, Ordering},
11+
atomic::{AtomicBool, AtomicU64, Ordering},
1212
};
1313
use std::thread::{self, JoinHandle};
1414
use std::time::Duration;
@@ -119,7 +119,8 @@ pub struct PromotionalEvictionPolicy {
119119
high_water: Zone,
120120
low_water: Zone,
121121
nr_zones: Zone,
122-
nr_chunks_per_zone: Chunk,
122+
pub nr_chunks_per_zone: Chunk,
123+
pub zone_chunk_counts: Arc<Vec<AtomicU64>>,
123124
lru: LruCache<Zone, ()>,
124125
#[cfg(feature = "eviction-metrics")]
125126
pub metrics: Option<Arc<crate::eviction_metrics::EvictionMetrics>>,
@@ -133,11 +134,17 @@ impl PromotionalEvictionPolicy {
133134
nr_chunks_per_zone: Chunk,
134135
) -> Self {
135136
let lru = LruCache::new(usize::MAX); // Effectively unbounded
137+
let zone_chunk_counts = Arc::new(
138+
(0..nr_zones)
139+
.map(|_| AtomicU64::new(0))
140+
.collect()
141+
);
136142
Self {
137143
high_water,
138144
low_water,
139145
nr_zones,
140146
nr_chunks_per_zone,
147+
zone_chunk_counts,
141148
lru,
142149
#[cfg(feature = "eviction-metrics")]
143150
metrics: None,
@@ -157,12 +164,9 @@ impl EvictionPolicy for PromotionalEvictionPolicy {
157164
metrics.record_write(&chunk);
158165
}
159166

160-
// assert!(!self.lru.contains(&chunk.zone)); // We cannot assert this because we allow out of order writes
161-
162-
// We only want to put it in the LRU once the zone is full
163-
if chunk.index == self.nr_chunks_per_zone - 1 {
164-
self.lru.insert(chunk.zone, ()).ok();
165-
}
167+
// This is now only called when zone is full (atomic check done outside lock)
168+
// Just insert the zone into the LRU
169+
self.lru.insert(chunk.zone, ()).ok();
166170
}
167171

168172
fn read_update(&mut self, chunk: ChunkLocation) {
@@ -171,9 +175,8 @@ impl EvictionPolicy for PromotionalEvictionPolicy {
171175
metrics.record_read(&chunk);
172176
}
173177

174-
// We only want to put it in the LRU once the zone is full
175-
// If it has filled before we want to update every time "promoting" it
176-
// Following this, only zones that have filled prior are updated
178+
// This is now only called when zone is full (atomic check done outside lock)
179+
// Promote the zone in the LRU if it's already there
177180
if self.lru.contains(&chunk.zone) {
178181
self.lru.insert(chunk.zone, ()).ok();
179182
}
@@ -558,7 +561,10 @@ mod tests {
558561

559562
// zone=[_,_,_,_], lru=()
560563
let mut order: VecDeque<Zone> = VecDeque::new();
561-
policy.write_update(ChunkLocation::new(3, 0));
564+
let count = policy.zone_chunk_counts[3].fetch_add(1, Ordering::Relaxed);
565+
if count + 1 == policy.nr_chunks_per_zone {
566+
policy.write_update(ChunkLocation::new(3, 0));
567+
}
562568
compare_order(&mut policy.lru, &order);
563569
let et = policy.get_evict_targets(false);
564570
let expect_none: Vec<Zone> = vec![];
@@ -569,7 +575,10 @@ mod tests {
569575
);
570576

571577
// zone=[_,_,_,_], lru=()
572-
policy.write_update(ChunkLocation::new(3, 1));
578+
let count = policy.zone_chunk_counts[3].fetch_add(1, Ordering::Relaxed);
579+
if count + 1 == policy.nr_chunks_per_zone {
580+
policy.write_update(ChunkLocation::new(3, 1));
581+
}
573582
// zone=[_,_,_,3], lru=(3)
574583
order.push_back(3);
575584
compare_order(&mut policy.lru, &order);
@@ -580,12 +589,18 @@ mod tests {
580589
expect_none, et
581590
);
582591

583-
policy.write_update(ChunkLocation::new(1, 0));
592+
let count = policy.zone_chunk_counts[1].fetch_add(1, Ordering::Relaxed);
593+
if count + 1 == policy.nr_chunks_per_zone {
594+
policy.write_update(ChunkLocation::new(1, 0));
595+
}
584596
// There should be no change
585597
// zone=[_,_,_,3], lru=(3)
586598
compare_order(&mut policy.lru, &order);
587599

588-
policy.write_update(ChunkLocation::new(1, 1));
600+
let count = policy.zone_chunk_counts[1].fetch_add(1, Ordering::Relaxed);
601+
if count + 1 == policy.nr_chunks_per_zone {
602+
policy.write_update(ChunkLocation::new(1, 1));
603+
}
589604
// zone=[_,1,_,3], lru=(3, 1)
590605
order.push_front(1);
591606
compare_order(&mut policy.lru, &order);
@@ -595,15 +610,22 @@ mod tests {
595610
"Expected = {:?}, but got {:?}",
596611
expect_none, et
597612
);
598-
599-
policy.write_update(ChunkLocation::new(2, 0));
600-
policy.write_update(ChunkLocation::new(2, 1));
613+
let count = policy.zone_chunk_counts[2].fetch_add(1, Ordering::Relaxed);
614+
if count + 1 == policy.nr_chunks_per_zone {
615+
policy.write_update(ChunkLocation::new(2, 0));
616+
}
617+
let count = policy.zone_chunk_counts[2].fetch_add(1, Ordering::Relaxed);
618+
if count + 1 == policy.nr_chunks_per_zone {
619+
policy.write_update(ChunkLocation::new(2, 1));
620+
}
601621
order.push_front(2);
602622
// zone=[_,1,2,3], lru=(3, 1, 2)
603623
compare_order(&mut policy.lru, &order);
604624

605625
// Should update in place, and adjust order
606-
policy.read_update(ChunkLocation::new(3, 1));
626+
if policy.zone_chunk_counts[3].load(Ordering::Relaxed) >= policy.nr_chunks_per_zone {
627+
policy.read_update(ChunkLocation::new(3, 1));
628+
}
607629
let c = order.pop_back().unwrap();
608630
order.push_front(c);
609631
// zone=[_,1,2,3], lru=(1, 2, 3)

oxcache/src/readerpool.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{cache, device};
33
use bytes::Bytes;
44
use flume::{Receiver, Sender, unbounded};
55
use std::sync::{Arc, Mutex};
6+
use std::sync::atomic::Ordering;
67
use std::thread::{self, JoinHandle};
78
use crate::metrics::{MetricType, METRICS};
89
use crate::cache::bucket::PinGuard;
@@ -53,8 +54,33 @@ impl Reader {
5354
METRICS.update_metric_histogram_latency("device_read_latency_ms", start.elapsed(), MetricType::MsLatency);
5455
if result.is_ok() {
5556
let mtx = Arc::clone(&self.eviction_policy);
56-
let mut policy = mtx.lock().unwrap();
57-
policy.read_update(msg.location);
57+
let policy = mtx.lock().unwrap();
58+
59+
match &*policy {
60+
EvictionPolicyWrapper::Promotional(p) => {
61+
// Clone Arc references before dropping lock
62+
let zone_idx = msg.location.zone as usize;
63+
let nr_chunks = p.nr_chunks_per_zone;
64+
let counters = Arc::clone(&p.zone_chunk_counts);
65+
drop(policy);
66+
67+
// Check atomically if zone is full (outside lock)
68+
if zone_idx < counters.len() {
69+
let count = counters[zone_idx].load(Ordering::Relaxed);
70+
71+
// Only acquire lock if zone is full
72+
if count >= nr_chunks {
73+
let mut policy = mtx.lock().unwrap();
74+
policy.read_update(msg.location);
75+
}
76+
}
77+
}
78+
EvictionPolicyWrapper::Chunk(_) => {
79+
drop(policy);
80+
let mut policy = mtx.lock().unwrap();
81+
policy.read_update(msg.location);
82+
}
83+
}
5884
}
5985
let resp = ReadResponse { data: result };
6086
let snd = msg.responder.send(resp);

oxcache/src/writerpool.rs

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,35 @@ impl Writer {
123123
let start = std::time::Instant::now();
124124

125125
let result = self.device.append(msg.data).inspect(|loc| {
126-
let mtx = Arc::clone(&self.eviction);
127-
128126
if msg.update_lru {
129-
let mut policy = mtx.lock().unwrap();
130-
policy.write_update(loc.clone());
127+
let mtx = Arc::clone(&self.eviction);
128+
let policy = mtx.lock().unwrap();
129+
130+
match &*policy {
131+
EvictionPolicyWrapper::Promotional(p) => {
132+
// Clone Arc references before dropping lock
133+
let zone_idx = loc.zone as usize;
134+
let nr_chunks = p.nr_chunks_per_zone;
135+
let counters = Arc::clone(&p.zone_chunk_counts);
136+
drop(policy);
137+
138+
// Do atomic increment outside lock
139+
if zone_idx < counters.len() {
140+
let count = counters[zone_idx].fetch_add(1, Ordering::Relaxed);
141+
142+
// Only re-acquire lock when zone becomes full
143+
if count + 1 == nr_chunks {
144+
let mut policy = mtx.lock().unwrap();
145+
policy.write_update(loc.clone());
146+
}
147+
}
148+
}
149+
EvictionPolicyWrapper::Chunk(_) => {
150+
drop(policy);
151+
let mut policy = mtx.lock().unwrap();
152+
policy.write_update(loc.clone());
153+
}
154+
}
131155
}
132156
});
133157
METRICS.update_metric_histogram_latency("device_write_latency_ms", start.elapsed(), MetricType::MsLatency);
@@ -153,9 +177,33 @@ impl Writer {
153177

154178
if let Ok(ref loc) = result {
155179
let mtx = Arc::clone(&self.eviction);
156-
let mut policy = mtx.lock().unwrap();
157-
policy.write_update(loc.clone());
158-
drop(policy);
180+
let policy = mtx.lock().unwrap();
181+
182+
match &*policy {
183+
EvictionPolicyWrapper::Promotional(p) => {
184+
// Clone Arc references before dropping lock
185+
let zone_idx = loc.zone as usize;
186+
let nr_chunks = p.nr_chunks_per_zone;
187+
let counters = Arc::clone(&p.zone_chunk_counts);
188+
drop(policy);
189+
190+
// Do atomic increment outside lock
191+
if zone_idx < counters.len() {
192+
let count = counters[zone_idx].fetch_add(1, Ordering::Relaxed);
193+
194+
// Only re-acquire lock when zone becomes full
195+
if count + 1 == nr_chunks {
196+
let mut policy = mtx.lock().unwrap();
197+
policy.write_update(loc.clone());
198+
}
199+
}
200+
}
201+
EvictionPolicyWrapper::Chunk(_) => {
202+
drop(policy);
203+
let mut policy = mtx.lock().unwrap();
204+
policy.write_update(loc.clone());
205+
}
206+
}
159207
} else {
160208
tracing::error!("Failed to append: {:?}", result);
161209
}

0 commit comments

Comments
 (0)