Skip to content

Commit a47509d

Browse files
fix(clients): reduce chances of Push rate limiting (#5153) (generated) [skip ci]
Co-authored-by: Clément Vannicatte <[email protected]>
1 parent 3160f87 commit a47509d

File tree

44 files changed

+352
-303
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+352
-303
lines changed

clients/algoliasearch-client-go/algolia/ingestion/api_ingestion.go

Lines changed: 37 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/api/IngestionClient.java

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.time.Duration;
1515
import java.util.ArrayList;
1616
import java.util.EnumSet;
17+
import java.util.Iterator;
1718
import java.util.List;
1819
import java.util.Map;
1920
import java.util.concurrent.CompletableFuture;
@@ -4997,9 +4998,19 @@ public <T> List<WatchResponse> chunkedPush(
49974998
) {
49984999
List<WatchResponse> responses = new ArrayList<>();
49995000
List<T> records = new ArrayList<>();
5001+
int offset = 0;
5002+
int waitBatchSize = batchSize / 10;
5003+
if (waitBatchSize < 1) {
5004+
waitBatchSize = batchSize;
5005+
}
5006+
5007+
Iterator<T> it = objects.iterator();
5008+
T current = it.next();
5009+
5010+
while (true) {
5011+
records.add(current);
50005012

5001-
for (T item : objects) {
5002-
if (records.size() == batchSize) {
5013+
if (records.size() == batchSize || !it.hasNext()) {
50035014
WatchResponse watch = this.push(
50045015
indexName,
50055016
new PushTaskPayload().setAction(action).setRecords(this.objectsToPushTaskRecords(records)),
@@ -5011,41 +5022,38 @@ public <T> List<WatchResponse> chunkedPush(
50115022
records.clear();
50125023
}
50135024

5014-
records.add(item);
5015-
}
5025+
if (waitForTasks && responses.size() > 0 && (responses.size() % waitBatchSize == 0 || !it.hasNext())) {
5026+
responses
5027+
.subList(offset, Math.min(offset + waitBatchSize, responses.size()))
5028+
.forEach(response -> {
5029+
TaskUtils.retryUntil(
5030+
() -> {
5031+
try {
5032+
return this.getEvent(response.getRunID(), response.getEventID());
5033+
} catch (AlgoliaApiException e) {
5034+
if (e.getStatusCode() == 404) {
5035+
return null;
5036+
}
5037+
5038+
throw e;
5039+
}
5040+
},
5041+
(Event resp) -> {
5042+
return resp != null;
5043+
},
5044+
50,
5045+
null
5046+
);
5047+
});
5048+
5049+
offset += waitBatchSize;
5050+
}
50165051

5017-
if (records.size() > 0) {
5018-
WatchResponse watch = this.push(
5019-
indexName,
5020-
new PushTaskPayload().setAction(action).setRecords(this.objectsToPushTaskRecords(records)),
5021-
waitForTasks,
5022-
referenceIndexName,
5023-
requestOptions
5024-
);
5025-
responses.add(watch);
5026-
}
5052+
if (!it.hasNext()) {
5053+
break;
5054+
}
50275055

5028-
if (waitForTasks) {
5029-
responses.forEach(response -> {
5030-
TaskUtils.retryUntil(
5031-
() -> {
5032-
try {
5033-
return this.getEvent(response.getRunID(), response.getEventID());
5034-
} catch (AlgoliaApiException e) {
5035-
if (e.getStatusCode() == 404) {
5036-
return null;
5037-
}
5038-
5039-
throw e;
5040-
}
5041-
},
5042-
(Event resp) -> {
5043-
return resp != null;
5044-
},
5045-
50,
5046-
null
5047-
);
5048-
});
5056+
current = it.next();
50495057
}
50505058

50515059
return responses;

clients/algoliasearch-client-javascript/packages/ingestion/src/ingestionClient.ts

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ export function createIngestionClient({
268268
requestOptions?: RequestOptions,
269269
): Promise<Array<WatchResponse>> {
270270
let records: Array<PushTaskRecords> = [];
271+
let offset = 0;
271272
const responses: Array<WatchResponse> = [];
273+
const waitBatchSize = Math.floor(batchSize / 10) || batchSize;
272274

273275
const objectEntries = objects.entries();
274276
for (const [i, obj] of objectEntries) {
@@ -279,44 +281,48 @@ export function createIngestionClient({
279281
);
280282
records = [];
281283
}
282-
}
283-
284-
let retryCount = 0;
285-
286-
if (waitForTasks) {
287-
for (const resp of responses) {
288-
if (!resp.eventID) {
289-
throw new Error('received unexpected response from the push endpoint, eventID must not be undefined');
290-
}
291284

292-
await createIterablePromise({
293-
func: async () => {
294-
if (resp.eventID === undefined || !resp.eventID) {
295-
throw new Error('received unexpected response from the push endpoint, eventID must not be undefined');
296-
}
297-
298-
return this.getEvent({ runID: resp.runID, eventID: resp.eventID }).catch((error: ApiError) => {
299-
if (error.status === 404) {
300-
return undefined;
285+
if (
286+
waitForTasks &&
287+
responses.length > 0 &&
288+
(responses.length % waitBatchSize === 0 || i === objects.length - 1)
289+
) {
290+
for (const resp of responses.slice(offset, offset + waitBatchSize)) {
291+
if (!resp.eventID) {
292+
throw new Error('received unexpected response from the push endpoint, eventID must not be undefined');
293+
}
294+
295+
let retryCount = 0;
296+
297+
await createIterablePromise({
298+
func: async () => {
299+
if (resp.eventID === undefined || !resp.eventID) {
300+
throw new Error('received unexpected response from the push endpoint, eventID must not be undefined');
301301
}
302302

303-
throw error;
304-
});
305-
},
306-
validate: (response) => response !== undefined,
307-
aggregator: () => (retryCount += 1),
308-
error: {
309-
validate: () => retryCount >= 50,
310-
message: () => `The maximum number of retries exceeded. (${retryCount}/${50})`,
311-
},
312-
timeout: (): number => Math.min(retryCount * 500, 5000),
313-
});
303+
return this.getEvent({ runID: resp.runID, eventID: resp.eventID }).catch((error: ApiError) => {
304+
if (error.status === 404) {
305+
return undefined;
306+
}
307+
308+
throw error;
309+
});
310+
},
311+
validate: (response) => response !== undefined,
312+
aggregator: () => (retryCount += 1),
313+
error: {
314+
validate: () => retryCount >= 50,
315+
message: () => `The maximum number of retries exceeded. (${retryCount}/${50})`,
316+
},
317+
timeout: (): number => Math.min(retryCount * 500, 5000),
318+
});
319+
}
320+
offset += waitBatchSize;
314321
}
315322
}
316323

317324
return responses;
318325
},
319-
320326
/**
321327
* Creates a new authentication resource.
322328
*

clients/algoliasearch-client-php/lib/Api/IngestionClient.php

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2861,49 +2861,50 @@ public function chunkedPush(
28612861
$responses = [];
28622862
$records = [];
28632863
$count = 0;
2864+
$offset = 0;
2865+
$waitBatchSize = (int) ($batchSize / 10);
2866+
if ($waitBatchSize < 1) {
2867+
$waitBatchSize = $batchSize;
2868+
}
28642869

28652870
foreach ($objects as $object) {
28662871
$records[] = $object;
28672872
$ok = false;
2873+
++$count;
28682874

2869-
if (sizeof($records) === $batchSize || $count === sizeof($objects) - 1) {
2875+
if (sizeof($records) === $batchSize || $count === sizeof($objects)) {
28702876
$responses[] = $this->push($indexName, ['action' => $action, 'records' => $records], false, $referenceIndexName, $requestOptions);
28712877
$records = [];
28722878
}
28732879

2874-
++$count;
2875-
}
2880+
if ($waitForTasks && !empty($responses) && (0 === sizeof($responses) % $waitBatchSize || $count === sizeof($objects))) {
2881+
$timeoutCalculation = 'Algolia\AlgoliaSearch\Support\Helpers::linearTimeout';
28762882

2877-
if (!empty($records)) {
2878-
$responses[] = $this->push($indexName, ['action' => $action, 'records' => $records], false, $referenceIndexName, $requestOptions);
2879-
}
2883+
foreach (array_slice($responses, $offset, $waitBatchSize) as $response) {
2884+
$retry = 0;
28802885

2881-
if ($waitForTasks && !empty($responses)) {
2882-
$timeoutCalculation = 'Algolia\AlgoliaSearch\Support\Helpers::linearTimeout';
2886+
while ($retry < 50) {
2887+
try {
2888+
$this->getEvent($response['runID'], $response['eventID']);
28832889

2884-
foreach ($responses as $response) {
2885-
$retry = 0;
2890+
$ok = true;
28862891

2887-
while ($retry < 50) {
2888-
try {
2889-
$this->getEvent($response['runID'], $response['eventID']);
2892+
break;
2893+
} catch (NotFoundException $e) {
2894+
// just retry
2895+
}
28902896

2891-
$ok = true;
2892-
2893-
break;
2894-
} catch (NotFoundException $e) {
2895-
// just retry
2897+
++$retry;
2898+
usleep(
2899+
call_user_func_array($timeoutCalculation, [$this->config->getWaitTaskTimeBeforeRetry(), $retry])
2900+
);
28962901
}
28972902

2898-
++$retry;
2899-
usleep(
2900-
call_user_func_array($timeoutCalculation, [$this->config->getWaitTaskTimeBeforeRetry(), $retry])
2901-
);
2902-
}
2903-
2904-
if (false === $ok) {
2905-
throw new ExceededRetriesException('Maximum number of retries (50) exceeded.');
2903+
if (false === $ok) {
2904+
throw new ExceededRetriesException('Maximum number of retries (50) exceeded.');
2905+
}
29062906
}
2907+
$offset = $offset + $waitBatchSize;
29072908
}
29082909
}
29092910

clients/algoliasearch-client-php/lib/FormDataProcessor.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
*/
1313

1414
/**
15-
* Ingestion API.
15+
* Search API.
1616
*
17-
* The Ingestion API lets you connect third-party services and platforms with Algolia and schedule tasks to ingest your data. The Ingestion API powers the no-code [data connectors](https://dashboard.algolia.com/connectors). ## Base URLs The base URLs for requests to the Ingestion API are: - `https://data.us.algolia.com` - `https://data.eu.algolia.com` Use the URL that matches your [analytics region](https://dashboard.algolia.com/account/infrastructure/analytics). **All requests must use HTTPS.** ## Authentication To authenticate your API requests, add these headers: - `x-algolia-application-id`. Your Algolia application ID. - `x-algolia-api-key`. An API key with the necessary permissions to make the request. The required access control list (ACL) to make a request is listed in each endpoint's reference. You can find your application ID and API key in the [Algolia dashboard](https://dashboard.algolia.com/account). ## Request format Request bodies must be JSON objects. ## Response status and errors Response bodies are JSON objects. Successful responses return a `2xx` status. Client errors return a `4xx` status. Server errors are indicated by a `5xx` status. Error responses have a `message` property with more information. ## Version The current version of the Ingestion API is version 1, as indicated by the `/1/` in each endpoint's URL.
17+
* The Algolia Search API lets you search, configure, and manage your indices and records. ## Client libraries Use Algolia's API clients and libraries to reliably integrate Algolia's APIs with your apps. The official API clients are covered by Algolia's [Service Level Agreement](https://www.algolia.com/policies/sla/). See: [Algolia's ecosystem](https://www.algolia.com/doc/guides/getting-started/how-algolia-works/in-depth/ecosystem/) ## Base URLs The base URLs for requests to the Search API are: - `https://{APPLICATION_ID}.algolia.net` - `https://{APPLICATION_ID}-dsn.algolia.net`. If your subscription includes a [Distributed Search Network](https://dashboard.algolia.com/infra), this ensures that requests are sent to servers closest to users. Both URLs provide high availability by distributing requests with load balancing. **All requests must use HTTPS.** ## Retry strategy To guarantee high availability, implement a retry strategy for all API requests using the URLs of your servers as fallbacks: - `https://{APPLICATION_ID}-1.algolianet.com` - `https://{APPLICATION_ID}-2.algolianet.com` - `https://{APPLICATION_ID}-3.algolianet.com` These URLs use a different DNS provider than the primary URLs. You should randomize this list to ensure an even load across the three servers. All Algolia API clients implement this retry strategy. ## Authentication To authenticate your API requests, add these headers: - `x-algolia-application-id`. Your Algolia application ID. - `x-algolia-api-key`. An API key with the necessary permissions to make the request. The required access control list (ACL) to make a request is listed in each endpoint's reference. You can find your application ID and API key in the [Algolia dashboard](https://dashboard.algolia.com/account). ## Request format Depending on the endpoint, request bodies are either JSON objects or arrays of JSON objects, ## Parameters Parameters are passed as query parameters for GET and DELETE requests, and in the request body for POST and PUT requests. Query parameters must be [URL-encoded](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding). Non-ASCII characters must be UTF-8 encoded. Plus characters (`+`) are interpreted as spaces. Arrays as query parameters must be one of: - A comma-separated string: `attributesToRetrieve=title,description` - A URL-encoded JSON array: `attributesToRetrieve=%5B%22title%22,%22description%22%D` ## Response status and errors The Search API returns JSON responses. Since JSON doesn't guarantee any specific ordering, don't rely on the order of attributes in the API response. Successful responses return a `2xx` status. Client errors return a `4xx` status. Server errors are indicated by a `5xx` status. Error responses have a `message` property with more information. ## Version The current version of the Search API is version 1, as indicated by the `/1/` in each endpoint's URL.
1818
*
1919
* The version of the OpenAPI document: 1.0.0
2020
* Generated by: https://openapi-generator.tech
@@ -29,7 +29,7 @@
2929

3030
namespace Algolia\AlgoliaSearch;
3131

32-
use Algolia\AlgoliaSearch\Model\Ingestion\ModelInterface;
32+
use Algolia\AlgoliaSearch\Model\Search\ModelInterface;
3333
use DateTime;
3434
use GuzzleHttp\Psr7\Utils;
3535
use Psr\Http\Message\StreamInterface;

0 commit comments

Comments
 (0)