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

in mem storage: got rid of the namespace indirection #344

Merged
merged 2 commits into from
Sep 18, 2024

Conversation

alexsnaps
Copy link
Member

image

Looks better in this screenshot, but I will flamegraph and test this a little further on linux

@alexsnaps alexsnaps changed the title Got rid of the namespace indirection in mem storage: got rid of the namespace indirection May 23, 2024
Copy link
Contributor

@chirino chirino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@alexsnaps
Copy link
Member Author

alexsnaps commented May 27, 2024

image Some further testing, with no `variables`, i.e actually hitting the change! Not that much difference... With the newly added bench scenario: #346

But if I grow the namespaces to a 1000...
image
Then it's a regression!

Now, that's all still on MacOS, I'll test these more rigorously on Linux next...

@alexsnaps
Copy link
Member Author

alexsnaps commented May 27, 2024

Also... if a "tree" (namespace 1 -> * Limits -> 1 Counter) seem to be faster... how would storing these in a BTreeMap work out... 🤔

1000 namespaces with 50 limits each:

image

10 namespaces with 50 limits each

image

Well much... better. Anyways, Imma stop here and run all these on Linux and flamegraph some of these, but some of this would absolutely apply to our distributed storage /cc @chirino

@alexsnaps alexsnaps changed the base branch from main to bench May 27, 2024 13:49
pub struct InMemoryStorage {
limits_for_namespace: RwLock<NamespacedLimitCounters<AtomicExpiringValue>>,
simple_limits: RwLock<HashMap<Limit, AtomicExpiringValue>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

use std::ops::Deref;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime};

pub struct InMemoryStorage {
simple_limits: RwLock<HashMap<Limit, AtomicExpiringValue>>,
simple_limits: RwLock<BTreeMap<Limit, AtomicExpiringValue>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

@alexsnaps
Copy link
Member Author

alexsnaps commented May 27, 2024

Did some more and, by now, relatively extensive testing... having the Limits comparable and store AtomicExpiringValue in a BTreeMap is giving the best results in most of the cases. When you have Limits with many (in the 100s) of Conditions, performance degrades. Still need to look into that further... But here is the callstack for Hash based vs Cmp based when searching for the Counters:

Hash based: HashMap<Namespace, HashMap<Limit, AtomicExpiringValue>>

Screenshot from 2024-05-27 16-21-23

Cmp based: BTreeMap<Limit, AtomicExpiringValue>

Screenshot from 2024-05-27 16-23-13

Giving us this kind of results:

Memory/check_rate_limited_and_update/10 namespaces with 50 limits each with 10 conditions and 0 vari...
                        time:   [253.02 µs 254.21 µs 255.86 µs]
                        change: [-9.1814% -8.4315% -7.7729%] (p = 0.00 < 0.05)
                        Performance has improved.

Screenshot from 2024-05-27 16-34-04

Zooming out...

Right now the worst offender tho, is not where we are looking for, in which ever storage version here btw:
Screenshot from 2024-05-27 16-27-57

RateLimiter::counters_that_applies spends all its time clone() the Limits (and composing parts) to then re-clone again and map into Counters... I'll have a look at that next.

What's next?

🛑 This cannot be merged! ⚠️

As of today we use the JSON serialized form of Limits to look counters up in Redis... It is highly unlikely that switching from HashMaps for our Limit field to BTreeMap would preserve the order we obtained in our own sorting functions.

So we first need to find a backwards compatible way of doing this all (i.e. having the crate::storage::keys::* functions keep on relying on the previous behavior, while using the new representation in-memory)

Limiter::counters_that_applies

I think we can probably store Arc<Limit> and use these instead of a full blown HashSet<Limit> on each request, requiring the cloning of all the Limits.

Base automatically changed from bench to main May 28, 2024 11:37
@alexsnaps alexsnaps force-pushed the inmem_single_lookup branch 2 times, most recently from 88fc6f8 to d35a266 Compare September 12, 2024 14:32
@alexsnaps
Copy link
Member Author

🛑 This cannot be merged! ⚠️

As of today we use the JSON serialized form of Limits to look counters up in Redis... It is highly unlikely that switching from >HashMaps for our Limit field to BTreeMap would preserve the order we obtained in our own sorting functions.

I'll break the format anyways with more PRs coming… which is why we are releasing Limitador v2, with that in mind, maybe we are ok merging this?

@alexsnaps alexsnaps marked this pull request as ready for review September 12, 2024 14:34
Signed-off-by: Alex Snaps <alex@wcgw.dev>
Signed-off-by: Alex Snaps <alex@wcgw.dev>
Copy link
Contributor

@didierofrivia didierofrivia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪜

@alexsnaps alexsnaps merged commit cdb9850 into main Sep 18, 2024
9 checks passed
@alexsnaps alexsnaps deleted the inmem_single_lookup branch September 18, 2024 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants