Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance optimization: create a new torrent repository using a SkipMap instead of a BTreeMap #778

Conversation

josecelano
Copy link
Member

@josecelano josecelano commented Apr 5, 2024

This PR implements a new Torrent Repository replacing the outer BTreeMap for torrents with a SkipMap.

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:

  
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:

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

image

image

image

Conclusion

I think we car merge it but I would also continue with the DashMap implementation to compare.

It will be used to create a new torrent repository implementation that
allos adding torrents in parallel. That could be potencially faster than
BTreeMap in a write intensive context.
SkipMap is an ordered map based on a lock-free skip list.
It's al alternative to BTreeMap which supports concurrent access across
multiple threads.

One of the performance problems with the current solution is we can only
add one torrent at the time because threads need to lock the whole
BTreeMap. The SkipMap should avoid that problem.

More info about SkiMap:

https://docs.rs/crossbeam-skiplist/latest/crossbeam_skiplist/struct.SkipMap.html#method.remove

The aquatic UDP load test was executed with the current implementation
and the new one:

Current Implementation:

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

New Implementation:

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

The result is pretty similar but the benchmarking for the repository
using criterios shows that this implementations is a litle bit better
than the current one.
@josecelano josecelano added the Optimization Make it Faster label Apr 5, 2024
Copy link

codecov bot commented Apr 5, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 77.84%. Comparing base (2277099) to head (12f54e7).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #778      +/-   ##
===========================================
+ Coverage    77.71%   77.84%   +0.12%     
===========================================
  Files          158      158              
  Lines         8711     8762      +51     
===========================================
+ Hits          6770     6821      +51     
  Misses        1941     1941              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

It's been used in the src/packages/torrent-repository package.
@josecelano josecelano force-pushed the 777-performance-optimization-create-a-new-torrent-repository-using-a-skipmap-instead-of-a-btreemap branch from 9d5d535 to 0989285 Compare April 5, 2024 17:31
@josecelano josecelano marked this pull request as ready for review April 5, 2024 18:05
@josecelano josecelano added this to the v3.0.0 milestone Apr 5, 2024
@da2ce7
Copy link
Contributor

da2ce7 commented Apr 6, 2024

Looks good, but it would be good to hook it up to the integration tests as well.

@josecelano
Copy link
Member Author

Looks good, but it would be good to hook it up to the integration tests as well.

OK. I did not see that.

@josecelano josecelano marked this pull request as draft April 7, 2024 10:15
@josecelano josecelano marked this pull request as ready for review April 8, 2024 14:54
@josecelano
Copy link
Member Author

ACK 12f54e7

@josecelano
Copy link
Member Author

Hi @da2ce7, I've added the new repo implementation to the tests. I will merge as it is, but since the new repo does not use a BTReeMap for the outer collection and we don't need the outer lock, I think we should rename the repos in the enum and fixtures.

The current names are:

pub(crate) enum Repo {
    RwLockStd(TorrentsRwLockStd),
    RwLockStdMutexStd(TorrentsRwLockStdMutexStd),
    RwLockStdMutexTokio(TorrentsRwLockStdMutexTokio),
    RwLockTokio(TorrentsRwLockTokio),
    RwLockTokioMutexStd(TorrentsRwLockTokioMutexStd),
    RwLockTokioMutexTokio(TorrentsRwLockTokioMutexTokio),
    SkipMapMutexStd(TorrentsSkipMapMutexStd),
}

Including the new implementation, we always have a pattern like this:

