Skip to content

Commit f5003dc

Browse files
0xdeafbeefpashinov
authored andcommitted
feat(core): add prefetching to ArchiveBlockProvider to download next archive early
``` logs.txt: total_time = 0:53:37.166710 (2025-04-15 11:26:15.563743 .. 2025-04-15 12:19:52.730453) logs-new.txt: total_time = 0:42:00.475914 (2025-04-15 17:39:13.769644 .. 2025-04-15 18:21:14.245558) Old total time: 0:53:37.166710 New total time: 0:42:00.475914 Absolute time saved: 0:11:36.690796 Speedup factor: 1.276x New code is 21.66% faster ``` replace known_archives map with ArchivesManager
1 parent c675c16 commit f5003dc

2 files changed

Lines changed: 196 additions & 36 deletions

File tree

core/src/block_strider/provider/archive_provider.rs

Lines changed: 193 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::{BTreeMap, btree_map};
1+
use std::collections::BTreeMap;
22
use std::io::Seek;
33
use std::sync::Arc;
44
use std::time::Duration;
@@ -24,12 +24,14 @@ use crate::storage::CoreStorage;
2424
#[serde(default)]
2525
pub struct ArchiveBlockProviderConfig {
2626
pub max_archive_to_memory_size: ByteSize,
27+
pub num_prefetched_archives: Option<usize>,
2728
}
2829

2930
impl Default for ArchiveBlockProviderConfig {
3031
fn default() -> Self {
3132
Self {
3233
max_archive_to_memory_size: ByteSize::mb(100),
34+
num_prefetched_archives: Some(10),
3335
}
3436
}
3537
}
@@ -48,21 +50,21 @@ impl ArchiveBlockProvider {
4850
) -> Self {
4951
let proof_checker = ProofChecker::new(storage.clone());
5052

53+
let archives_manager = ArchivesManager::new();
54+
5155
Self {
5256
inner: Arc::new(Inner {
5357
client,
5458
proof_checker,
55-
56-
known_archives: parking_lot::Mutex::new(Default::default()),
57-
59+
archives: archives_manager,
5860
storage,
5961
config,
6062
}),
6163
}
6264
}
6365

