Muitos serviços fornecem a capacidade de interagir com eles não apenas para usuários comuns por meio de interfaces gráficas refinadas e otimizadas, mas também para desenvolvedores externos de seus programas por meio da API. Ao mesmo tempo, é importante que os serviços controlem a carga em sua infraestrutura. Na situação com usuários comuns, a maioria dos problemas de carga não surgirá devido ao controle do código do aplicativo enviando solicitações ao serviço pelos desenvolvedores do serviço (usuários tentando fazer algo no aplicativo fora da estrutura das interfaces propostas pelos desenvolvedores e das capacidades documentadas, estamos neste artigo não considerado). No caso de desenvolvedores externos, o escopo para criar uma carga no serviço é limitado apenas pela imaginação desses desenvolvedores externos. Para limitar um pouco esse espaço,a prática de introduzir restrições no número de solicitações por unidade de tempo para a API de serviço se tornou generalizada.

, Data Platfrom ManyChat. , , , Intercom, In-App -. , Intercom (, , ..). Intercom - -, . , , ( ), , -. , ML- . , Intercom.
: , , API 1000 . , Intercom, .

, API , . «» «» -, .
- , « » API, API, API. , , API .
« »
, ManyChat Redis — . « » - , API . , API, «», , - . , , «» , - Intercom, , «» .
Redis, List .
, API, consumer API. rate-limit, , , .
— «» - ( BackendQueue), «» (AnalyticsQueue). , , consumer, , .
(JSON):
{
"method_name": "users_update", // ,
"parameters": {"user_id": 123} // ,
}
MVP consumer'a (PHP)
class APICaller
{
private const RETRIES_LIMIT = 5;
private const RATE_LIMIT_TIMEFRAME = 10;
...
public function callMethod(array $payload): void
{
switch ($payload['method_name']) {
case 'users_update':
$this->getIntercomAPI()->users->update($payload['parameters']);
break;
default:
throw new \RuntimeException('Unknown method in API call');
}
}
public function actionProcessQueue(): void
{
while (true) {
$payload = $this->getRedis()->rawCommand('LPOP', 'BackendQueue');
if ($payload === null) {
$payload = $this->getRedis()->rawCommand('LPOP', 'AnalyticsQueue');
}
if ($payload) {
$retries = 0;
$processed = false;
while ($processed === false && $retries < self::RETRIES_LIMIT)
{
try {
$this->callMethod(json_decode($payload));
$processed = true;
} catch (IntercomRateLimitException $e) {
$retries++;
sleep(self::RATE_LIMIT_TIMEFRAME);
}
}
} else {
sleep(1);
}
}
}
}
, , — .
:
Backend (PHP):
...
$payload = [
'method_name' => 'users_update',
'parameters' => ['user_id' => 123, 'registration_date' => '2020-10-01'],
];
$this->getRedis()->rawCommand('RPUSH', 'BackendQueue', json_encode($payload));
...
(Python):
...
payload = {
'method_name': 'users_update',
'parameters': {'user_id': 123, 'advanced_metric': 42},
}
redis_client.rpush('AnalyticsQueue', json.dumps(payload))
...

→
— , Intercom, . — - , API «» , rate-limit, customer'a rate-limit', , - . Redis ( ) consumer'. , , consumer', , . , , , , .
, , consumer' , . consumer' , API .
consumer'a (PHP)
class APICaller
{
private const RETRIES_LIMIT = 5;
private const RATE_LIMIT_TIMEFRAME = 10;
private const INTERCOM_RATE_LIMIT = 150;
private const INTERCOM_API_WORKERS = 5;
...
public function callMethod(array $payload): void
{
switch ($payload['method_name']) {
case 'users_update':
$this->getIntercomAPI()->users->update($payload['parameters']);
break;
default:
throw new \RuntimeException('Unknown method in API call');
}
}
public function actionProcessQueue(): void
{
$currentTimeframe = $this->getCurrentTimeframe();
$currentRequestCount = 0;
while (true) {
if ($currentTimeframe !== $this->getCurrentTimeframe()) {
$currentTimeframe = $this->getCurrentTimeframe();
$currentRequestCount = 0;
} elseif ($currentRequestCount > $this->getProcessRateLimit()) {
usleep(100 * 1000);
continue;
}
$payload = $this->getRedis()->rawCommand('LPOP', 'BackendQueue');
if ($payload === null) {
$payload = $this->getRedis()->rawCommand('LPOP', 'AnalyticsQueue');
}
if ($payload) {
$retries = 0;
$processed = false;
while ($processed === false && $retries < self::RETRIES_LIMIT)
{
try {
$this->callMethod(json_decode($payload));
$processed = true;
} catch (IntercomRateLimitException $e) {
$retries++;
sleep(self::RATE_LIMIT_TIMEFRAME);
}
}
} else {
sleep(1);
}
}
}
private function getProcessRateLimit(): int
{
return (int) floor(self::INTERCOM_RATE_LIMIT / self::INTERCOM_API_WORKERS);
}
private function getCurrentTimeframe(): int
{
return (int) ceil(time() / self::RATE_LIMIT_TIMEFRAME);
}
}
API
- API, . API . — . , , callback'e, consumer' . callback', , .
, , , , .
, , //?
, API , rate-limit, . , . , , , , , .
, , .
API, , , API , API .
, - . , API .