[STD_RW_LOCK|TOKIO_RW_LOCK] -> OUTER_COLLECTION -> INNER_COLLECTION -> [STD_MUTEX|TOKIO_MUTEX -> [ARC] -> [STD_LOCK|TOKIO_LOCK] -> ENTRY

OUTER_COLLECTION: torrents
INNER_COLLECTION: peer list
[]: optional

We could rename them to:

pub(crate) enum Repo {
    //          Outer     Inner
    RwLockStd___BTreeMap__BTreeMap_None_________(TorrentsRwLockStd),
    RwLockStd___BTreeMap__BTreeMap_ArcMutexStd__(TorrentsRwLockStdMutexStd),
    RwLockStd___BTreeMap__BTreeMap_ArcMutexTokio(TorrentsRwLockStdMutexTokio),
    RwLockTokio_BTreeMap__BTreeMap_None_________(TorrentsRwLockTokio),
    RwLockTokio_BTreeMap__BTreeMap_ArcMutexStd__(TorrentsRwLockTokioMutexStd),
    RwLockTokio_BTreeMap__BTreeMap_ArcMutexTokio(TorrentsRwLockTokioMutexTokio),
    None________SkipMap___BTreeMap_ArcMutexStd__(TorrentsSkipMapMutexStd),
}

Underscores and "None" are to highlight the pattern.

Or just:

pub(crate) enum Repo {
    //          Outer     Inner
    Impl1(TorrentsRwLockStd),
    Impl2(TorrentsRwLockStdMutexStd),
    Impl3(TorrentsRwLockStdMutexTokio),
    Impl4(TorrentsRwLockTokio),
    Impl5(TorrentsRwLockTokioMutexStd),
    Impl6(TorrentsRwLockTokioMutexTokio),
    Impl7(TorrentsSkipMapMutexStd),
}

A generic name will be easier to maintain.

An option in the middle could be:

pub(crate) enum Repo {
    //  Outer and Inner Collections
    DoubleBTreeMapImpl1(TorrentsRwLockStd),
    DoubleBTreeMapImpl1(TorrentsRwLockStdMutexStd),
    DoubleBTreeMapImpl1(TorrentsRwLockStdMutexTokio),
    DoubleBTreeMapImpl1(TorrentsRwLockTokio),
    DoubleBTreeMapImpl1(TorrentsRwLockTokioMutexStd),
    DoubleBTreeMapImpl1(TorrentsRwLockTokioMutexTokio),
    SkipMapBTreeMap(TorrentsSkipMapMutexStd),
}

Just mention the collections types and remove the lock types.

@josecelano josecelano merged commit 6eff113 into torrust:develop Apr 8, 2024
17 checks passed
josecelano added a commit that referenced this pull request Apr 9, 2024
… using `DashMap`

4030fd1 fix: torrent repository tests. DashMap is not ordered (Jose Celano)
1e76c17 chore: add dashmap cargo dep to cargp machete (Jose Celano)
00ee9db feat: [#565] new torrent repository implementation usind DashMap (Jose Celano)
78b46c4 chore(deps): add cargo dependency: dashmap (Jose Celano)

Pull request description:

  Relates to:

  - #778
  - #567 (comment)

  This PR adds a new torrent repository implementation where the outer collection for torrents uses a [DashMap](https://docs.rs/dashmap/latest/dashmap/).

  This is something @mickvandijke was working on this [PR](#645).

  There have been many changes. @da2ce7 has extracted a [package for the repositories](https://github.com/torrust/torrust-tracker/tree/develop/packages/torrent-repository).

  This PR adds a new repo using DashMap to the new package. However, it does not implement some of the extra features @mickvandijke added to the other [PR](#645). For example, it does not limit memory consumption. None of the other repos have that feature, so I suggest merging this PR and implementing that feature in the future for all repos.

  ### Why

  The current repository used in production is the one using a [SkipMap](https://docs.rs/crossbeam-skiplist/latest/crossbeam_skiplist/struct.SkipMap.html) data structure. DashMap was the first type we considered to allow adding new torrents in parallels.

  The implementation with DashMap has not been merged because it does not guarantee the order when you iterate over the torrents. The tracker API returns torrents ordered by InfoHash. To avoid breaking the API that PR would need to add a new data structure (kind of Index) to keep the torrent list ordered.

  This implementation helps us run performance tests with this option without spending much time fixing its limitations. It looks like the performance is similar to the SkiMap, so we don't need to use it in production now. We only need to keep it for benchmarking in the future if other versions are better.

  ### Benchmarking

  Running the Aquatic UDP load test, the DashMap looks slightly better.

  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
  ```

  DashMap with initial capacity 0 (best result. On average is lower, similar to SkipMap):

  ```output
  Requests out: 410658.38/second
  Responses in: 365892.86/second
    - Connect responses:  181258.91
    - Announce responses: 181005.95
    - Scrape responses:   3628.00
    - 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: 295
    - p100: 363
  ```

  With Criterion:

  ![image](https://github.com/torrust/torrust-tracker/assets/58816/1e1fca2d-821d-4e2c-adf8-49e055758bd0)

  ![image](https://github.com/torrust/torrust-tracker/assets/58816/fcb9a32f-c4f1-4ffd-9797-4a74fc000336)

  ![image](https://github.com/torrust/torrust-tracker/assets/58816/efe2d788-31ac-4997-82f6-d022aa8f79a0)

  ![image](https://github.com/torrust/torrust-tracker/assets/58816/f64a4e5a-a363-48cd-87d9-78162cda11d4)

  ### Conclusion

  From my point of view, other [performance optimisations](#774) have more potential than this, considering this implementation is not finished. The changes needed to finish this implementation will probably decrease the performance obtained in this benchmarking. In the future, we can review this option if we change the [tracker API behaviour to getting all torrents](#775).

ACKs for top commit:
  josecelano:
    ACK 4030fd1

Tree-SHA512: 1a6c56f3aecb34fc40c401597efe1663c3cc66903f2d27bf8f2bbc6b058080d487a89b705c224657aaa6059f1c2a8597583e636e30727f9293bb43f460441415
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Optimization Make it Faster
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Performance optimization: create a new torrent repository using a SkipMap instead of a BTreeMap
2 participants