-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge #778: Performance optimization: create a new torrent repository…
… using a `SkipMap` instead of a `BTreeMap` 12f54e7 test: add tests for new torrent repository using SkipMap (Jose Celano) 0989285 refactor: separate torrent repository trait from implementations (Jose Celano) eec2024 chore: ignore crossbeam-skiplist crate in cargo-machete (Jose Celano) 642d6be feat: new torrent repository using crossbeam_skiplist::SkipMap (Jose Celano) 608585e chore: add new cargo dependency: crossbeam-skiplist (Jose Celano) Pull request description: This PR implements a new Torrent Repository replacing the outer BTreeMap for torrents with a [SkipMap](https://docs.rs/crossbeam-skiplist/latest/crossbeam_skiplist/). ### Why - It is straightforward to implement because the API is very similar. In fact, SkipMap is a replacement for BTreeMap that allows concurrency in adding new torrents. - One problem with BTreeMap is that you have to lock the entire structure to add a new torrent. SkipMap is a lock-free structure. - It is a lock-free structure that uses Atomics internally, which is more lightweight than locks. - Unlike the DashMap implementation, it does not use unsafe code. However: > NOTICE: Race conditions could be introduced if the implementation was incorrect. See https://docs.rs/crossbeam-skiplist/latest/crossbeam_skiplist/#concurrent-access ### Benchmarking Running the Aquatic UDP load test gives the same results: Current best implementation: ```output Requests out: 397287.37/second Responses in: 357549.15/second - Connect responses: 177073.94 - Announce responses: 176905.36 - Scrape responses: 3569.85 - Error responses: 0.00 Peers per announce response: 0.00 Announce responses per info hash: - p10: 1 - p25: 1 - p50: 1 - p75: 1 - p90: 2 - p95: 3 - p99: 104 - p99.9: 287 - p100: 371 ``` SkipMap: ```output Requests out: 396788.68/second Responses in: 357105.27/second - Connect responses: 176662.91 - Announce responses: 176863.44 - Scrape responses: 3578.91 - Error responses: 0.00 Peers per announce response: 0.00 Announce responses per info hash: - p10: 1 - p25: 1 - p50: 1 - p75: 1 - p90: 2 - p95: 3 - p99: 105 - p99.9: 287 - p100: 351 ``` However, benchmarking the repositories using Criterion shows better results in adding and updating multiple torrents in parallel. ![image](https://github.com/torrust/torrust-tracker/assets/58816/4f0736f1-3e70-4a55-8084-3265e9cd087d) ![image](https://github.com/torrust/torrust-tracker/assets/58816/a9c7a034-dbd3-40a0-85d5-c884f1286964) ![image](https://github.com/torrust/torrust-tracker/assets/58816/a3a8f37c-4230-4cfb-9adb-2c4d72ab9fd7) ![image](https://github.com/torrust/torrust-tracker/assets/58816/43a2d9cb-ccbc-496c-9d57-a98887f1575c) ### Conclusion I think we car merge it but I would also continue with the DashMap implementation to compare. ACKs for top commit: josecelano: ACK 12f54e7 Tree-SHA512: 0f300360abe5f70cef21bb5c583ecadedd5a1b233125951d90ffe510e551a9ee7b41ba6dacd23176656fb18095645fdde24a23c23d901e964300e3e1257b3b71
- Loading branch information
Showing
13 changed files
with
388 additions
and
131 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,6 +135,7 @@ | |
"Shareaza", | ||
"sharktorrent", | ||
"SHLVL", | ||
"skiplist", | ||
"socketaddr", | ||
"sqllite", | ||
"subsec", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
packages/torrent-repository/src/repository/skip_map_mutex_std.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use std::collections::BTreeMap; | ||
use std::sync::Arc; | ||
|
||
use crossbeam_skiplist::SkipMap; | ||
use torrust_tracker_configuration::TrackerPolicy; | ||
use torrust_tracker_primitives::info_hash::InfoHash; | ||
use torrust_tracker_primitives::pagination::Pagination; | ||
use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; | ||
use torrust_tracker_primitives::torrent_metrics::TorrentsMetrics; | ||
use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch, PersistentTorrents}; | ||
|
||
use super::Repository; | ||
use crate::entry::{Entry, EntrySync}; | ||
use crate::{EntryMutexStd, EntrySingle}; | ||
|
||
#[derive(Default, Debug)] | ||
pub struct CrossbeamSkipList<T> { | ||
pub torrents: SkipMap<InfoHash, T>, | ||
} | ||
|
||
impl Repository<EntryMutexStd> for CrossbeamSkipList<EntryMutexStd> | ||
where | ||
EntryMutexStd: EntrySync, | ||
EntrySingle: Entry, | ||
{ | ||
fn update_torrent_with_peer_and_get_stats(&self, info_hash: &InfoHash, peer: &peer::Peer) -> (bool, SwarmMetadata) { | ||
let entry = self.torrents.get_or_insert(*info_hash, Arc::default()); | ||
entry.value().insert_or_update_peer_and_get_stats(peer) | ||
} | ||
|
||
fn get(&self, key: &InfoHash) -> Option<EntryMutexStd> { | ||
let maybe_entry = self.torrents.get(key); | ||
maybe_entry.map(|entry| entry.value().clone()) | ||
} | ||
|
||
fn get_metrics(&self) -> TorrentsMetrics { | ||
let mut metrics = TorrentsMetrics::default(); | ||
|
||
for entry in &self.torrents { | ||
let stats = entry.value().lock().expect("it should get a lock").get_stats(); | ||
metrics.complete += u64::from(stats.complete); | ||
metrics.downloaded += u64::from(stats.downloaded); | ||
metrics.incomplete += u64::from(stats.incomplete); | ||
metrics.torrents += 1; | ||
} | ||
|
||
metrics | ||
} | ||
|
||
fn get_paginated(&self, pagination: Option<&Pagination>) -> Vec<(InfoHash, EntryMutexStd)> { | ||
match pagination { | ||
Some(pagination) => self | ||
.torrents | ||
.iter() | ||
.skip(pagination.offset as usize) | ||
.take(pagination.limit as usize) | ||
.map(|entry| (*entry.key(), entry.value().clone())) | ||
.collect(), | ||
None => self | ||
.torrents | ||
.iter() | ||
.map(|entry| (*entry.key(), entry.value().clone())) | ||
.collect(), | ||
} | ||
} | ||
|
||
fn import_persistent(&self, persistent_torrents: &PersistentTorrents) { | ||
for (info_hash, completed) in persistent_torrents { | ||
if self.torrents.contains_key(info_hash) { | ||
continue; | ||
} | ||
|
||
let entry = EntryMutexStd::new( | ||
EntrySingle { | ||
peers: BTreeMap::default(), | ||
downloaded: *completed, | ||
} | ||
.into(), | ||
); | ||
|
||
// Since SkipMap is lock-free the torrent could have been inserted | ||
// after checking if it exists. | ||
self.torrents.get_or_insert(*info_hash, entry); | ||
} | ||
} | ||
|
||
fn remove(&self, key: &InfoHash) -> Option<EntryMutexStd> { | ||
self.torrents.remove(key).map(|entry| entry.value().clone()) | ||
} | ||
|
||
fn remove_inactive_peers(&self, current_cutoff: DurationSinceUnixEpoch) { | ||
for entry in &self.torrents { | ||
entry.value().remove_inactive_peers(current_cutoff); | ||
} | ||
} | ||
|
||
fn remove_peerless_torrents(&self, policy: &TrackerPolicy) { | ||
for entry in &self.torrents { | ||
if entry.value().is_good(policy) { | ||
continue; | ||
} | ||
|
||
entry.remove(); | ||
} | ||
} | ||
} |
Oops, something went wrong.