De nombreux services offrent la possibilité d'interagir avec eux non seulement pour les utilisateurs ordinaires via des interfaces graphiques raffinées et optimisées, mais également pour les développeurs externes à partir de leurs programmes via l'API. Dans le même temps, il est important que les services contrôlent la charge sur leur infrastructure. Dans une situation avec des utilisateurs ordinaires, la plupart des problèmes de charge ne se poseront pas en raison du contrôle du code de l'application qui envoie des requêtes au service par les développeurs du service (utilisateurs essayant de faire quelque chose dans l'application en dehors du cadre des interfaces proposées par les développeurs et des capacités documentées, nous sommes dans cet article pas considéré). Dans le cas des développeurs externes, la possibilité de créer une charge sur le service n'est limitée que par l'imagination de ces développeurs très externes. Pour limiter un peu cet espace,la pratique consistant à introduire des restrictions sur le nombre de requêtes par unité de temps à l'API de service s'est généralisée.

, 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 .