diff --git a/Cargo.lock b/Cargo.lock
index c755be63042b4efda333ecbb172a0ccf9f0708b6..40aaf2d0d9e7e18d428e68768e07fa05bb6990e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3332,7 +3332,6 @@ dependencies = [
  "cumulus-primitives-parachain-inherent",
  "cumulus-relay-chain-interface",
  "futures",
- "lru 0.10.1",
  "parity-scale-codec",
  "polkadot-node-primitives",
  "polkadot-node-subsystem",
@@ -3344,6 +3343,7 @@ dependencies = [
  "sc-consensus-babe",
  "sc-consensus-slots",
  "sc-telemetry",
+ "schnellru",
  "sp-api",
  "sp-application-crypto",
  "sp-block-builder",
@@ -3822,7 +3822,6 @@ dependencies = [
  "cumulus-relay-chain-interface",
  "cumulus-relay-chain-rpc-interface",
  "futures",
- "lru 0.11.0",
  "polkadot-availability-recovery",
  "polkadot-collator-protocol",
  "polkadot-core-primitives",
@@ -3839,6 +3838,7 @@ dependencies = [
  "sc-service",
  "sc-tracing",
  "sc-utils",
+ "schnellru",
  "sp-api",
  "sp-consensus",
  "sp-consensus-babe",
@@ -3857,7 +3857,6 @@ dependencies = [
  "futures",
  "futures-timer",
  "jsonrpsee",
- "lru 0.11.0",
  "parity-scale-codec",
  "pin-project",
  "polkadot-overseer",
@@ -3865,6 +3864,7 @@ dependencies = [
  "sc-client-api",
  "sc-rpc-api",
  "sc-service",
+ "schnellru",
  "serde",
  "serde_json",
  "smoldot",
@@ -7765,9 +7765,6 @@ name = "lru"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5"
-dependencies = [
- "hashbrown 0.14.0",
-]
 
 [[package]]
 name = "lru-cache"
@@ -11696,7 +11693,6 @@ dependencies = [
  "fatality",
  "futures",
  "futures-timer",
- "lru 0.11.0",
  "parity-scale-codec",
  "polkadot-erasure-coding",
  "polkadot-node-network-protocol",
@@ -11708,6 +11704,7 @@ dependencies = [
  "polkadot-primitives-test-helpers",
  "rand 0.8.5",
  "sc-network",
+ "schnellru",
  "sp-core",
  "sp-keyring",
  "sp-keystore",
@@ -11726,7 +11723,6 @@ dependencies = [
  "futures",
  "futures-timer",
  "log",
- "lru 0.11.0",
  "parity-scale-codec",
  "polkadot-erasure-coding",
  "polkadot-node-network-protocol",
@@ -11738,6 +11734,7 @@ dependencies = [
  "polkadot-primitives-test-helpers",
  "rand 0.8.5",
  "sc-network",
+ "schnellru",
  "sp-application-crypto",
  "sp-core",
  "sp-keyring",
@@ -11827,7 +11824,6 @@ dependencies = [
  "futures-timer",
  "indexmap 1.9.3",
  "lazy_static",
- "lru 0.11.0",
  "parity-scale-codec",
  "polkadot-erasure-coding",
  "polkadot-node-network-protocol",
@@ -11839,6 +11835,7 @@ dependencies = [
  "polkadot-primitives-test-helpers",
  "sc-keystore",
  "sc-network",
+ "schnellru",
  "sp-application-crypto",
  "sp-keyring",
  "sp-keystore",
@@ -11951,7 +11948,6 @@ dependencies = [
  "futures-timer",
  "kvdb",
  "kvdb-memorydb",
- "lru 0.11.0",
  "merlin 2.0.1",
  "parity-scale-codec",
  "parking_lot 0.12.1",
@@ -11965,6 +11961,7 @@ dependencies = [
  "polkadot-primitives-test-helpers",
  "rand_core 0.5.1",
  "sc-keystore",
+ "schnellru",
  "schnorrkel 0.9.1",
  "sp-application-crypto",
  "sp-consensus",
@@ -12125,7 +12122,6 @@ dependencies = [
  "futures-timer",
  "kvdb",
  "kvdb-memorydb",
- "lru 0.11.0",
  "parity-scale-codec",
  "polkadot-node-primitives",
  "polkadot-node-subsystem",
@@ -12134,6 +12130,7 @@ dependencies = [
  "polkadot-primitives",
  "polkadot-primitives-test-helpers",
  "sc-keystore",
+ "schnellru",
  "sp-application-crypto",
  "sp-core",
  "sp-keyring",
@@ -12333,7 +12330,6 @@ version = "1.0.0"
 dependencies = [
  "async-trait",
  "futures",
- "lru 0.11.0",
  "polkadot-node-metrics",
  "polkadot-node-primitives",
  "polkadot-node-subsystem",
@@ -12341,6 +12337,7 @@ dependencies = [
  "polkadot-node-subsystem-types",
  "polkadot-primitives",
  "polkadot-primitives-test-helpers",
+ "schnellru",
  "sp-api",
  "sp-consensus-babe",
  "sp-core",
@@ -12504,7 +12501,6 @@ dependencies = [
  "kvdb-shared-tests",
  "lazy_static",
  "log",
- "lru 0.11.0",
  "parity-db",
  "parity-scale-codec",
  "parking_lot 0.11.2",
@@ -12520,6 +12516,7 @@ dependencies = [
  "polkadot-primitives-test-helpers",
  "prioritized-metered-channel",
  "rand 0.8.5",
+ "schnellru",
  "sp-application-crypto",
  "sp-core",
  "sp-keystore",
@@ -12537,7 +12534,6 @@ dependencies = [
  "femme",
  "futures",
  "futures-timer",
- "lru 0.11.0",
  "orchestra",
  "parking_lot 0.12.1",
  "polkadot-node-metrics",
@@ -12548,6 +12544,7 @@ dependencies = [
  "polkadot-primitives-test-helpers",
  "prioritized-metered-channel",
  "sc-client-api",
+ "schnellru",
  "sp-api",
  "sp-core",
  "tikv-jemalloc-ctl",
@@ -12992,7 +12989,6 @@ dependencies = [
  "kvdb",
  "kvdb-rocksdb",
  "log",
- "lru 0.11.0",
  "mmr-gadget",
  "pallet-babe",
  "pallet-im-online",
@@ -13066,6 +13062,7 @@ dependencies = [
  "sc-telemetry",
  "sc-transaction-pool",
  "sc-transaction-pool-api",
+ "schnellru",
  "serde",
  "serde_json",
  "serial_test",
diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml
index d5781d5ed8125ca99a59d4a3746c4f1e253a053f..030ab705f1609583df7fe67e00da4c82551d30ae 100644
--- a/cumulus/client/consensus/aura/Cargo.toml
+++ b/cumulus/client/consensus/aura/Cargo.toml
@@ -10,7 +10,7 @@ async-trait = "0.1.73"
 codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] }
 futures = "0.3.28"
 tracing = "0.1.37"
-lru = "0.10.0"
+schnellru = "0.2.1"
 
 # Substrate
 sc-client-api = { path = "../../../../substrate/client/api" }
diff --git a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs
index 682b7b91a655696b23a6d80dda068eca9669660f..919e0da39b1a98663427dc632b0949037e526f7f 100644
--- a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs
+++ b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs
@@ -21,7 +21,7 @@
 /// should be thrown out and which ones should be kept.
 use codec::Codec;
 use cumulus_client_consensus_common::ParachainBlockImportMarker;
-use lru::LruCache;
+use schnellru::{ByLength, LruMap};
 
 use sc_consensus::{
 	import_queue::{BasicQueue, Verifier as VerifierT},
@@ -36,27 +36,28 @@ use sp_consensus_aura::{AuraApi, Slot, SlotDuration};
 use sp_core::crypto::Pair;
 use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
 use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
-use std::{fmt::Debug, num::NonZeroUsize, sync::Arc};
+use std::{fmt::Debug, sync::Arc};
 
-const LRU_WINDOW: usize = 256;
+const LRU_WINDOW: u32 = 256;
 const EQUIVOCATION_LIMIT: usize = 16;
 
 struct NaiveEquivocationDefender {
-	cache: LruCache<u64, usize>,
+	cache: LruMap<u64, usize>,
 }
 
 impl Default for NaiveEquivocationDefender {
 	fn default() -> Self {
-		NaiveEquivocationDefender {
-			cache: LruCache::new(NonZeroUsize::new(LRU_WINDOW).expect("window > 0; qed")),
-		}
+		NaiveEquivocationDefender { cache: LruMap::new(ByLength::new(LRU_WINDOW)) }
 	}
 }
 
 impl NaiveEquivocationDefender {
 	// return `true` if equivocation is beyond the limit.
 	fn insert_and_check(&mut self, slot: Slot) -> bool {
-		let val = self.cache.get_or_insert_mut(*slot, || 0);
+		let val = self
+			.cache
+			.get_or_insert(*slot, || 0)
+			.expect("insertion with ByLength limiter always succeeds; qed");
 		if *val == EQUIVOCATION_LIMIT {
 			true
 		} else {
diff --git a/cumulus/client/relay-chain-minimal-node/Cargo.toml b/cumulus/client/relay-chain-minimal-node/Cargo.toml
index 7fc53d17470a67533296a7935bab87d54ff8d8e6..c3914573aabf3250271a5f87dfcba2007e4dc87c 100644
--- a/cumulus/client/relay-chain-minimal-node/Cargo.toml
+++ b/cumulus/client/relay-chain-minimal-node/Cargo.toml
@@ -36,7 +36,7 @@ cumulus-relay-chain-rpc-interface = { path = "../relay-chain-rpc-interface" }
 cumulus-primitives-core = { path = "../../primitives/core" }
 
 array-bytes = "6.1"
-lru = "0.11.0"
+schnellru = "0.2.1"
 tracing = "0.1.37"
 async-trait = "0.1.73"
 futures = "0.3.28"
diff --git a/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs b/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs
index 4d843eb224f2cf754aea3defbcaf24988df650a7..0acd04e73cd83d03be20898141bed9495752a8d7 100644
--- a/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs
+++ b/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs
@@ -15,7 +15,7 @@
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
 use futures::{select, StreamExt};
-use lru::LruCache;
+use schnellru::{ByLength, LruMap};
 use std::sync::Arc;
 
 use polkadot_availability_recovery::AvailabilityRecoverySubsystem;
@@ -157,7 +157,7 @@ fn build_overseer(
 		.span_per_active_leaf(Default::default())
 		.active_leaves(Default::default())
 		.supports_parachains(runtime_client)
-		.known_leaves(LruCache::new(KNOWN_LEAVES_CACHE_SIZE))
+		.known_leaves(LruMap::new(ByLength::new(KNOWN_LEAVES_CACHE_SIZE)))
 		.metrics(Metrics::register(registry)?)
 		.spawner(spawner);
 
diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml
index d73b9ae8989ad72152f2db1aedd0e91da944674d..2356e747333676e39ade7b1f287642f5d73ed0de 100644
--- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml
+++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml
@@ -34,7 +34,7 @@ async-trait = "0.1.73"
 url = "2.4.0"
 serde_json = "1.0.105"
 serde = "1.0.183"
-lru = "0.11.0"
+schnellru = "0.2.1"
 smoldot = { version = "0.11.0",  default_features = false, features = ["std"]}
 smoldot-light = { version = "0.9.0", default_features = false, features = ["std"] }
 either = "1.8.1"
diff --git a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs
index 5add8a96ef10084e46fd8e3378e9c38e103e1b37..5a35b2b5bfa0724a0dedc76eae871154d8044bd2 100644
--- a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs
+++ b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs
@@ -31,10 +31,10 @@ use jsonrpsee::{
 	},
 	ws_client::WsClientBuilder,
 };
-use lru::LruCache;
 use sc_rpc_api::chain::ChainApiClient;
+use schnellru::{ByLength, LruMap};
 use sp_runtime::generic::SignedBlock;
-use std::{num::NonZeroUsize, sync::Arc};
+use std::sync::Arc;
 use tokio::sync::mpsc::{
 	channel as tokio_channel, Receiver as TokioReceiver, Sender as TokioSender,
 };
@@ -307,8 +307,7 @@ impl ReconnectingWebsocketWorker {
 			return
 		};
 
-		let mut imported_blocks_cache =
-			LruCache::new(NonZeroUsize::new(40).expect("40 is nonzero; qed."));
+		let mut imported_blocks_cache = LruMap::new(ByLength::new(40));
 		let mut should_reconnect = ConnectionStatus::Connected;
 		let mut last_seen_finalized_num: RelayNumber = 0;
 		loop {
@@ -365,7 +364,7 @@ impl ReconnectingWebsocketWorker {
 					match import_event {
 						Some(Ok(header)) => {
 							let hash = header.hash();
-							if imported_blocks_cache.contains(&hash) {
+							if imported_blocks_cache.peek(&hash).is_some() {
 								tracing::debug!(
 									target: LOG_TARGET,
 									number = header.number,
@@ -374,7 +373,7 @@ impl ReconnectingWebsocketWorker {
 								);
 								continue;
 							}
-							imported_blocks_cache.put(hash, ());
+							imported_blocks_cache.insert(hash, ());
 							distribute_header(header, &mut self.imported_header_listeners);
 						},
 						None => {
diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml
index 5ae99d44d0a549e087a342d0d49889fca707e847..593f6bff4c830cd894d2d059c06f62fe87fe0c7d 100644
--- a/polkadot/node/core/approval-voting/Cargo.toml
+++ b/polkadot/node/core/approval-voting/Cargo.toml
@@ -11,7 +11,7 @@ futures-timer = "3.0.2"
 parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] }
 gum = { package = "tracing-gum", path = "../../gum" }
 bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }
-lru = "0.11.0"
+schnellru = "0.2.1"
 merlin = "2.0"
 schnorrkel = "0.9.1"
 kvdb = "0.13.0"
diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs
index c504ba71b3c2d801a8f97b3920404a6cc99d7776..6f376fc6fcc1e8f952d72776ec2b24a9c51de6fe 100644
--- a/polkadot/node/core/approval-voting/src/import.rs
+++ b/polkadot/node/core/approval-voting/src/import.rs
@@ -643,7 +643,7 @@ pub(crate) mod tests {
 			blank_state(),
 			RuntimeInfo::new_with_config(RuntimeInfoConfig {
 				keystore: None,
-				session_cache_lru_size: DISPUTE_WINDOW.into(),
+				session_cache_lru_size: DISPUTE_WINDOW.get(),
 			}),
 		)
 	}
@@ -755,7 +755,7 @@ pub(crate) mod tests {
 
 			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
 				keystore: None,
-				session_cache_lru_size: DISPUTE_WINDOW.into(),
+				session_cache_lru_size: DISPUTE_WINDOW.get(),
 			});
 
 			let header = header.clone();
@@ -878,7 +878,7 @@ pub(crate) mod tests {
 		let test_fut = {
 			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
 				keystore: None,
-				session_cache_lru_size: DISPUTE_WINDOW.into(),
+				session_cache_lru_size: DISPUTE_WINDOW.get(),
 			});
 
 			let header = header.clone();
@@ -994,7 +994,7 @@ pub(crate) mod tests {
 		let test_fut = {
 			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
 				keystore: None,
-				session_cache_lru_size: DISPUTE_WINDOW.into(),
+				session_cache_lru_size: DISPUTE_WINDOW.get(),
 			});
 
 			let header = header.clone();
@@ -1092,7 +1092,7 @@ pub(crate) mod tests {
 
 			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
 				keystore: None,
-				session_cache_lru_size: DISPUTE_WINDOW.into(),
+				session_cache_lru_size: DISPUTE_WINDOW.get(),
 			});
 
 			let header = header.clone();
diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs
index b29e47b4c4354f559ba9daaa0f56e7effdede9a9..e99572b31c4b5fb62abc57edf6cb8c507d3b257b 100644
--- a/polkadot/node/core/approval-voting/src/lib.rs
+++ b/polkadot/node/core/approval-voting/src/lib.rs
@@ -69,11 +69,12 @@ use std::{
 	collections::{
 		btree_map::Entry as BTMEntry, hash_map::Entry as HMEntry, BTreeMap, HashMap, HashSet,
 	},
-	num::NonZeroUsize,
 	sync::Arc,
 	time::Duration,
 };
 
+use schnellru::{ByLength, LruMap};
+
 use approval_checking::RequiredTranches;
 use criteria::{AssignmentCriteria, RealAssignmentCriteria};
 use persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry};
@@ -102,10 +103,7 @@ const APPROVAL_CHECKING_TIMEOUT: Duration = Duration::from_secs(120);
 /// Value rather arbitrarily: Should not be hit in practice, it exists to more easily diagnose dead
 /// lock issues for example.
 const WAIT_FOR_SIGS_TIMEOUT: Duration = Duration::from_millis(500);
-const APPROVAL_CACHE_SIZE: NonZeroUsize = match NonZeroUsize::new(1024) {
-	Some(cap) => cap,
-	None => panic!("Approval cache size must be non-zero."),
-};
+const APPROVAL_CACHE_SIZE: u32 = 1024;
 
 const TICK_TOO_FAR_IN_FUTURE: Tick = 20; // 10 seconds.
 const APPROVAL_DELAY: Tick = 2;
@@ -627,7 +625,7 @@ impl CurrentlyCheckingSet {
 
 	pub async fn next(
 		&mut self,
-		approvals_cache: &mut lru::LruCache<CandidateHash, ApprovalOutcome>,
+		approvals_cache: &mut LruMap<CandidateHash, ApprovalOutcome>,
 	) -> (HashSet<Hash>, ApprovalState) {
 		if !self.currently_checking.is_empty() {
 			if let Some(approval_state) = self.currently_checking.next().await {
@@ -635,7 +633,8 @@ impl CurrentlyCheckingSet {
 					.candidate_hash_map
 					.remove(&approval_state.candidate_hash)
 					.unwrap_or_default();
-				approvals_cache.put(approval_state.candidate_hash, approval_state.approval_outcome);
+				approvals_cache
+					.insert(approval_state.candidate_hash, approval_state.approval_outcome);
 				return (out, approval_state)
 			}
 		}
@@ -782,11 +781,11 @@ where
 	// `None` on start-up. Gets initialized/updated on leaf update
 	let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig {
 		keystore: None,
-		session_cache_lru_size: DISPUTE_WINDOW.into(),
+		session_cache_lru_size: DISPUTE_WINDOW.get(),
 	});
 	let mut wakeups = Wakeups::default();
 	let mut currently_checking_set = CurrentlyCheckingSet::default();
-	let mut approvals_cache = lru::LruCache::new(APPROVAL_CACHE_SIZE);
+	let mut approvals_cache = LruMap::new(ByLength::new(APPROVAL_CACHE_SIZE));
 
 	let mut last_finalized_height: Option<BlockNumber> = {
 		let (tx, rx) = oneshot::channel();
@@ -922,7 +921,7 @@ async fn handle_actions<Context>(
 	metrics: &Metrics,
 	wakeups: &mut Wakeups,
 	currently_checking_set: &mut CurrentlyCheckingSet,
-	approvals_cache: &mut lru::LruCache<CandidateHash, ApprovalOutcome>,
+	approvals_cache: &mut LruMap<CandidateHash, ApprovalOutcome>,
 	mode: &mut Mode,
 	actions: Vec<Action>,
 ) -> SubsystemResult<bool> {
diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml
index 465c108211e333ce9bd732806cfbd0bc95a5403f..5698114d278f866c92b91d6d0ca455fc71648976 100644
--- a/polkadot/node/core/dispute-coordinator/Cargo.toml
+++ b/polkadot/node/core/dispute-coordinator/Cargo.toml
@@ -11,7 +11,7 @@ gum = { package = "tracing-gum", path = "../../gum" }
 parity-scale-codec = "3.6.1"
 kvdb = "0.13.0"
 thiserror = "1.0.31"
-lru = "0.11.0"
+schnellru = "0.2.1"
 fatality = "0.0.6"
 
 polkadot-primitives = { path = "../../../primitives" }
diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs
index a2c500e08e283fdd3256ab46bef7d3550bcf3d92..f7e3d0657f2d941ff9a6b96dbbbb8c977663d566 100644
--- a/polkadot/node/core/dispute-coordinator/src/lib.rs
+++ b/polkadot/node/core/dispute-coordinator/src/lib.rs
@@ -25,7 +25,7 @@
 //! When importing a dispute vote from another node, this will trigger dispute participation to
 //! recover and validate the block.
 
-use std::{num::NonZeroUsize, sync::Arc};
+use std::sync::Arc;
 
 use futures::FutureExt;
 
@@ -222,8 +222,7 @@ impl DisputeCoordinatorSubsystem {
 			// keep all sessions for a dispute window
 			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
 				keystore: None,
-				session_cache_lru_size: NonZeroUsize::new(DISPUTE_WINDOW.get() as usize)
-					.expect("DISPUTE_WINDOW can't be 0; qed."),
+				session_cache_lru_size: DISPUTE_WINDOW.get(),
 			});
 			let mut overlay_db = OverlayedBackend::new(&mut backend);
 			let (
diff --git a/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs b/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs
index f93ad0abab91efccabf48f2d8eece0d621ea81a4..67434bca85d418b0dccd7b96173935e72d7942cc 100644
--- a/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs
+++ b/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs
@@ -14,13 +14,10 @@
 // You should have received a copy of the GNU General Public License
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
-use std::{
-	collections::{BTreeMap, HashSet},
-	num::NonZeroUsize,
-};
+use std::collections::{BTreeMap, HashSet};
 
 use futures::channel::oneshot;
-use lru::LruCache;
+use schnellru::{ByLength, LruMap};
 
 use polkadot_node_primitives::{DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION, MAX_FINALITY_LAG};
 use polkadot_node_subsystem::{
@@ -52,10 +49,7 @@ mod candidates;
 /// `last_observed_blocks` LRU. This means, this value should the very least be as large as the
 /// number of expected forks for keeping chain scraping efficient. Making the LRU much larger than
 /// that has very limited use.
-const LRU_OBSERVED_BLOCKS_CAPACITY: NonZeroUsize = match NonZeroUsize::new(20) {
-	Some(cap) => cap,
-	None => panic!("Observed blocks cache size must be non-zero"),
-};
+const LRU_OBSERVED_BLOCKS_CAPACITY: u32 = 20;
 
 /// `ScrapedUpdates`
 ///
@@ -173,7 +167,7 @@ pub struct ChainScraper {
 	///
 	/// We assume that ancestors of cached blocks are already processed, i.e. we have saved
 	/// corresponding included candidates.
-	last_observed_blocks: LruCache<Hash, ()>,
+	last_observed_blocks: LruMap<Hash, ()>,
 	/// Maps included candidate hashes to one or more relay block heights and hashes.
 	/// These correspond to all the relay blocks which marked a candidate as included,
 	/// and are needed to apply reversions in case a dispute is concluded against the
@@ -202,7 +196,7 @@ impl ChainScraper {
 		let mut s = Self {
 			included_candidates: candidates::ScrapedCandidates::new(),
 			backed_candidates: candidates::ScrapedCandidates::new(),
-			last_observed_blocks: LruCache::new(LRU_OBSERVED_BLOCKS_CAPACITY),
+			last_observed_blocks: LruMap::new(ByLength::new(LRU_OBSERVED_BLOCKS_CAPACITY)),
 			inclusions: Inclusions::new(),
 		};
 		let update =
@@ -288,7 +282,7 @@ impl ChainScraper {
 			},
 		}
 
-		self.last_observed_blocks.put(activated.hash, ());
+		self.last_observed_blocks.insert(activated.hash, ());
 
 		Ok(scraped_updates)
 	}
diff --git a/polkadot/node/core/runtime-api/Cargo.toml b/polkadot/node/core/runtime-api/Cargo.toml
index 1a74ccdf73dfd5d5b70fc629021828f34584d5b5..de165ebcc5c966245f3ebdfe010a68bd93e15294 100644
--- a/polkadot/node/core/runtime-api/Cargo.toml
+++ b/polkadot/node/core/runtime-api/Cargo.toml
@@ -8,7 +8,7 @@ license.workspace = true
 [dependencies]
 futures = "0.3.21"
 gum = { package = "tracing-gum", path = "../../gum" }
-lru = "0.11.0"
+schnellru = "0.2.1"
 
 sp-consensus-babe = { path = "../../../../substrate/primitives/consensus/babe" }
 
diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs
index 26aaf3fb6ec891b77b6d714dbf5c680a9c33b7a1..cd22f37ddee43e096ffb5414f61293e609ccd7cd 100644
--- a/polkadot/node/core/runtime-api/src/cache.rs
+++ b/polkadot/node/core/runtime-api/src/cache.rs
@@ -14,9 +14,9 @@
 // You should have received a copy of the GNU General Public License
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
-use std::{collections::btree_map::BTreeMap, num::NonZeroUsize};
+use std::collections::btree_map::BTreeMap;
 
-use lru::LruCache;
+use schnellru::{ByLength, LruMap};
 use sp_consensus_babe::Epoch;
 
 use polkadot_primitives::{
@@ -32,77 +32,74 @@ use polkadot_primitives::{
 /// much if finality stalls (we only query state for unfinalized blocks + maybe latest finalized).
 /// In any case, a cache is an optimization. We should avoid a situation where having a large cache
 /// leads to OOM or puts pressure on other important stuff like PVF execution/preparation.
-const DEFAULT_CACHE_CAP: NonZeroUsize = match NonZeroUsize::new(128) {
-	Some(cap) => cap,
-	None => panic!("lru capacity must be non-zero"),
-};
+const DEFAULT_CACHE_CAP: u32 = 128;
 
 pub(crate) struct RequestResultCache {
-	authorities: LruCache<Hash, Vec<AuthorityDiscoveryId>>,
-	validators: LruCache<Hash, Vec<ValidatorId>>,
-	validator_groups: LruCache<Hash, (Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>,
-	availability_cores: LruCache<Hash, Vec<CoreState>>,
+	authorities: LruMap<Hash, Vec<AuthorityDiscoveryId>>,
+	validators: LruMap<Hash, Vec<ValidatorId>>,
+	validator_groups: LruMap<Hash, (Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>,
+	availability_cores: LruMap<Hash, Vec<CoreState>>,
 	persisted_validation_data:
-		LruCache<(Hash, ParaId, OccupiedCoreAssumption), Option<PersistedValidationData>>,
+		LruMap<(Hash, ParaId, OccupiedCoreAssumption), Option<PersistedValidationData>>,
 	assumed_validation_data:
-		LruCache<(ParaId, Hash), Option<(PersistedValidationData, ValidationCodeHash)>>,
-	check_validation_outputs: LruCache<(Hash, ParaId, CandidateCommitments), bool>,
-	session_index_for_child: LruCache<Hash, SessionIndex>,
-	validation_code: LruCache<(Hash, ParaId, OccupiedCoreAssumption), Option<ValidationCode>>,
-	validation_code_by_hash: LruCache<ValidationCodeHash, Option<ValidationCode>>,
-	candidate_pending_availability: LruCache<(Hash, ParaId), Option<CommittedCandidateReceipt>>,
-	candidate_events: LruCache<Hash, Vec<CandidateEvent>>,
-	session_executor_params: LruCache<SessionIndex, Option<ExecutorParams>>,
-	session_info: LruCache<SessionIndex, SessionInfo>,
-	dmq_contents: LruCache<(Hash, ParaId), Vec<InboundDownwardMessage<BlockNumber>>>,
+		LruMap<(ParaId, Hash), Option<(PersistedValidationData, ValidationCodeHash)>>,
+	check_validation_outputs: LruMap<(Hash, ParaId, CandidateCommitments), bool>,
+	session_index_for_child: LruMap<Hash, SessionIndex>,
+	validation_code: LruMap<(Hash, ParaId, OccupiedCoreAssumption), Option<ValidationCode>>,
+	validation_code_by_hash: LruMap<ValidationCodeHash, Option<ValidationCode>>,
+	candidate_pending_availability: LruMap<(Hash, ParaId), Option<CommittedCandidateReceipt>>,
+	candidate_events: LruMap<Hash, Vec<CandidateEvent>>,
+	session_executor_params: LruMap<SessionIndex, Option<ExecutorParams>>,
+	session_info: LruMap<SessionIndex, SessionInfo>,
+	dmq_contents: LruMap<(Hash, ParaId), Vec<InboundDownwardMessage<BlockNumber>>>,
 	inbound_hrmp_channels_contents:
-		LruCache<(Hash, ParaId), BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>,
-	current_babe_epoch: LruCache<Hash, Epoch>,
-	on_chain_votes: LruCache<Hash, Option<ScrapedOnChainVotes>>,
-	pvfs_require_precheck: LruCache<Hash, Vec<ValidationCodeHash>>,
+		LruMap<(Hash, ParaId), BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>,
+	current_babe_epoch: LruMap<Hash, Epoch>,
+	on_chain_votes: LruMap<Hash, Option<ScrapedOnChainVotes>>,
+	pvfs_require_precheck: LruMap<Hash, Vec<ValidationCodeHash>>,
 	validation_code_hash:
-		LruCache<(Hash, ParaId, OccupiedCoreAssumption), Option<ValidationCodeHash>>,
-	version: LruCache<Hash, u32>,
-	disputes: LruCache<Hash, Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>>,
+		LruMap<(Hash, ParaId, OccupiedCoreAssumption), Option<ValidationCodeHash>>,
+	version: LruMap<Hash, u32>,
+	disputes: LruMap<Hash, Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>>,
 	unapplied_slashes:
-		LruCache<Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>,
+		LruMap<Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>,
 	key_ownership_proof:
-		LruCache<(Hash, ValidatorId), Option<vstaging::slashing::OpaqueKeyOwnershipProof>>,
+		LruMap<(Hash, ValidatorId), Option<vstaging::slashing::OpaqueKeyOwnershipProof>>,
 
-	staging_para_backing_state: LruCache<(Hash, ParaId), Option<vstaging::BackingState>>,
-	staging_async_backing_params: LruCache<Hash, vstaging::AsyncBackingParams>,
+	staging_para_backing_state: LruMap<(Hash, ParaId), Option<vstaging::BackingState>>,
+	staging_async_backing_params: LruMap<Hash, vstaging::AsyncBackingParams>,
 }
 
 impl Default for RequestResultCache {
 	fn default() -> Self {
 		Self {
-			authorities: LruCache::new(DEFAULT_CACHE_CAP),
-			validators: LruCache::new(DEFAULT_CACHE_CAP),
-			validator_groups: LruCache::new(DEFAULT_CACHE_CAP),
-			availability_cores: LruCache::new(DEFAULT_CACHE_CAP),
-			persisted_validation_data: LruCache::new(DEFAULT_CACHE_CAP),
-			assumed_validation_data: LruCache::new(DEFAULT_CACHE_CAP),
-			check_validation_outputs: LruCache::new(DEFAULT_CACHE_CAP),
-			session_index_for_child: LruCache::new(DEFAULT_CACHE_CAP),
-			validation_code: LruCache::new(DEFAULT_CACHE_CAP),
-			validation_code_by_hash: LruCache::new(DEFAULT_CACHE_CAP),
-			candidate_pending_availability: LruCache::new(DEFAULT_CACHE_CAP),
-			candidate_events: LruCache::new(DEFAULT_CACHE_CAP),
-			session_executor_params: LruCache::new(DEFAULT_CACHE_CAP),
-			session_info: LruCache::new(DEFAULT_CACHE_CAP),
-			dmq_contents: LruCache::new(DEFAULT_CACHE_CAP),
-			inbound_hrmp_channels_contents: LruCache::new(DEFAULT_CACHE_CAP),
-			current_babe_epoch: LruCache::new(DEFAULT_CACHE_CAP),
-			on_chain_votes: LruCache::new(DEFAULT_CACHE_CAP),
-			pvfs_require_precheck: LruCache::new(DEFAULT_CACHE_CAP),
-			validation_code_hash: LruCache::new(DEFAULT_CACHE_CAP),
-			version: LruCache::new(DEFAULT_CACHE_CAP),
-			disputes: LruCache::new(DEFAULT_CACHE_CAP),
-			unapplied_slashes: LruCache::new(DEFAULT_CACHE_CAP),
-			key_ownership_proof: LruCache::new(DEFAULT_CACHE_CAP),
-
-			staging_para_backing_state: LruCache::new(DEFAULT_CACHE_CAP),
-			staging_async_backing_params: LruCache::new(DEFAULT_CACHE_CAP),
+			authorities: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			validators: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			validator_groups: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			availability_cores: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			persisted_validation_data: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			assumed_validation_data: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			check_validation_outputs: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			session_index_for_child: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			validation_code: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			validation_code_by_hash: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			candidate_pending_availability: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			candidate_events: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			session_executor_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			session_info: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			dmq_contents: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			inbound_hrmp_channels_contents: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			current_babe_epoch: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			on_chain_votes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			pvfs_require_precheck: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			validation_code_hash: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			version: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			disputes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			unapplied_slashes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			key_ownership_proof: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+
+			staging_para_backing_state: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			staging_async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
 		}
 	}
 }
@@ -112,7 +109,7 @@ impl RequestResultCache {
 		&mut self,
 		relay_parent: &Hash,
 	) -> Option<&Vec<AuthorityDiscoveryId>> {
-		self.authorities.get(relay_parent)
+		self.authorities.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_authorities(
@@ -120,22 +117,22 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		authorities: Vec<AuthorityDiscoveryId>,
 	) {
-		self.authorities.put(relay_parent, authorities);
+		self.authorities.insert(relay_parent, authorities);
 	}
 
 	pub(crate) fn validators(&mut self, relay_parent: &Hash) -> Option<&Vec<ValidatorId>> {
-		self.validators.get(relay_parent)
+		self.validators.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_validators(&mut self, relay_parent: Hash, validators: Vec<ValidatorId>) {
-		self.validators.put(relay_parent, validators);
+		self.validators.insert(relay_parent, validators);
 	}
 
 	pub(crate) fn validator_groups(
 		&mut self,
 		relay_parent: &Hash,
 	) -> Option<&(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)> {
-		self.validator_groups.get(relay_parent)
+		self.validator_groups.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_validator_groups(
@@ -143,22 +140,22 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		groups: (Vec<Vec<ValidatorIndex>>, GroupRotationInfo),
 	) {
-		self.validator_groups.put(relay_parent, groups);
+		self.validator_groups.insert(relay_parent, groups);
 	}
 
 	pub(crate) fn availability_cores(&mut self, relay_parent: &Hash) -> Option<&Vec<CoreState>> {
-		self.availability_cores.get(relay_parent)
+		self.availability_cores.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_availability_cores(&mut self, relay_parent: Hash, cores: Vec<CoreState>) {
-		self.availability_cores.put(relay_parent, cores);
+		self.availability_cores.insert(relay_parent, cores);
 	}
 
 	pub(crate) fn persisted_validation_data(
 		&mut self,
 		key: (Hash, ParaId, OccupiedCoreAssumption),
 	) -> Option<&Option<PersistedValidationData>> {
-		self.persisted_validation_data.get(&key)
+		self.persisted_validation_data.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_persisted_validation_data(
@@ -166,14 +163,14 @@ impl RequestResultCache {
 		key: (Hash, ParaId, OccupiedCoreAssumption),
 		data: Option<PersistedValidationData>,
 	) {
-		self.persisted_validation_data.put(key, data);
+		self.persisted_validation_data.insert(key, data);
 	}
 
 	pub(crate) fn assumed_validation_data(
 		&mut self,
 		key: (Hash, ParaId, Hash),
 	) -> Option<&Option<(PersistedValidationData, ValidationCodeHash)>> {
-		self.assumed_validation_data.get(&(key.1, key.2))
+		self.assumed_validation_data.get(&(key.1, key.2)).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_assumed_validation_data(
@@ -181,14 +178,14 @@ impl RequestResultCache {
 		key: (ParaId, Hash),
 		data: Option<(PersistedValidationData, ValidationCodeHash)>,
 	) {
-		self.assumed_validation_data.put(key, data);
+		self.assumed_validation_data.insert(key, data);
 	}
 
 	pub(crate) fn check_validation_outputs(
 		&mut self,
 		key: (Hash, ParaId, CandidateCommitments),
 	) -> Option<&bool> {
-		self.check_validation_outputs.get(&key)
+		self.check_validation_outputs.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_check_validation_outputs(
@@ -196,11 +193,11 @@ impl RequestResultCache {
 		key: (Hash, ParaId, CandidateCommitments),
 		value: bool,
 	) {
-		self.check_validation_outputs.put(key, value);
+		self.check_validation_outputs.insert(key, value);
 	}
 
 	pub(crate) fn session_index_for_child(&mut self, relay_parent: &Hash) -> Option<&SessionIndex> {
-		self.session_index_for_child.get(relay_parent)
+		self.session_index_for_child.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_session_index_for_child(
@@ -208,14 +205,14 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		index: SessionIndex,
 	) {
-		self.session_index_for_child.put(relay_parent, index);
+		self.session_index_for_child.insert(relay_parent, index);
 	}
 
 	pub(crate) fn validation_code(
 		&mut self,
 		key: (Hash, ParaId, OccupiedCoreAssumption),
 	) -> Option<&Option<ValidationCode>> {
-		self.validation_code.get(&key)
+		self.validation_code.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_validation_code(
@@ -223,7 +220,7 @@ impl RequestResultCache {
 		key: (Hash, ParaId, OccupiedCoreAssumption),
 		value: Option<ValidationCode>,
 	) {
-		self.validation_code.put(key, value);
+		self.validation_code.insert(key, value);
 	}
 
 	// the actual key is `ValidationCodeHash` (`Hash` is ignored),
@@ -232,7 +229,7 @@ impl RequestResultCache {
 		&mut self,
 		key: (Hash, ValidationCodeHash),
 	) -> Option<&Option<ValidationCode>> {
-		self.validation_code_by_hash.get(&key.1)
+		self.validation_code_by_hash.get(&key.1).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_validation_code_by_hash(
@@ -240,14 +237,14 @@ impl RequestResultCache {
 		key: ValidationCodeHash,
 		value: Option<ValidationCode>,
 	) {
-		self.validation_code_by_hash.put(key, value);
+		self.validation_code_by_hash.insert(key, value);
 	}
 
 	pub(crate) fn candidate_pending_availability(
 		&mut self,
 		key: (Hash, ParaId),
 	) -> Option<&Option<CommittedCandidateReceipt>> {
-		self.candidate_pending_availability.get(&key)
+		self.candidate_pending_availability.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_candidate_pending_availability(
@@ -255,11 +252,11 @@ impl RequestResultCache {
 		key: (Hash, ParaId),
 		value: Option<CommittedCandidateReceipt>,
 	) {
-		self.candidate_pending_availability.put(key, value);
+		self.candidate_pending_availability.insert(key, value);
 	}
 
 	pub(crate) fn candidate_events(&mut self, relay_parent: &Hash) -> Option<&Vec<CandidateEvent>> {
-		self.candidate_events.get(relay_parent)
+		self.candidate_events.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_candidate_events(
@@ -267,22 +264,22 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		events: Vec<CandidateEvent>,
 	) {
-		self.candidate_events.put(relay_parent, events);
+		self.candidate_events.insert(relay_parent, events);
 	}
 
 	pub(crate) fn session_info(&mut self, key: SessionIndex) -> Option<&SessionInfo> {
-		self.session_info.get(&key)
+		self.session_info.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_session_info(&mut self, key: SessionIndex, value: SessionInfo) {
-		self.session_info.put(key, value);
+		self.session_info.insert(key, value);
 	}
 
 	pub(crate) fn session_executor_params(
 		&mut self,
 		session_index: SessionIndex,
 	) -> Option<&Option<ExecutorParams>> {
-		self.session_executor_params.get(&session_index)
+		self.session_executor_params.get(&session_index).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_session_executor_params(
@@ -290,14 +287,14 @@ impl RequestResultCache {
 		session_index: SessionIndex,
 		value: Option<ExecutorParams>,
 	) {
-		self.session_executor_params.put(session_index, value);
+		self.session_executor_params.insert(session_index, value);
 	}
 
 	pub(crate) fn dmq_contents(
 		&mut self,
 		key: (Hash, ParaId),
 	) -> Option<&Vec<InboundDownwardMessage<BlockNumber>>> {
-		self.dmq_contents.get(&key)
+		self.dmq_contents.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_dmq_contents(
@@ -305,14 +302,14 @@ impl RequestResultCache {
 		key: (Hash, ParaId),
 		value: Vec<InboundDownwardMessage<BlockNumber>>,
 	) {
-		self.dmq_contents.put(key, value);
+		self.dmq_contents.insert(key, value);
 	}
 
 	pub(crate) fn inbound_hrmp_channels_contents(
 		&mut self,
 		key: (Hash, ParaId),
 	) -> Option<&BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>> {
-		self.inbound_hrmp_channels_contents.get(&key)
+		self.inbound_hrmp_channels_contents.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_inbound_hrmp_channel_contents(
@@ -320,22 +317,22 @@ impl RequestResultCache {
 		key: (Hash, ParaId),
 		value: BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>,
 	) {
-		self.inbound_hrmp_channels_contents.put(key, value);
+		self.inbound_hrmp_channels_contents.insert(key, value);
 	}
 
 	pub(crate) fn current_babe_epoch(&mut self, relay_parent: &Hash) -> Option<&Epoch> {
-		self.current_babe_epoch.get(relay_parent)
+		self.current_babe_epoch.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_current_babe_epoch(&mut self, relay_parent: Hash, epoch: Epoch) {
-		self.current_babe_epoch.put(relay_parent, epoch);
+		self.current_babe_epoch.insert(relay_parent, epoch);
 	}
 
 	pub(crate) fn on_chain_votes(
 		&mut self,
 		relay_parent: &Hash,
 	) -> Option<&Option<ScrapedOnChainVotes>> {
-		self.on_chain_votes.get(relay_parent)
+		self.on_chain_votes.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_on_chain_votes(
@@ -343,14 +340,14 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		scraped: Option<ScrapedOnChainVotes>,
 	) {
-		self.on_chain_votes.put(relay_parent, scraped);
+		self.on_chain_votes.insert(relay_parent, scraped);
 	}
 
 	pub(crate) fn pvfs_require_precheck(
 		&mut self,
 		relay_parent: &Hash,
 	) -> Option<&Vec<ValidationCodeHash>> {
-		self.pvfs_require_precheck.get(relay_parent)
+		self.pvfs_require_precheck.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_pvfs_require_precheck(
@@ -358,14 +355,14 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		pvfs: Vec<ValidationCodeHash>,
 	) {
-		self.pvfs_require_precheck.put(relay_parent, pvfs);
+		self.pvfs_require_precheck.insert(relay_parent, pvfs);
 	}
 
 	pub(crate) fn validation_code_hash(
 		&mut self,
 		key: (Hash, ParaId, OccupiedCoreAssumption),
 	) -> Option<&Option<ValidationCodeHash>> {
-		self.validation_code_hash.get(&key)
+		self.validation_code_hash.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_validation_code_hash(
@@ -373,22 +370,22 @@ impl RequestResultCache {
 		key: (Hash, ParaId, OccupiedCoreAssumption),
 		value: Option<ValidationCodeHash>,
 	) {
-		self.validation_code_hash.put(key, value);
+		self.validation_code_hash.insert(key, value);
 	}
 
 	pub(crate) fn version(&mut self, relay_parent: &Hash) -> Option<&u32> {
-		self.version.get(relay_parent)
+		self.version.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_version(&mut self, key: Hash, value: u32) {
-		self.version.put(key, value);
+		self.version.insert(key, value);
 	}
 
 	pub(crate) fn disputes(
 		&mut self,
 		relay_parent: &Hash,
 	) -> Option<&Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>> {
-		self.disputes.get(relay_parent)
+		self.disputes.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_disputes(
@@ -396,14 +393,14 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		value: Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>,
 	) {
-		self.disputes.put(relay_parent, value);
+		self.disputes.insert(relay_parent, value);
 	}
 
 	pub(crate) fn unapplied_slashes(
 		&mut self,
 		relay_parent: &Hash,
 	) -> Option<&Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>> {
-		self.unapplied_slashes.get(relay_parent)
+		self.unapplied_slashes.get(relay_parent).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_unapplied_slashes(
@@ -411,14 +408,14 @@ impl RequestResultCache {
 		relay_parent: Hash,
 		value: Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>,
 	) {
-		self.unapplied_slashes.put(relay_parent, value);
+		self.unapplied_slashes.insert(relay_parent, value);
 	}
 
 	pub(crate) fn key_ownership_proof(
 		&mut self,
 		key: (Hash, ValidatorId),
 	) -> Option<&Option<vstaging::slashing::OpaqueKeyOwnershipProof>> {
-		self.key_ownership_proof.get(&key)
+		self.key_ownership_proof.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_key_ownership_proof(
@@ -426,7 +423,7 @@ impl RequestResultCache {
 		key: (Hash, ValidatorId),
 		value: Option<vstaging::slashing::OpaqueKeyOwnershipProof>,
 	) {
-		self.key_ownership_proof.put(key, value);
+		self.key_ownership_proof.insert(key, value);
 	}
 
 	// This request is never cached, hence always returns `None`.
@@ -441,7 +438,7 @@ impl RequestResultCache {
 		&mut self,
 		key: (Hash, ParaId),
 	) -> Option<&Option<vstaging::BackingState>> {
-		self.staging_para_backing_state.get(&key)
+		self.staging_para_backing_state.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_staging_para_backing_state(
@@ -449,14 +446,14 @@ impl RequestResultCache {
 		key: (Hash, ParaId),
 		value: Option<vstaging::BackingState>,
 	) {
-		self.staging_para_backing_state.put(key, value);
+		self.staging_para_backing_state.insert(key, value);
 	}
 
 	pub(crate) fn staging_async_backing_params(
 		&mut self,
 		key: &Hash,
 	) -> Option<&vstaging::AsyncBackingParams> {
-		self.staging_async_backing_params.get(key)
+		self.staging_async_backing_params.get(key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_staging_async_backing_params(
@@ -464,7 +461,7 @@ impl RequestResultCache {
 		key: Hash,
 		value: vstaging::AsyncBackingParams,
 	) {
-		self.staging_async_backing_params.put(key, value);
+		self.staging_async_backing_params.insert(key, value);
 	}
 }
 
diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml
index b81145688b69bf17b40fd5319c310ac301357f8f..60fe8fb9bd78913a746c63e8cfcfc2aa7a116dfd 100644
--- a/polkadot/node/network/availability-distribution/Cargo.toml
+++ b/polkadot/node/network/availability-distribution/Cargo.toml
@@ -20,7 +20,7 @@ sp-keystore = { path = "../../../../substrate/primitives/keystore" }
 thiserror = "1.0.31"
 rand = "0.8.5"
 derive_more = "0.99.17"
-lru = "0.11.0"
+schnellru = "0.2.1"
 fatality = "0.0.6"
 
 [dev-dependencies]
diff --git a/polkadot/node/network/availability-distribution/src/requester/session_cache.rs b/polkadot/node/network/availability-distribution/src/requester/session_cache.rs
index e9311fb220382396762a1a801a4f26a588d85053..8a48e19c2827d13fe2d15ea4cc5ded50f058ca03 100644
--- a/polkadot/node/network/availability-distribution/src/requester/session_cache.rs
+++ b/polkadot/node/network/availability-distribution/src/requester/session_cache.rs
@@ -14,10 +14,10 @@
 // You should have received a copy of the GNU General Public License
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
-use std::{collections::HashSet, num::NonZeroUsize};
+use std::collections::HashSet;
 
-use lru::LruCache;
 use rand::{seq::SliceRandom, thread_rng};
+use schnellru::{ByLength, LruMap};
 
 use polkadot_node_subsystem::overseer;
 use polkadot_node_subsystem_util::runtime::RuntimeInfo;
@@ -37,7 +37,7 @@ pub struct SessionCache {
 	/// Note: Performance of fetching is really secondary here, but we need to ensure we are going
 	/// to get any existing cache entry, before fetching new information, as we should not mess up
 	/// the order of validators in `SessionInfo::validator_groups`.
-	session_info_cache: LruCache<SessionIndex, SessionInfo>,
+	session_info_cache: LruMap<SessionIndex, SessionInfo>,
 }
 
 /// Localized session information, tailored for the needs of availability distribution.
@@ -83,7 +83,7 @@ impl SessionCache {
 	pub fn new() -> Self {
 		SessionCache {
 			// We need to cache the current and the last session the most:
-			session_info_cache: LruCache::new(NonZeroUsize::new(2).unwrap()),
+			session_info_cache: LruMap::new(ByLength::new(2)),
 		}
 	}
 
@@ -115,7 +115,7 @@ impl SessionCache {
 			gum::trace!(target: LOG_TARGET, session_index, "Calling `with_info`");
 			let r = with_info(&info);
 			gum::trace!(target: LOG_TARGET, session_index, "Storing session info in lru!");
-			self.session_info_cache.put(session_index, info);
+			self.session_info_cache.insert(session_index, info);
 			Ok(Some(r))
 		} else {
 			Ok(None)
@@ -142,7 +142,7 @@ impl SessionCache {
 	/// will be put at the beginning of the group.
 	pub fn report_bad(&mut self, report: BadValidators) -> Result<()> {
 		let available_sessions = self.session_info_cache.iter().map(|(k, _)| *k).collect();
-		let session = self.session_info_cache.get_mut(&report.session_index).ok_or(
+		let session = self.session_info_cache.get(&report.session_index).ok_or(
 			Error::NoSuchCachedSession {
 				available_sessions,
 				missing_session: report.session_index,
diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml
index 9dc4d05353eda05209695dc717df7dca0faeeee1..d6d79d3fc24d844296195cd36bd6e5c8e8a75ce7 100644
--- a/polkadot/node/network/availability-recovery/Cargo.toml
+++ b/polkadot/node/network/availability-recovery/Cargo.toml
@@ -7,7 +7,7 @@ license.workspace = true
 
 [dependencies]
 futures = "0.3.21"
-lru = "0.11.0"
+schnellru = "0.2.1"
 rand = "0.8.5"
 fatality = "0.0.6"
 thiserror = "1.0.31"
diff --git a/polkadot/node/network/availability-recovery/src/lib.rs b/polkadot/node/network/availability-recovery/src/lib.rs
index fb0cdb7205710623af2ad90dabd5ef4febcfb90f..99f42f4bf9fe668132cb020dd3e74d4faca34c46 100644
--- a/polkadot/node/network/availability-recovery/src/lib.rs
+++ b/polkadot/node/network/availability-recovery/src/lib.rs
@@ -35,8 +35,8 @@ use futures::{
 	stream::{FuturesUnordered, StreamExt},
 	task::{Context, Poll},
 };
-use lru::LruCache;
 use rand::seq::SliceRandom;
+use schnellru::{ByLength, LruMap};
 
 use fatality::Nested;
 use polkadot_erasure_coding::{
@@ -82,10 +82,7 @@ const LOG_TARGET: &str = "parachain::availability-recovery";
 const N_PARALLEL: usize = 50;
 
 // Size of the LRU cache where we keep recovered data.
-const LRU_SIZE: NonZeroUsize = match NonZeroUsize::new(16) {
-	Some(cap) => cap,
-	None => panic!("Availability-recovery cache size must be non-zero."),
-};
+const LRU_SIZE: u32 = 16;
 
 const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Peer sent unparsable request");
 
@@ -927,7 +924,7 @@ struct State {
 	live_block: (BlockNumber, Hash),
 
 	/// An LRU cache of recently recovered data.
-	availability_lru: LruCache<CandidateHash, CachedRecovery>,
+	availability_lru: LruMap<CandidateHash, CachedRecovery>,
 }
 
 impl Default for State {
@@ -935,7 +932,7 @@ impl Default for State {
 		Self {
 			ongoing_recoveries: FuturesUnordered::new(),
 			live_block: (0, Hash::default()),
-			availability_lru: LruCache::new(LRU_SIZE),
+			availability_lru: LruMap::new(ByLength::new(LRU_SIZE)),
 		}
 	}
 }
@@ -1152,7 +1149,7 @@ async fn query_chunk_size<Context>(
 
 #[overseer::contextbounds(AvailabilityRecovery, prefix = self::overseer)]
 impl AvailabilityRecoverySubsystem {
-	/// Create a new instance of `AvailabilityRecoverySubsystem` which never requests the  
+	/// Create a new instance of `AvailabilityRecoverySubsystem` which never requests the
 	/// `AvailabilityStoreSubsystem` subsystem.
 	pub fn with_availability_store_skip(
 		req_receiver: IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
@@ -1334,7 +1331,7 @@ impl AvailabilityRecoverySubsystem {
 				output = state.ongoing_recoveries.select_next_some() => {
 					if let Some((candidate_hash, result)) = output {
 						if let Ok(recovery) = CachedRecovery::try_from(result) {
-							state.availability_lru.put(candidate_hash, recovery);
+							state.availability_lru.insert(candidate_hash, recovery);
 						}
 					}
 				}
diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml
index 24f597bacadd6c73c7291762e4760640a544f944..24ae7ba25bc5ad6850f349feeac38f77c8c1874d 100644
--- a/polkadot/node/network/dispute-distribution/Cargo.toml
+++ b/polkadot/node/network/dispute-distribution/Cargo.toml
@@ -22,7 +22,7 @@ sp-application-crypto = { path = "../../../../substrate/primitives/application-c
 sp-keystore = { path = "../../../../substrate/primitives/keystore" }
 thiserror = "1.0.31"
 fatality = "0.0.6"
-lru = "0.11.0"
+schnellru = "0.2.1"
 indexmap = "1.9.1"
 
 [dev-dependencies]
diff --git a/polkadot/node/network/dispute-distribution/src/lib.rs b/polkadot/node/network/dispute-distribution/src/lib.rs
index ad99bc41fa64d772b29f149e3c8de32834692395..071dc3c3343b25068c1ca73a02947c9492350877 100644
--- a/polkadot/node/network/dispute-distribution/src/lib.rs
+++ b/polkadot/node/network/dispute-distribution/src/lib.rs
@@ -24,7 +24,7 @@
 //! The sender is responsible for getting our vote out, see [`sender`]. The receiver handles
 //! incoming [`DisputeRequest`]s and offers spam protection, see [`receiver`].
 
-use std::{num::NonZeroUsize, time::Duration};
+use std::time::Duration;
 
 use futures::{channel::mpsc, FutureExt, StreamExt, TryFutureExt};
 
@@ -165,8 +165,7 @@ where
 	) -> Self {
 		let runtime = RuntimeInfo::new_with_config(runtime::Config {
 			keystore: Some(keystore),
-			session_cache_lru_size: NonZeroUsize::new(DISPUTE_WINDOW.get() as usize)
-				.expect("Dispute window can not be 0; qed"),
+			session_cache_lru_size: DISPUTE_WINDOW.get(),
 		});
 		let (tx, sender_rx) = NestingSender::new_root(1);
 		let disputes_sender = DisputeSender::new(tx, metrics.clone());
diff --git a/polkadot/node/network/dispute-distribution/src/receiver/mod.rs b/polkadot/node/network/dispute-distribution/src/receiver/mod.rs
index 827a77281ccb3c78b36429370df801bdfcf160e2..2b3fc45983a98f67954a16fd342d76ac8068ae07 100644
--- a/polkadot/node/network/dispute-distribution/src/receiver/mod.rs
+++ b/polkadot/node/network/dispute-distribution/src/receiver/mod.rs
@@ -15,7 +15,6 @@
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
 use std::{
-	num::NonZeroUsize,
 	pin::Pin,
 	task::{Context, Poll},
 	time::Duration,
@@ -161,8 +160,7 @@ where
 	) -> Self {
 		let runtime = RuntimeInfo::new_with_config(runtime::Config {
 			keystore: None,
-			session_cache_lru_size: NonZeroUsize::new(DISPUTE_WINDOW.get() as usize)
-				.expect("Dispute window can not be 0; qed"),
+			session_cache_lru_size: DISPUTE_WINDOW.get(),
 		});
 		Self {
 			runtime,
diff --git a/polkadot/node/overseer/Cargo.toml b/polkadot/node/overseer/Cargo.toml
index 577b12e2620e07caa775d98a10d8964e484c7de4..982ccc7ff245ca006900be25ba8a4f47146e3754 100644
--- a/polkadot/node/overseer/Cargo.toml
+++ b/polkadot/node/overseer/Cargo.toml
@@ -18,7 +18,7 @@ polkadot-node-metrics = { path = "../metrics" }
 polkadot-primitives = { path = "../../primitives" }
 orchestra = "0.0.5"
 gum = { package = "tracing-gum", path = "../gum" }
-lru = "0.11.0"
+schnellru = "0.2.1"
 sp-core = { path = "../../../substrate/primitives/core" }
 async-trait = "0.1.57"
 tikv-jemalloc-ctl = { version = "0.5.0", optional = true }
diff --git a/polkadot/node/overseer/src/dummy.rs b/polkadot/node/overseer/src/dummy.rs
index 79daba1406769f8e256b5e435daf34163d345bbf..fea96e5dbab73a05e72fa90e8d4cef1765b9921e 100644
--- a/polkadot/node/overseer/src/dummy.rs
+++ b/polkadot/node/overseer/src/dummy.rs
@@ -19,9 +19,9 @@ use crate::{
 	Overseer, OverseerMetrics, OverseerSignal, OverseerSubsystemContext, SpawnGlue,
 	KNOWN_LEAVES_CACHE_SIZE,
 };
-use lru::LruCache;
 use orchestra::{FromOrchestra, SpawnedSubsystem, Subsystem, SubsystemContext};
 use polkadot_node_subsystem_types::{errors::SubsystemError, messages::*};
+use schnellru::{ByLength, LruMap};
 // Generated dummy messages
 use crate::messages::*;
 
@@ -193,7 +193,7 @@ where
 		.activation_external_listeners(Default::default())
 		.span_per_active_leaf(Default::default())
 		.active_leaves(Default::default())
-		.known_leaves(LruCache::new(KNOWN_LEAVES_CACHE_SIZE))
+		.known_leaves(LruMap::new(ByLength::new(KNOWN_LEAVES_CACHE_SIZE)))
 		.spawner(SpawnGlue(spawner))
 		.metrics(metrics)
 		.supports_parachains(supports_parachains);
diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs
index 5655a3ef79c17ca2183c6077daf3bff9a39e0e8f..b8643982323cdc0c495a192c2de49140193ef88a 100644
--- a/polkadot/node/overseer/src/lib.rs
+++ b/polkadot/node/overseer/src/lib.rs
@@ -62,14 +62,13 @@
 use std::{
 	collections::{hash_map, HashMap},
 	fmt::{self, Debug},
-	num::NonZeroUsize,
 	pin::Pin,
 	sync::Arc,
 	time::Duration,
 };
 
 use futures::{channel::oneshot, future::BoxFuture, select, Future, FutureExt, StreamExt};
-use lru::LruCache;
+use schnellru::LruMap;
 
 use client::{BlockImportNotification, BlockchainEvents, FinalityNotification};
 use polkadot_primitives::{Block, BlockNumber, Hash};
@@ -113,10 +112,7 @@ pub use orchestra::{
 
 /// Store 2 days worth of blocks, not accounting for forks,
 /// in the LRU cache. Assumes a 6-second block time.
-pub const KNOWN_LEAVES_CACHE_SIZE: NonZeroUsize = match NonZeroUsize::new(2 * 24 * 3600 / 6) {
-	Some(cap) => cap,
-	None => panic!("Known leaves cache size must be non-zero"),
-};
+pub const KNOWN_LEAVES_CACHE_SIZE: u32 = 2 * 24 * 3600 / 6;
 
 #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
 mod memory_stats;
@@ -632,7 +628,7 @@ pub struct Overseer<SupportsParachains> {
 	pub supports_parachains: SupportsParachains,
 
 	/// An LRU cache for keeping track of relay-chain heads that have already been seen.
-	pub known_leaves: LruCache<Hash, ()>,
+	pub known_leaves: LruMap<Hash, ()>,
 
 	/// Various Prometheus metrics.
 	pub metrics: OverseerMetrics,
@@ -880,9 +876,10 @@ where
 		let span = Arc::new(span);
 		self.span_per_active_leaf.insert(*hash, span.clone());
 
-		let status = if let Some(_) = self.known_leaves.put(*hash, ()) {
+		let status = if self.known_leaves.get(hash).is_some() {
 			LeafStatus::Stale
 		} else {
+			self.known_leaves.insert(*hash, ());
 			LeafStatus::Fresh
 		};
 
diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs
index 392781783319e25a095c8fc08b36d4c30cd8cacf..4cebc23ad31da8e67243b4490460e2d74989be6a 100644
--- a/polkadot/node/primitives/src/lib.rs
+++ b/polkadot/node/primitives/src/lib.rs
@@ -22,7 +22,7 @@
 
 #![deny(missing_docs)]
 
-use std::{num::NonZeroUsize, pin::Pin};
+use std::pin::Pin;
 
 use bounded_vec::BoundedVec;
 use futures::Future;
@@ -143,12 +143,6 @@ impl SessionWindowSize {
 	}
 }
 
-impl From<SessionWindowSize> for NonZeroUsize {
-	fn from(value: SessionWindowSize) -> Self {
-		NonZeroUsize::new(value.get() as usize).expect("SessionWindowSize can't be 0. qed.")
-	}
-}
-
 /// The cumulative weight of a block in a fork-choice rule.
 pub type BlockWeight = u32;
 
diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml
index f58f4abca3627063f2395356ca40e700090dbac2..58be7a885357d3326ad1f0509a85d454c8fe1916 100644
--- a/polkadot/node/service/Cargo.toml
+++ b/polkadot/node/service/Cargo.toml
@@ -86,7 +86,7 @@ parity-db = { version = "0.4.8", optional = true }
 codec = { package = "parity-scale-codec", version = "3.6.1" }
 
 async-trait = "0.1.57"
-lru = "0.11.0"
+schnellru = "0.2.1"
 log = "0.4.17"
 is_executable = "1.0.1"
 
diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs
index cb6b80eb83c8206f7eada6dbcc8d5c60457debe6..1c49577508f6e3089917060e1fa685f52fe9c54e 100644
--- a/polkadot/node/service/src/overseer.rs
+++ b/polkadot/node/service/src/overseer.rs
@@ -19,7 +19,6 @@ use polkadot_node_subsystem_types::DefaultSubsystemClient;
 use sc_transaction_pool_api::OffchainTransactionPoolFactory;
 use sp_core::traits::SpawnNamed;
 
-use lru::LruCache;
 use polkadot_availability_distribution::IncomingRequestReceivers;
 use polkadot_node_core_approval_voting::Config as ApprovalVotingConfig;
 use polkadot_node_core_av_store::Config as AvailabilityConfig;
@@ -41,6 +40,7 @@ use polkadot_overseer::{
 	metrics::Metrics as OverseerMetrics, InitializedOverseerBuilder, MetricsTrait, Overseer,
 	OverseerConnector, OverseerHandle, SpawnGlue,
 };
+use schnellru::{ByLength, LruMap};
 
 use polkadot_primitives::runtime_api::ParachainHost;
 use sc_authority_discovery::Service as AuthorityDiscoveryService;
@@ -344,7 +344,7 @@ where
 		.span_per_active_leaf(Default::default())
 		.active_leaves(Default::default())
 		.supports_parachains(runtime_api_client)
-		.known_leaves(LruCache::new(KNOWN_LEAVES_CACHE_SIZE))
+		.known_leaves(LruMap::new(ByLength::new(KNOWN_LEAVES_CACHE_SIZE)))
 		.metrics(metrics)
 		.spawner(spawner);
 
diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml
index a7c802a8ca87fb9bf0f607a96c89ad4db8eb79a5..87a823da9090c338c5d36c4158106ac0e037ba0c 100644
--- a/polkadot/node/subsystem-util/Cargo.toml
+++ b/polkadot/node/subsystem-util/Cargo.toml
@@ -19,7 +19,7 @@ thiserror = "1.0.31"
 fatality = "0.0.6"
 gum = { package = "tracing-gum", path = "../gum" }
 derive_more = "0.99.17"
-lru = "0.11.0"
+schnellru = "0.2.1"
 
 polkadot-node-subsystem = { path = "../subsystem" }
 polkadot-node-jaeger = { path = "../jaeger" }
diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs
index 1f5641e3ea956c0379c79a2c686144336ff95030..a044a64e93a0d9622f48fc7193b87f476def38f8 100644
--- a/polkadot/node/subsystem-util/src/runtime/mod.rs
+++ b/polkadot/node/subsystem-util/src/runtime/mod.rs
@@ -16,9 +16,7 @@
 
 //! Convenient interface to runtime information.
 
-use std::num::NonZeroUsize;
-
-use lru::LruCache;
+use schnellru::{ByLength, LruMap};
 
 use parity_scale_codec::Encode;
 use sp_application_crypto::AppCrypto;
@@ -58,7 +56,7 @@ pub struct Config {
 	pub keystore: Option<KeystorePtr>,
 
 	/// How many sessions should we keep in the cache?
-	pub session_cache_lru_size: NonZeroUsize,
+	pub session_cache_lru_size: u32,
 }
 
 /// Caching of session info.
@@ -69,10 +67,10 @@ pub struct RuntimeInfo {
 	///
 	/// We query this up to a 100 times per block, so caching it here without roundtrips over the
 	/// overseer seems sensible.
-	session_index_cache: LruCache<Hash, SessionIndex>,
+	session_index_cache: LruMap<Hash, SessionIndex>,
 
 	/// Look up cached sessions by `SessionIndex`.
-	session_info_cache: LruCache<SessionIndex, ExtendedSessionInfo>,
+	session_info_cache: LruMap<SessionIndex, ExtendedSessionInfo>,
 
 	/// Key store for determining whether we are a validator and what `ValidatorIndex` we have.
 	keystore: Option<KeystorePtr>,
@@ -101,7 +99,7 @@ impl Default for Config {
 		Self {
 			keystore: None,
 			// Usually we need to cache the current and the last session.
-			session_cache_lru_size: NonZeroUsize::new(2).expect("2 is larger than 0; qed"),
+			session_cache_lru_size: 2,
 		}
 	}
 }
@@ -115,11 +113,8 @@ impl RuntimeInfo {
 	/// Create with more elaborate configuration options.
 	pub fn new_with_config(cfg: Config) -> Self {
 		Self {
-			session_index_cache: LruCache::new(
-				cfg.session_cache_lru_size
-					.max(NonZeroUsize::new(10).expect("10 is larger than 0; qed")),
-			),
-			session_info_cache: LruCache::new(cfg.session_cache_lru_size),
+			session_index_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size.max(10))),
+			session_info_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
 			keystore: cfg.keystore,
 		}
 	}
@@ -139,7 +134,7 @@ impl RuntimeInfo {
 			None => {
 				let index =
 					recv_runtime(request_session_index_for_child(parent, sender).await).await?;
-				self.session_index_cache.put(parent, index);
+				self.session_index_cache.insert(parent, index);
 				Ok(index)
 			},
 		}
@@ -172,7 +167,7 @@ impl RuntimeInfo {
 	where
 		Sender: SubsystemSender<RuntimeApiMessage>,
 	{
-		if !self.session_info_cache.contains(&session_index) {
+		if self.session_info_cache.get(&session_index).is_none() {
 			let session_info =
 				recv_runtime(request_session_info(parent, session_index, sender).await)
 					.await?
@@ -181,7 +176,7 @@ impl RuntimeInfo {
 
 			let full_info = ExtendedSessionInfo { session_info, validator_info };
 
-			self.session_info_cache.put(session_index, full_info);
+			self.session_info_cache.insert(session_index, full_info);
 		}
 		Ok(self
 			.session_info_cache
diff --git a/polkadot/roadmap/implementers-guide/src/node/availability/availability-recovery.md b/polkadot/roadmap/implementers-guide/src/node/availability/availability-recovery.md
index 48fb0fb1ca19da77a04fdec4dd8993bacceafef2..ac733c1ce9186c182140e42dfb822006b7aac33b 100644
--- a/polkadot/roadmap/implementers-guide/src/node/availability/availability-recovery.md
+++ b/polkadot/roadmap/implementers-guide/src/node/availability/availability-recovery.md
@@ -30,7 +30,7 @@ struct State {
     /// A recent block hash for which state should be available.
     live_block_hash: Hash,
     // An LRU cache of recently recovered data.
-    availability_lru: LruCache<CandidateHash, Result<AvailableData, RecoveryError>>,
+    availability_lru: LruMap<CandidateHash, Result<AvailableData, RecoveryError>>,
 }
 
 /// This is a future, which concludes either when a response is received from the recovery tasks,
diff --git a/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md b/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md
index 7692491fde1f7fbe47bf7ae9fa96a159d15587dd..f8bfe6506aa5c01b3a2624eafec07a7bfb53938c 100644
--- a/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md
+++ b/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md
@@ -9,7 +9,7 @@ In particular the dispute-coordinator is responsible for:
 
 - Ensuring that the node is able to raise a dispute in case an invalid candidate
   is found during approval checking.
-- Ensuring that backing and approval votes will be recorded on chain. With these 
+- Ensuring that backing and approval votes will be recorded on chain. With these
   votes on chain we can be certain that appropriate targets for slashing will be
   available for concluded disputes. Also, scraping these votes during a dispute
   is necessary for critical spam prevention measures.
@@ -678,7 +678,7 @@ struct State {
   // It can be a `Vec` if the need to track more arises.
   error: Option<SessionsUnavailable>,
   /// Latest relay blocks that have been successfully scraped.
-  last_scraped_blocks: LruCache<Hash, ()>,
+  last_scraped_blocks: LruMap<Hash, ()>,
 }
 ```