-
-
Notifications
You must be signed in to change notification settings - Fork 964
Request limiter - prevent hitting Telegram's API limits #397
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
…etChat() to get numeric value!)
# Conflicts: # src/Telegram.php
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.
Maybe a silly question, but shouldn't we add a request limiter as default behaviour?
Does this slow the bot down a lot?
src/Request.php
Outdated
'editMessageReplyMarkup', | ||
]; | ||
|
||
if ((isset($data['chat_id']) || isset($data['inline_message_id'])) && in_array($action, $limited_methods)) { |
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.
Makes sense to put some extra parenthesis here to clearly define what is being tested, making it easier to read the code.
src/Request.php
Outdated
|
||
$requests = DB::getTelegramRequestCount((isset($data['chat_id']) ? $data['chat_id'] : null), (isset($data['inline_message_id']) ? $data['inline_message_id'] : null)); | ||
|
||
if ($requests['LIMIT_PER_SEC'] <= 0 && ((isset($data['inline_message_id']) && $requests['LIMIT_PER_SEC_ALL'] < 30) || (isset($data['chat_id']) && $data['chat_id'] > 0 && $requests['LIMIT_PER_SEC_ALL'] < 30) || (isset($data['chat_id']) && $data['chat_id'] < 0 && $requests['LIMIT_PER_MINUTE'] < 20))) { |
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.
Wow, this one is quite difficult to understand.
Maybe there's a better way of presenting/evaluating this?
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.
I could transform it into multi-line statement with comments, should be fine?
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.
For this it would make sense to add variables for $data['chat_id']
and $data['inline_message_id']
as they're also used further up.
So instead of all the isset() && ...
you could just use the variables directly.
Will think about this exact spot and how to make it more readable 👍
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.
$chat_id = isset($data['chat_id']) ? $data['chat_id'] : null;
$inline_message_id = isset($data['inline_message_id']) ? $data['inline_message_id'] : null;
if (($chat_id || $inline_message_id) && in_array($action, $limited_methods)) {
$timeout = 60;
while (true) {
if ($timeout <= 0) {
throw new TelegramException('Timed out while waiting for a request spot!');
}
$requests = DB::getTelegramRequestCount($chat_id, $inline_message_id);
if (
$requests['LIMIT_PER_SEC'] == 0 && // No more than one message per second inside a particular chat
(
(($chat_id > 0 || $inline_message_id) && $requests['LIMIT_PER_SEC_ALL'] < 30) || // No more than 30 messages per second globally
($chat_id < 0 && $requests['LIMIT_PER_MINUTE'] < 20) // No more than 20 messages per minute allowed for group chats
)
) {
break;
}
$timeout--;
sleep(1);
}
DB::insertTelegramRequest($action, $data);
}
Seems to look a bit better, what you think?
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.
Yep, looks a lot better, nice 👌
src/Request.php
Outdated
$timeout = 60; | ||
|
||
while (true) { | ||
if ($timeout <= 0) { |
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.
You could also do $timeout--
here instead of $timeout = $timeout - 1;
further down.
Looks awesome, IMHO this should be implemented by default on next releases. |
src/Request.php
Outdated
@@ -1029,21 +1029,30 @@ private static function limitTelegramRequests($action, array $data = []) | |||
'editMessageReplyMarkup', | |||
]; | |||
|
|||
if ((isset($data['chat_id']) || isset($data['inline_message_id'])) && in_array($action, $limited_methods)) { |
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.
I love one-line code instead multiline
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.
@nesttle Me too! (as long as it's readable 😉)
|
||
$requests = DB::getTelegramRequestCount($chat_id, $inline_message_id); | ||
|
||
if ($requests['LIMIT_PER_SEC'] == 0 // No more than one message per second inside a particular chat |
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.
Any way I can (realistically) test these limits?
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.
Doing it alone will be quite hard, you can test the one msg per sec and no more than 20 per minute in groups rules easily, 30 per sec globally will be pretty hard to achieve alone.
Spamming command with callback button editing own message should be good testing ground.
sleep(1); | ||
} | ||
|
||
DB::insertTelegramRequest($action, $data); |
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.
I noticed that entries once added, never get deleted from the DB once handled.
Shouldn't they be cleared again after being processed?
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.
But if I remove them next requests will think they are ok to go.
I could add a query to remove entries older than 1 minute but I don't think it will be good practice to run such query every second (or more under heavy load)...?
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.
Hmm, I'm just thinking long term here. As this is a temporary table, it just gets bigger and bigger, in some cases pretty quick, right?
So some sort of cleanup would be good I think.
Maybe something like:
if (mt_rand(0, 999) === 42) {
do_cleanup();
}
Or maybe I'm going too far here and this isn't really a problem...
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.
I guess, in my case I'm cleaning the table every 5 minutes, set as a task in Mysql.
I have no 'good' idea how to implement cleanup inside this code without adding potential delays or performance losses.
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.
Ok, then let's leave this as is for the moment and passively think of something 😉
Busy trying to test this and get it merged. Hope to get it in soon 😊 |
Didn't find the time to thoroughly test this, but I think it should be ok to merge. |
#157
So, I've been using this implementation for a while now and I don't see any issues so far, neither any exceptions about Telegram returning response that I've hit the limit.
By default disabled because it can be heavy on the database.
Will delay requests using
sleep(1);
.As mentioned in https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
This also applies to message editing! Does not apply to methods like getChat or sendChatAction.
Currently I also included 'retry' mode, sometimes if the bot is heavily used 60 seconds wait might not be enough... should I remove this and simply give it only 60 seconds, no more? Or leave it as is?
Will appreciate anyone to take a look at this!
Suggestions are welcome!