Skip to content

Commit f889bab

Browse files
authored
fix: handle graphql rate limit (#467)
* fix: handle graphql rate limit * fix: copilot reviews
1 parent 05a6e74 commit f889bab

File tree

1 file changed

+64
-14
lines changed

1 file changed

+64
-14
lines changed

crawler/src/main.js

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const repoWithDetailsArray = [];
99
const API_KEY = process.env.API_KEY;
1010
const API_URL = process.env.API_URL;
1111
const REPO_NAMES = process.env.REPO_NAMES; //comma separated list of repo names within bcgov org
12-
12+
const BASE_DELAY = process.env.BASE_DELAY ? parseInt(process.env.BASE_DELAY, 10) : 60000; // 1 minute base delay
13+
const MAX_DELAY = process.env.MAX_DELAY ? parseInt(process.env.MAX_DELAY, 10) : 600000; // 10 minutes maximum delay
14+
const JITTER_FACTOR = Math.random() * 0.3; // Random jitter between 0-30%
1315
/**
1416
* Fetches the bcgovpubcode yaml for the specified repo and branch
1517
* @param repoName
@@ -140,20 +142,68 @@ async function bulkLoadPubCodes(yamlArrayAsJson) {
140142
* @param query
141143
* @returns {Promise<*|{}|number[]>}
142144
*/
145+
/**
146+
* Sends a GraphQL query to the GitHub API and handles rate limiting with exponential backoff.
147+
*
148+
* @async
149+
* @function getGraphQlResponseOnQuery
150+
* @param {string} query - The GraphQL query to send to the GitHub API
151+
* @returns {Promise<Object>} The response data from the GraphQL API
152+
* @throws {Error} Throws an error if all retry attempts fail or if a non-rate-limit error occurs
153+
*
154+
* @description
155+
* This function sends a GraphQL query to GitHub's API and implements:
156+
* - Authentication using a token
157+
* - Automatic retry for rate limit errors (up to 5 attempts)
158+
* - Exponential backoff with jitter to respect rate limits
159+
* - Detailed logging of retry attempts
160+
* https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#about-secondary-rate-limits
161+
* Retry delays increase exponentially, with random jitter added:
162+
*/
143163
async function getGraphQlResponseOnQuery(query) {
144-
const response = await axios({
145-
method: "post",
146-
url: "https://api.github.com/graphql",
147-
headers: {
148-
Authorization: `Bearer ${token}`,
149-
"Content-Type": "application/json",
150-
"Cache-Control": "no-cache",
151-
},
152-
data: {
153-
query,
154-
},
155-
});
156-
return response.data;
164+
const maxRetries = 5;
165+
let retries = 0;
166+
167+
while (retries < maxRetries) {
168+
try {
169+
const response = await axios({
170+
method: "post",
171+
url: "https://api.github.com/graphql",
172+
headers: {
173+
Authorization: `Bearer ${token}`,
174+
"Content-Type": "application/json",
175+
"Cache-Control": "no-cache",
176+
},
177+
data: {
178+
query,
179+
},
180+
});
181+
return response.data;
182+
} catch (error) {
183+
if (error.response?.status === 403 && error.response?.data?.message?.includes("rate limit")) {
184+
retries++;
185+
if (retries >= maxRetries) throw error;
186+
187+
188+
189+
// Calculate delay with exponential backoff and jitter
190+
const exponentialPart = BASE_DELAY * (2 ** retries);
191+
const jitterFactor = Math.random() * 0.3; // Random jitter between 0-30%
192+
const jitterAmount = exponentialPart * jitterFactor;
193+
const delay = Math.min(exponentialPart + jitterAmount, MAX_DELAY);
194+
// Example delay values (in milliseconds) per retry:
195+
// Retry 1: ~60000ms (1 min) + up to 18000ms jitter = ~78s
196+
// Retry 2: ~120000ms (2 min) + up to 36000ms jitter = ~156s
197+
// Retry 3: ~240000ms (4 min) + up to 72000ms jitter = ~312s
198+
// Retry 4: ~480000ms (8 min) + up to 144000ms jitter = ~624s
199+
// Retry 5: ~600000ms (10 min, capped, jitter ignored) = 10 min
200+
console.log(`Rate limit hit. Retrying in ${Math.round(delay/1000)} seconds... (Attempt ${retries}/${maxRetries})`);
201+
await new Promise(resolve => setTimeout(resolve, delay));
202+
} else {
203+
throw error;
204+
}
205+
}
206+
}
157207
}
158208

159209
/**

0 commit comments

Comments
 (0)