6466
async fn get_next_block_impl(&self, block_id: &BlockId) -> OptionalBlockStuff {
65-
let this = self.inner.as_ref();
67+
let this = &self.inner;
6668

6769
let next_mc_seqno = block_id.seqno + 1;
6870

@@ -96,7 +98,7 @@ impl ArchiveBlockProvider {
9698
}
9799

98100
async fn get_block_impl(&self, block_id_relation: &BlockIdRelation) -> OptionalBlockStuff {
99-
let this = self.inner.as_ref();
101+
let this = &self.inner;
100102

101103
let block_id = block_id_relation.block_id;
102104
let mc_block_id = block_id_relation.mc_block_id;
@@ -156,23 +158,34 @@ struct Inner {
156158
client: BlockchainRpcClient,
157159
proof_checker: ProofChecker,
158160

159-
known_archives: parking_lot::Mutex<ArchivesMap>,
161+
archives: ArchivesManager,
160162

161163
config: ArchiveBlockProviderConfig,
162164
}
163165

164166
impl Inner {
165-
async fn get_archive(&self, mc_seqno: u32) -> Option<(u32, ArchiveInfo)> {
167+
async fn get_archive(self: &Arc<Self>, mc_seqno: u32) -> Option<(u32, ArchiveInfo)> {
166168
loop {
167169
let mut pending = 'pending: {
168-
let mut guard = self.known_archives.lock();
169-
170170
// Search for the downloaded archive or for and existing downloader task.
171-
for (archive_key, value) in guard.iter() {
171+
for (archive_key, value) in self.archives.iter() {
172172
match value {
173173
ArchiveSlot::Downloaded(info) => {
174174
if info.archive.mc_block_ids.contains_key(&mc_seqno) {
175-
return Some((*archive_key, info.clone()));
175+
// Prefetch next archive if enabled
176+
if self.config.num_prefetched_archives.is_some() {
177+
if let Some((last_seqno, _)) =
178+
info.archive.mc_block_ids.last_key_value()
179+
{
180+
let next_archive_start_seqno = last_seqno.saturating_add(1);
181+
let this = self.clone();
182+
tokio::spawn(async move {
183+
this.try_prefetch_archive(next_archive_start_seqno)
184+
.await;
185+
});
186+
}
187+
}
188+
return Some((archive_key, info.clone()));
176189
}
177190
}
178191
ArchiveSlot::Pending(task) => break 'pending task.clone(),
@@ -181,7 +194,8 @@ impl Inner {
181194

182195
// Start downloading otherwise
183196
let task = self.make_downloader().spawn(mc_seqno);
184-
guard.insert(mc_seqno, ArchiveSlot::Pending(task.clone()));
197+
self.archives
198+
.insert(mc_seqno, ArchiveSlot::Pending(task.clone()));
185199

186200
task
187201
};
@@ -205,23 +219,39 @@ impl Inner {
205219
}
206220

207221
// Replace pending with downloaded
208-
match self.known_archives.lock().entry(pending.archive_key) {
209-
btree_map::Entry::Vacant(_) => {
210-
// Do nothing if the entry was already removed.
211-
}
212-
btree_map::Entry::Occupied(mut entry) => match &res {
222+
match self.archives.get(&pending.archive_key) {
223+
Some(ArchiveSlot::Pending(_)) => match &res {
213224
None => {
214225
// Task was either cancelled or received `TooNew` so no archive received.
215-
entry.remove();
226+
self.archives.remove(&pending.archive_key);
216227
}
217228
Some(info) => {
218229
// Task was finished with a non-empty result so store it.
219-
entry.insert(ArchiveSlot::Downloaded(info.clone()));
230+
self.archives
231+
.insert(pending.archive_key, ArchiveSlot::Downloaded(info.clone()));
220232
}
221233
},
234+
_ => {
235+
// Do nothing if the entry was already removed or replaced.
236+
}
222237
}
223238

224239
if finished {
240+
// Prefetch next archive if enabled
241+
if self.config.num_prefetched_archives.is_some() {
242+
if let Some(info) = res
243+
.as_ref()
244+
.and_then(|i| i.archive.mc_block_ids.last_key_value())
245+
{
246+
let (last_seqno, _) = info;
247+
let next_archive_start_seqno = last_seqno.saturating_add(1);
248+
let this = self.clone();
249+
// Fire and forget prefetch
250+
tokio::spawn(async move {
251+
this.try_prefetch_archive(next_archive_start_seqno).await;
252+
});
253+
}
254+
}
225255
return res.map(|info| (pending.archive_key, info));
226256
}
227257

@@ -232,28 +262,55 @@ impl Inner {
232262
}
233263

234264
fn remove_archive_if_same(&self, archive_key: u32, prev: &ArchiveInfo) -> bool {
235-
match self.known_archives.lock().entry(archive_key) {
236-
btree_map::Entry::Vacant(_) => false,
237-
btree_map::Entry::Occupied(entry) => {
238-
if matches!(
239-
entry.get(),
240-
ArchiveSlot::Downloaded(info)
241-
if Arc::ptr_eq(&info.archive, &prev.archive)
242-
) {
243-
entry.remove();
244-
true
245-
} else {
246-
false
265+
match self.archives.get(&archive_key) {
266+
Some(ArchiveSlot::Downloaded(info)) if Arc::ptr_eq(&info.archive, &prev.archive) => {
267+
self.archives.remove(&archive_key);
268+
true
269+
}
270+
_ => false,
271+
}
272+
}
273+
274+
/// Try to prefetch the archive containing `prefetch_seqno` if not already present or pending.
275+
async fn try_prefetch_archive(&self, prefetch_seqno: u32) {
276+
// Check if archive already present or pending
277+
for value in self.archives.values() {
278+
match value {
279+
ArchiveSlot::Downloaded(info) => {
280+
if info.archive.mc_block_ids.contains_key(&prefetch_seqno) {
281+
tracing::trace!(
282+
prefetch_seqno,
283+
"archive already downloaded, skipping prefetch"
284+
);
285+
return;
286+
}
287+
}
288+
ArchiveSlot::Pending(task) => {
289+
if task.archive_key == prefetch_seqno {
290+
tracing::trace!(
291+
prefetch_seqno,
292+
"archive download already pending, skipping prefetch"
293+
);
294+
return;
295+
}
247296
}
248297
}
249298
}
299+
300+
let task = self.make_downloader().spawn(prefetch_seqno);
301+
self.archives
302+
.insert(prefetch_seqno, ArchiveSlot::Pending(task));
303+
tracing::debug!(prefetch_seqno, "starting archive prefetch");
250304
}
251305

252306
fn make_downloader(&self) -> ArchiveDownloader {
253307
ArchiveDownloader {
254308
client: self.client.clone(),
255309
storage: self.storage.clone(),
256310
memory_threshold: self.config.max_archive_to_memory_size,
311+
max_downloaded_archives: self.config.num_prefetched_archives.unwrap_or(1), /* 0 will deadlock */
312+
map_len_at_spawn: self.archives.len(),
313+
archives_len_rx: self.archives.len_update_rx(),
257314
}
258315
}
259316

@@ -264,8 +321,7 @@ impl Inner {
264321
let mut entries_remaining = 0usize;
265322
let mut entries_removed = 0usize;
266323

267-
let mut guard = self.known_archives.lock();
268-
guard.retain(|_, archive| {
324+
self.archives.retain(|_, archive| {
269325
let retain;
270326
match archive {
271327
ArchiveSlot::Downloaded(info) => match info.archive.mc_block_ids.last_key_value() {
@@ -284,7 +340,6 @@ impl Inner {
284340
entries_removed += !retain as usize;
285341
retain
286342
});
287-
drop(guard);
288343

289344
tracing::debug!(
290345
entries_remaining,
@@ -297,6 +352,84 @@ impl Inner {
297352

298353
type ArchivesMap = BTreeMap<u32, ArchiveSlot>;
299354

355+
struct ArchivesManager {
356+
map: parking_lot::Mutex<ArchivesMap>,
357+
len_tx: watch::Sender<usize>,
358+
known_archive_len: watch::Receiver<usize>,
359+
}
360+
361+
impl ArchivesManager {
362+
fn new() -> Self {
363+
let (len_tx, len_rx) = watch::channel(0);
364+
Self {
365+
map: parking_lot::Mutex::new(BTreeMap::new()),
366+
len_tx,
367+
known_archive_len: len_rx,
368+
}
369+
}
370+
371+
fn len_update_rx(&self) -> watch::Receiver<usize> {
372+
self.known_archive_len.clone()
373+
}
374+
375+
fn len(&self) -> usize {
376+
self.map.lock().len()
377+
}
378+
379+
fn insert(&self, key: u32, value: ArchiveSlot) -> Option<ArchiveSlot> {
380+
let mut map = self.map.lock();
381+
let res = map.insert(key, value);
382+
self.update_len(map.len());
383+
res
384+
}
385+
386+
fn remove(&self, key: &u32) -> Option<ArchiveSlot> {
387+
let mut map = self.map.lock();
388+
let res = map.remove(key);
389+
390+
self.update_len(map.len());
391+
392+
res
393+
}
394+
395+
fn get(&self, key: &u32) -> Option<ArchiveSlot> {
396+
self.map.lock().get(key).cloned()
397+
}
398+
399+
fn iter(&self) -> Vec<(u32, ArchiveSlot)> {
400+
self.map
401+
.lock()
402+
.iter()
403+
.map(|(k, v)| (*k, v.clone()))
404+
.collect()
405+
}
406+
407+
fn values(&self) -> Vec<ArchiveSlot> {
408+
self.map.lock().values().cloned().collect()
409+
}
410+
411+
fn retain<F>(&self, mut f: F)
412+
where
413+
F: FnMut(&u32, &mut ArchiveSlot) -> bool,
414+
{
415+
let mut map = self.map.lock();
416+
map.retain(|k, v| f(k, v));
417+
self.update_len(map.len());
418+
}
419+
420+
fn update_len(&self, len: usize) {
421+
let _ = self.len_tx.send_if_modified(|current| {
422+
if *current != len {
423+
*current = len;
424+
true
425+
} else {
426+
false
427+
}
428+
});
429+
}
430+
}
431+
432+
#[derive(Clone)]
300433
enum ArchiveSlot {
301434
Downloaded(ArchiveInfo),
302435
Pending(ArchiveTask),
@@ -312,10 +445,13 @@ struct ArchiveDownloader {
312445
client: BlockchainRpcClient,
313446
storage: CoreStorage,
314447
memory_threshold: ByteSize,
448+
max_downloaded_archives: usize,
449+
map_len_at_spawn: usize,
450+
archives_len_rx: watch::Receiver<usize>,
315451
}
316452

317453
impl ArchiveDownloader {
318-
fn spawn(self, mc_seqno: u32) -> ArchiveTask {
454+
fn spawn(mut self, mc_seqno: u32) -> ArchiveTask {
319455
// TODO: Use a proper backoff here?
320456
const INTERVAL: Duration = Duration::from_secs(1);
321457

@@ -337,6 +473,27 @@ impl ArchiveDownloader {
337473
tracing::debug!(mc_seqno, "finished preloading archive");
338474
}
339475

476+
if self.map_len_at_spawn > self.max_downloaded_archives {
477+
tracing::debug!(mc_seqno, "too many archives already downloaded, waiting");
478+
match self
479+
.archives_len_rx
480+
.wait_for(|x| x < &self.max_downloaded_archives)
481+
.await
482+
{
483+
Err(e) => {
484+
tracing::warn!(
485+
mc_seqno,
486+
"archive task cancelled while waiting for free space: {e}"
487+
);
488+
return;
489+
}
490+
Ok(v) => {
491+
let v = *v;
492+
tracing::debug!(mc_seqno, used_slots = v, "free space available: {v}");
493+
}
494+
}
495+
}
496+
340497
loop {
341498
match self.try_download(mc_seqno).await {
342499
Ok(res) => {

storage/src/fs/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ pub struct UnnamedFileBuilder {
274274
}
275275

276276
impl UnnamedFileBuilder {
277+
/// File is immediately unlinked after creation.
278+
/// Fs will reclaim the space when fd is closed.
279+
/// (File handle is dropped)
277280
pub fn open(self) -> Result<File> {
278281
let file = tempfile::tempfile_in(&self.base_dir)?;
279282
if let Some(prealloc) = self.prealloc {

0 commit comments

Comments
 (0)