-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[RateLimiter] Always store SlidingWindows with an expiration set #44996
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
Conversation
I think the purpose was precisely to reduce the storage size |
It does not seem to have any effect though, so I think it was mostly there to exclude the FYI here is the full output with igbinary:
You can see it is quite a bit shorter with __serialize, but it's much more with the RateLimiter classes because somehow the serialized output includes the property names with full class name every time, it's extremely wasteful: One example from my prod redis server: |
Oh no, you are very correct. This is indeed a bug. I’m not sure about your solution. I think there is a good reason why we didn’t set a new expectation time, Ie we don’t want to move the window. (Yes, it is “sliding” but we need a clear start and end of each window which is defined by the cache item) I need to give this a second look asap to confirm. (I’m currently traveling and my computer broke down) |
@Nyholm ping here? I'd be happy to get this resolved at some point, as our redis keeps filling up right now :) Thanks to the bug right now, the cache item doesn't expire ever if you hit more than once. So either the way I see it, either:
So the fix seems correct to me (it would only change the last bit of the last bullet point above), but maybe I misunderstood something of course. |
Rebased/retargetted to 5.4 as 5.3 is now EOL |
Please let me delay this week too. I have an open source session planned on Sunday. If I fail to come back then just ignore me. Sorry for being unavailable. |
Any news here? |
bbd6f76
to
3a72990
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
It does not seem to have any effect though, so I think it was mostly there to exclude the cached property
Indeed, I don't think storage size was considered here.
src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php
Outdated
Show resolved
Hide resolved
5d96d1d
to
5336906
Compare
5336906
to
54cacfe
Compare
Thank you @Seldaek. |
I tested the patch with the latest changes from @nicolas-grekas and the good news is it does work well doing:
instead of:
So much more efficient storage, and SETEX instead of SET 👍🏻 Bad news is the migration path is a bit broken (like you get a fatal error..) if it reloads existing data which was serialized with __sleep and tries to unserialize using the new __unserialize. I'll send a follow-up PR. |
thanks for the quick checking in a real app :) |
Here #45876 |
…che (Seldaek) This PR was merged into the 5.4 branch. Discussion ---------- Add BC layer to handle old objects already present in cache | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | Refs #44996 | License | MIT | Doc PR | symfony/symfony-docs#... <!-- required for new features --> When reloading an old RateLimiter instance from cache with the new code I did get an exception because windowEndAt was a string and getHitCount does windowEndAt - intervalInSeconds, and string - int fails. The reason is the new code assigns the id to windowEndAt via: ``` $pack = key($data); $this->windowEndAt = $data[$pack]; ``` I dumped $this and $data in __unserialize just FYI and you get this: ``` object(Symfony\Component\RateLimiter\Policy\SlidingWindow)[959] private 'id' => null private 'hitCount' => int 0 private 'hitCountForLastWindow' => int 0 private 'intervalInSeconds' => null private 'windowEndAt' => null ``` ``` array (size=5) '�Symfony\Component\RateLimiter\Policy\SlidingWindow�id' => string 'login_per_ip-127.0.0.1' (length=22) '�Symfony\Component\RateLimiter\Policy\SlidingWindow�hitCount' => int 2 '�Symfony\Component\RateLimiter\Policy\SlidingWindow�intervalInSeconds' => int 60 '�Symfony\Component\RateLimiter\Policy\SlidingWindow�hitCountForLastWindow' => int 0 '�Symfony\Component\RateLimiter\Policy\SlidingWindow�windowEndAt' => float 1648544909.4762 ``` I only tested the code in real conditions for SlidingWindow, but I do believe/hope the same applies for the other two policies. Commits ------- f039ee6 Add BC layer to handle old objects already present in cache
Confirmed to work in prod now with 6.0.7 🥳 Thanks again everyone. |
See #44995 for more details. The short story is that this code causes the cache item to be overwritten without TTL in
symfony/src/Symfony/Component/RateLimiter/Storage/CacheStorage.php
Lines 31 to 37 in 6bffe41
So our redis DB is filling up with rate limiter data, which is not the expected outcome. I couldn't really figure out why the logic was the way it was so it seems safe for me to remove this, but maybe I missed something.
As a related side note, implementing __serialize/__unserialize in SlidingWindow and co would be good as it would reduce the storage requirements in the cache from 400 bytes to 100 bytes per rate limiter stored. I'm happy to help with that just let me know, I am not sure if there was a good reason not to implement this or if storage size was simply not a consideration. I see __sleep is implemented but that does not seem to help here, not sure what the purpose is/was.