ISSUE #518 - Make hot cache invalidation explicit

The "add ip mapping" call was flushing the XDP 'hot cache'. That was fine in testing, and not working well at scale:

* Each table wipe locks the associated underlying hash map.
* Running 1200+ clears means waiting for 1200 occasions in which packets don't already have a hold on the lock (eBPF doesn't expose the locking mechanisms, so we can't change that behavior)
* The result is that under load, it can take a REALLY long time to reload the shaping stack without pausing traffic.

Instead, add_ip_mapping no longer clears the hot cache. An explicit invalidation call has been added to the bus, and added to the end of the batched IP map updates. This reduces the number of table locks from 1200+ to 2 (once for clearing the IP map, once for clearing the cache).
This commit is contained in:
Herbert Wolverson 2024-07-11 09:22:37 -05:00
parent a13fb0482e
commit abdcdafef5
7 changed files with 22 additions and 3 deletions

View File

@ -954,6 +954,7 @@ def refreshShapers():
print("Executing XDP-CPUMAP-TC IP filter commands")
numXdpCommands = ipMapBatch.length()
if enable_actual_shell_commands():
ipMapBatch.finish_ip_mappings()
ipMapBatch.submit()
#for command in xdpCPUmapCommands:
# logging.info(command)

View File

@ -70,6 +70,11 @@ pub enum BusRequest {
upload: bool,
},
/// After a batch of `MapIpToFlow` requests, this command will
/// clear the hot cache, forcing the XDP program to re-read the
/// mapping table.
ClearHotCache,
/// Requests that the XDP program unmap an IP address/subnet from
/// the traffic management system.
DelIpFlow {

View File

@ -234,6 +234,12 @@ impl BatchedCommands {
}
}
pub fn finish_ip_mappings(&mut self) -> PyResult<()> {
let request = BusRequest::ClearHotCache;
self.batch.push(request);
Ok(())
}
pub fn length(&self) -> PyResult<usize> {
Ok(self.batch.len())
}

View File

@ -36,7 +36,8 @@ pub fn add_ip_to_tc(
let mut value =
IpHashData { cpu: ip_to_add.cpu, tc_handle: ip_to_add.handle() };
bpf_map.insert_or_update(&mut key, &mut value)?;
clear_hot_cache()?;
// Removed because it should be cleared explicitly at the end of a batch operation
//clear_hot_cache()?;
Ok(())
}
@ -97,7 +98,7 @@ pub fn list_mapped_ips() -> Result<Vec<(IpHashKey, IpHashData)>> {
/// Clears the "hot cache", which should be done whenever you change the IP
/// mappings - because otherwise cached data will keep going to the previous
/// destinations.
fn clear_hot_cache() -> Result<()> {
pub fn clear_hot_cache() -> Result<()> {
let mut bpf_map = BpfMap::<XdpIpAddress, IpHashData>::from_path("/sys/fs/bpf/ip_to_cpu_and_tc_hotcache")?;
bpf_map.clear()?;
Ok(())

View File

@ -23,7 +23,7 @@ mod bpf_iterator;
pub mod flowbee_data;
pub use ip_mapping::{
add_ip_to_tc, clear_ips_from_tc, del_ip_from_tc, list_mapped_ips,
add_ip_to_tc, clear_ips_from_tc, del_ip_from_tc, list_mapped_ips, clear_hot_cache,
};
pub use kernel_wrapper::LibreQoSKernels;
pub use linux::num_possible_cpus;

View File

@ -19,6 +19,10 @@ pub(crate) fn map_ip_to_flow(
expect_ack(lqos_sys::add_ip_to_tc(ip_address, *tc_handle, cpu, upload))
}
pub(crate) fn clear_hot_cache() -> BusResponse {
expect_ack(lqos_sys::clear_hot_cache())
}
pub(crate) fn del_ip_flow(ip_address: &str, upload: bool) -> BusResponse {
expect_ack(lqos_sys::del_ip_from_tc(ip_address, upload))
}

View File

@ -37,6 +37,7 @@ mod preflight_checks;
// Use JemAllocator only on supported platforms
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use jemallocator::Jemalloc;
use crate::ip_mapping::clear_hot_cache;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[global_allocator]
@ -169,6 +170,7 @@ fn handle_bus_requests(
BusRequest::MapIpToFlow { ip_address, tc_handle, cpu, upload } => {
map_ip_to_flow(ip_address, tc_handle, *cpu, *upload)
}
BusRequest::ClearHotCache => clear_hot_cache(),
BusRequest::DelIpFlow { ip_address, upload } => {
del_ip_flow(ip_address, *upload)
}