Skip to content

Commit f17fde8

Browse files
authored
[router] add remove tenant method in the radix tree (sgl-project#2379)
1 parent 3ecee5e commit f17fde8

2 files changed

Lines changed: 136 additions & 8 deletions

File tree

rust/src/router.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl Router {
168168

169169
let locked_tree_clone = tree_clone.lock().unwrap();
170170
// Run eviction
171-
locked_tree_clone.evict_tenant_data(max_tree_size);
171+
locked_tree_clone.evict_tenant_by_size(max_tree_size);
172172

173173
// Print the process queue
174174
let locked_processed_queue = processed_queue_clone.lock().unwrap();

rust/src/tree.rs

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use log::info;
55
use std::cmp::Reverse;
66
use std::collections::BinaryHeap;
77
use std::collections::HashMap;
8+
use std::collections::VecDeque;
89
use std::sync::Arc;
910
use std::sync::RwLock;
1011

@@ -404,7 +405,7 @@ impl Tree {
404405
.collect()
405406
}
406407

407-
pub fn evict_tenant_data(&self, max_size: usize) {
408+
pub fn evict_tenant_by_size(&self, max_size: usize) {
408409
// Calculate used size and collect leaves
409410
let mut stack = vec![Arc::clone(&self.root)];
410411
let mut pq = BinaryHeap::new();
@@ -483,6 +484,46 @@ impl Tree {
483484
}
484485
}
485486

487+
pub fn remove_tenant(&self, tenant: &str) {
488+
// 1. Find all the leaves for the tenant
489+
let mut stack = vec![Arc::clone(&self.root)];
490+
let mut queue = VecDeque::new();
491+
492+
while let Some(curr) = stack.pop() {
493+
for child in curr.children.iter() {
494+
stack.push(Arc::clone(child.value()));
495+
}
496+
497+
if Tree::leaf_of(&curr).contains(&tenant.to_string()) {
498+
queue.push_back(Arc::clone(&curr));
499+
}
500+
}
501+
502+
// 2. Start from the leaves and traverse up to the root, removing the tenant from each node
503+
while let Some(curr) = queue.pop_front() {
504+
// remove tenant from node
505+
curr.tenant_last_access_time.remove(&tenant.to_string());
506+
507+
// remove empty nodes
508+
if curr.children.is_empty() && curr.tenant_last_access_time.is_empty() {
509+
if let Some(parent) = curr.parent.read().unwrap().as_ref() {
510+
let first_char = curr.text.read().unwrap().chars().next().unwrap();
511+
parent.children.remove(&first_char);
512+
}
513+
}
514+
515+
// add parent to queue if it becomes a leaf
516+
if let Some(parent) = curr.parent.read().unwrap().as_ref() {
517+
if Tree::leaf_of(parent).contains(&tenant.to_string()) {
518+
queue.push_back(Arc::clone(&parent));
519+
}
520+
}
521+
}
522+
523+
// 3. Remove the tenant from the tenant_char_count map
524+
self.tenant_char_count.remove(&tenant.to_string());
525+
}
526+
486527
pub fn get_tenant_char_count(&self) -> HashMap<String, usize> {
487528
self.tenant_char_count
488529
.iter()
@@ -673,7 +714,7 @@ mod tests {
673714
);
674715

675716
// Test eviction
676-
tree.evict_tenant_data(3); // This should evict tenants with more than 3 chars
717+
tree.evict_tenant_by_size(3); // This should evict tenants with more than 3 chars
677718

678719
let post_eviction_smallest = tree.get_smallest_tenant();
679720
println!("Smallest tenant after eviction: {}", post_eviction_smallest);
@@ -754,7 +795,7 @@ mod tests {
754795
);
755796

756797
// Phase 4: Eviction test
757-
tree.evict_tenant_data(10);
798+
tree.evict_tenant_by_size(10);
758799

759800
let computed_sizes = tree.get_used_size_per_tenant();
760801
let maintained_counts: HashMap<String, usize> = tree
@@ -1132,7 +1173,7 @@ mod tests {
11321173
assert_eq!(sizes_before.get("tenant2").unwrap(), &10); // "hello" + "world" = 10
11331174

11341175
// Evict - should remove "hello" from tenant2 as it's the oldest
1135-
tree.evict_tenant_data(max_size);
1176+
tree.evict_tenant_by_size(max_size);
11361177

11371178
tree.pretty_print();
11381179

@@ -1168,7 +1209,7 @@ mod tests {
11681209
}
11691210

11701211
// Perform eviction
1171-
tree.evict_tenant_data(max_size);
1212+
tree.evict_tenant_by_size(max_size);
11721213

11731214
// Check sizes after eviction
11741215
let sizes_after = tree.get_used_size_per_tenant();
@@ -1200,7 +1241,7 @@ mod tests {
12001241
let handle = thread::spawn(move || {
12011242
while start_time.elapsed() < test_duration {
12021243
// Run eviction
1203-
tree.evict_tenant_data(max_size);
1244+
tree.evict_tenant_by_size(max_size);
12041245

12051246
// Sleep for 5 seconds
12061247
thread::sleep(Duration::from_secs(5));
@@ -1245,7 +1286,7 @@ mod tests {
12451286
}
12461287

12471288
// final eviction
1248-
tree.evict_tenant_data(max_size);
1289+
tree.evict_tenant_by_size(max_size);
12491290

12501291
// Final size check
12511292
let final_sizes = tree.get_used_size_per_tenant();
@@ -1352,4 +1393,91 @@ mod tests {
13521393
assert_eq!(tree.prefix_match_tenant("hello", "tenant3"), ""); // Non-existent tenant
13531394
assert_eq!(tree.prefix_match_tenant("help", "tenant3"), ""); // Non-existent tenant
13541395
}
1396+
1397+
#[test]
1398+
fn test_simple_tenant_eviction() {
1399+
let tree = Tree::new();
1400+
1401+
// Insert data for multiple tenants
1402+
tree.insert("hello", "tenant1");
1403+
tree.insert("world", "tenant1");
1404+
tree.insert("hello", "tenant2");
1405+
tree.insert("help", "tenant2");
1406+
1407+
// Verify initial state
1408+
let initial_sizes = tree.get_used_size_per_tenant();
1409+
assert_eq!(initial_sizes.get("tenant1").unwrap(), &10); // "hello" + "world"
1410+
assert_eq!(initial_sizes.get("tenant2").unwrap(), &6); // "hello" + "p"
1411+
1412+
// Evict tenant1
1413+
tree.remove_tenant("tenant1");
1414+
1415+
// Verify after eviction
1416+
let final_sizes = tree.get_used_size_per_tenant();
1417+
assert!(
1418+
!final_sizes.contains_key("tenant1"),
1419+
"tenant1 should be completely removed"
1420+
);
1421+
assert_eq!(
1422+
final_sizes.get("tenant2").unwrap(),
1423+
&6,
1424+
"tenant2 should be unaffected"
1425+
);
1426+
1427+
// Verify tenant1's data is inaccessible
1428+
assert_eq!(tree.prefix_match_tenant("hello", "tenant1"), "");
1429+
assert_eq!(tree.prefix_match_tenant("world", "tenant1"), "");
1430+
1431+
// Verify tenant2's data is still accessible
1432+
assert_eq!(tree.prefix_match_tenant("hello", "tenant2"), "hello");
1433+
assert_eq!(tree.prefix_match_tenant("help", "tenant2"), "help");
1434+
}
1435+
1436+
#[test]
1437+
fn test_complex_tenant_eviction() {
1438+
let tree = Tree::new();
1439+
1440+
// Create a more complex tree structure with shared prefixes
1441+
tree.insert("apple", "tenant1");
1442+
tree.insert("application", "tenant1");
1443+
tree.insert("apple", "tenant2");
1444+
tree.insert("appetite", "tenant2");
1445+
tree.insert("banana", "tenant1");
1446+
tree.insert("banana", "tenant2");
1447+
tree.insert("ball", "tenant2");
1448+
1449+
// Verify initial state
1450+
let initial_sizes = tree.get_used_size_per_tenant();
1451+
println!("Initial sizes: {:?}", initial_sizes);
1452+
tree.pretty_print();
1453+
1454+
// Evict tenant1
1455+
tree.remove_tenant("tenant1");
1456+
1457+
// Verify final state
1458+
let final_sizes = tree.get_used_size_per_tenant();
1459+
println!("Final sizes: {:?}", final_sizes);
1460+
tree.pretty_print();
1461+
1462+
// Verify tenant1 is completely removed
1463+
assert!(
1464+
!final_sizes.contains_key("tenant1"),
1465+
"tenant1 should be completely removed"
1466+
);
1467+
1468+
// Verify all tenant1's data is inaccessible
1469+
assert_eq!(tree.prefix_match_tenant("apple", "tenant1"), "");
1470+
assert_eq!(tree.prefix_match_tenant("application", "tenant1"), "");
1471+
assert_eq!(tree.prefix_match_tenant("banana", "tenant1"), "");
1472+
1473+
// Verify tenant2's data is intact
1474+
assert_eq!(tree.prefix_match_tenant("apple", "tenant2"), "apple");
1475+
assert_eq!(tree.prefix_match_tenant("appetite", "tenant2"), "appetite");
1476+
assert_eq!(tree.prefix_match_tenant("banana", "tenant2"), "banana");
1477+
assert_eq!(tree.prefix_match_tenant("ball", "tenant2"), "ball");
1478+
1479+
// Verify the tree structure is still valid for tenant2
1480+
let tenant2_size = final_sizes.get("tenant2").unwrap();
1481+
assert_eq!(tenant2_size, &(5 + 5 + 6 + 2)); // "apple" + "etite" + "banana" + "ll"
1482+
}
13551483
}

0 commit comments

Comments
 (0)