How to Implement Retries
Learn how to implement retries in your code to handle errors and improve application reliability.
Introduction
Alchemy is a powerful platform that provides developers with advanced blockchain tools, such as APIs, monitoring, and analytics, to build their blockchain applications faster and more efficiently. Alchemy's Elastic Throughput system guarantees a given throughput limit measured in compute units per second, but you may still hit your throughput capacity in some cases. In this tutorial, we will explore how to implement retries to handle Alchemy 429 errors.
Option 1: Alchemy SDK
The Alchemy SDK is the easiest way to connect your dApp to the blockchain. It automatically handles retry logic for you. To use the Alchemy SDK, follow these steps:
- Create a new node.js project and Install the Alchemy SDK using npm or yarn:
mkdir my-project
cd my-project
npm install alchemy-sdk
mkdir my-project
cd my-project
yarn add alchemy-sdk
- Import and configure the Alchemy SDK with your API key and choice of network.
// Importing the Alchemy SDK
const { Network, Alchemy } = require('alchemy-sdk');
// Configuring the Alchemy SDK
const settings = {
apiKey: 'demo', // Replace with your Alchemy API Key.
network: Network.ETH_MAINNET, // Replace with your network.
};
// Creating an instance to make requests
const alchemy = new Alchemy(settings);
- Start making requests to the blockchain:
// getting the current block number and logging to the console
alchemy.core.getBlockNumber().then(console.log);
- Here's the complete code:
// Importing the Alchemy SDK
const { Network, Alchemy } = require("alchemy-sdk");
// Configuring the Alchemy SDK
const settings = {
apiKey: "demo", // Replace with your Alchemy API Key.
network: Network.ETH_MAINNET, // Replace with your network.
};
// Creating an instance to make requests
const alchemy = new Alchemy(settings);
// getting the current block number and logging to the console
alchemy.core.getBlockNumber().then(console.log);
The Alchemy SDK automatically handles retries for you, so you don't need to worry about implementing retry logic.
Option 2: Exponential Backoff
Exponential backoff is a standard error-handling strategy for network applications. It is a similar solution to retries, however, instead of waiting random intervals, an exponential backoff algorithm retries requests exponentially, increasing the waiting time between retries up to a maximum backoff time.
Here is an example of an exponential backoff algorithm:
- Make a request.
- If the request fails, wait
1 + random_number_milliseconds
seconds and retry the request. - If the request fails, wait
2 + random_number_milliseconds
seconds and retry the request. - If the request fails, wait
4 + random_number_milliseconds
seconds and retry the request. - And so on, up to a maximum_backoff time...
- Continue waiting and retrying up to some maximum number of retries, but do not increase the wait period between retries.
Where:
- The wait time is
min(((2^n)+random_number_milliseconds), maximum_backoff)
, withn
incremented by 1 for each iteration (request). random_number_milliseconds
is a random number of milliseconds less than or equal to 1000. This helps to avoid cases in which many clients are synchronized by some situation and all retry at once, sending requests in synchronized waves. The value ofrandom_number_milliseconds
is recalculated after each retry request.maximum_backoff
is typically 32 or 64 seconds. The appropriate value depends on the use case.- The client can continue retrying after it has reached the
maximum_backoff
time. Retries after this point do not need to continue increasing backoff time. For example, suppose a client uses amaximum_backoff
time of 64 seconds. After reaching this value, the client can retry every 64 seconds. At some point, clients should be prevented from retrying indefinitely.
To implement exponential backoff in your Alchemy application, you can use a library such as retry
or async-retry
for handling retries in a more structured and scalable way.
Here's an example implementation of exponential backoff using the async-retry
library in a Node.js application where we call the eth_blockNumber
API using Alchemy:
// Setup: npm install [email protected] | npm install async-retry
// Import required modules
const fetch = require("node-fetch");
const retry = require("async-retry");
// Set your API key
const apiKey = "demo"; // Replace with your Alchemy API key
// Set the endpoint and request options
const url = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
const options = {
method: "POST",
headers: { accept: "application/json", "content-type": "application/json" },
body: JSON.stringify({ id: 1, jsonrpc: "2.0", method: "eth_blockNumber" }),
};
// Create a function to fetch with retries
const fetchWithRetries = async () => {
const result = await retry(
async () => {
// Make the API request
const response = await fetch(url, options);
// Parse the response JSON
let json = await response.json();
// If we receive a 429 error (Too Many Requests), log an error and retry
if (json.error && json.error.code === 429) {
console.error("HTTP error 429: Too Many Requests, retrying...");
throw new Error("HTTP error 429: Too Many Requests, retrying...");
}
// Otherwise, return the response JSON
return json;
},
{
retries: 5, // Number of retries before giving up
factor: 2, // Exponential factor
minTimeout: 1000, // Minimum wait time before retrying
maxTimeout: 60000, // Maximum wait time before retrying
randomize: true, // Randomize the wait time
}
);
// Return the result
return result;
};
// Call the fetchWithRetries function and log the result, or any errors
fetchWithRetries()
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
In this example, we define a new function called fetchWithRetries
that uses the async-retry
library to retry the fetch request with exponential backoff. The retry function takes two arguments:
- An async function that performs the fetch request and returns a response object or throws an error.
- An options object that specifies the retry behavior. We set the number of retries to 5, the exponential factor to 2, and the minimum and maximum wait times to 1 second and 60 seconds, respectively.
Finally, we call the fetchWithRetries
function and log the result or the error to the console.
Option 3: Simple Retries
If exponential backoff poses a challenge to you, a simple retry solution is to wait a random interval between 1000 and 1250 milliseconds after receiving a 429 response and sending the request again, up to some maximum number of attempts you are willing to wait.
Here's an example implementation of simple retries in a node.js application where we call the eth_blocknumber
API using Alchemy:
// Setup: npm install [email protected]
// Import required modules
const fetch = require("node-fetch");
// Set your API key
const apiKey = "demo"; // Replace with your Alchemy API key
// Set the endpoint and request options
const url = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
const options = {
method: "POST",
headers: { accept: "application/json", "content-type": "application/json" },
body: JSON.stringify({ id: 1, jsonrpc: "2.0", method: "eth_blockNumber" }),
};
const maxRetries = 5; // Maximum number of retries before giving up
let retries = 0; // Current number of retries
// Create a function to make the request
function makeRequest() {
fetch(url, options)
.then((res) => {
if (res.status === 429 && retries < maxRetries) {
// If we receive a 429 response, wait for a random amount of time and try again
const retryAfter = Math.floor(Math.random() * 251) + 1000; // Generate a random wait time between 1000ms and 1250ms
console.log(`Received 429 response, retrying after ${retryAfter} ms`);
retries++;
setTimeout(() => {
makeRequest(); // Try the request again after the wait time has elapsed
}, retryAfter);
} else if (res.ok) {
return res.json(); // If the response is successful, return the JSON data
} else {
throw new Error(`Received ${res.status} status code`); // If the response is not successful, throw an error
}
})
.then((json) => console.log(json)) // Log the JSON data if there were no errors
.catch((err) => {
if (retries < maxRetries) {
console.error(`Error: ${err.message}, retrying...`);
retries++;
makeRequest(); // Try the request again
} else {
console.error(`Max retries reached, exiting: ${err.message}`);
}
});
}
makeRequest(); // Call the function to make the initial request.
-
In this example, we define a
maxRetries
constant to limit the number of retries we're willing to wait. We also define aretries
variable to keep track of how many times we've retried so far. -
We then define the
makeRequest()
function, which is responsible for making the API request. We use thefetch
function to send the request with the specifiedurl
and options. -
We then check the response status: if it's a
429 (Too Many Requests)
response and we haven't reached themaxRetries
limit, we wait a random interval between 1000 and 1250 milliseconds before callingmakeRequest()
again. Otherwise, if the response isOK
, we parse the JSON response usingres.json()
and log it to the console. If the response status is anything else, we throw an error. -
If an error is caught, we check if we've reached the
maxRetries
limit. If we haven't, we log an error message and callmakeRequest()
again after waiting a random interval between 1000 and 1250 milliseconds. If we have reached themaxRetries
limit, we log an error message and exit the function.
Finally, we call makeRequest() to start the process.
Option 4: Retry-After
If you're using HTTP instead of WebSockets, you might come across a 'Retry-After' header in the HTTP response. This header serves as the duration you should wait before initiating a subsequent request. Despite the utility of the 'Retry-After' header, we continue to advise the use of exponential backoff. This is because the 'Retry-After' header only provides a fixed delay duration, while exponential backoff offers a more adaptable delay scheme. By adjusting the delay durations, exponential backoff can effectively prevent a server from being swamped with a high volume of requests in a short time frame.
Here's an example implementation of "Retry-After" in a node.js application where we call the eth_blocknumber
API using Alchemy:
// Setup: npm install [email protected]
// Import required modules
const fetch = require("node-fetch");
// Set your API key
const apiKey = "demo"; // Replace with your Alchemy API key
// Set the endpoint and request options
const url = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
body: JSON.stringify({ id: 1, jsonrpc: "2.0", method: "eth_blockNumber" }),
};
const maxRetries = 5; // maximum number of retries
let retries = 0; // number of retries
// Create a function to fetch with retries
function makeRequest() {
fetch(url, options)
.then((res) => {
if (res.status === 429 && retries < maxRetries) {
// check for 429 status code and if max retries not reached
const retryAfter = res.headers.get("Retry-After"); // get the value of Retry-After header in the response
if (retryAfter) {
// if Retry-After header is present
const retryAfterMs = parseInt(retryAfter) * 1000; // convert Retry-After value to milliseconds
console.log(
`Received 429 response, retrying after ${retryAfter} seconds`
);
retries++;
setTimeout(() => {
makeRequest(); // call the same function after the delay specified in Retry-After header
}, retryAfterMs);
} else {
// if Retry-After header is not present
const retryAfterMs = Math.floor(Math.random() * 251) + 1000; // generate a random delay between 1 and 250 milliseconds
console.log(
`Received 429 response, retrying after ${retryAfterMs} ms`
);
retries++;
setTimeout(() => {
makeRequest(); // call the same function after the random delay
}, retryAfterMs);
}
} else if (res.ok) {
// if response is successful
return res.json(); // parse the response as JSON
} else {
throw new Error(`Received ${res.status} status code`); // throw an error for any other status code
}
})
.then((json) => console.log(json)) // log the JSON response
.catch((err) => {
if (retries < maxRetries) {
// if max retries not reached
console.error(`Error: ${err.message}, retrying...`);
retries++;
makeRequest(); // call the same function again
} else {
// if max retries reached
console.error(`Max retries reached, exiting: ${err.message}`);
}
});
}
makeRequest(); // call the makeRequest function to start the retry loop
- The code starts by defining the API endpoint URL and the request options. It then sets up a function
makeRequest()
that usesfetch()
to make a POST request to the API. - If the response status code is
429 (Too Many Requests)
, the code checks for aRetry-After
header in the response. - If the header is present, the code retries the request after the number of seconds specified in the header.
- If the header is not present, the code generates a random retry time between 1 and 250ms and retries the request after that time.
- If the response status code is not
429
and is notOK
, the code throws an error. - If the response is
OK
, the code returns the response JSON. If there is an error, the code catches the error and retries the request if the number of retries is less than the maximum number of retries. - If the number of retries is equal to the maximum number of retries, the code logs an error message and exits.
Conclusion
In conclusion, retries are an important error-handling strategy for network applications that can help improve application reliability and handle errors. In this tutorial, we discussed 4 ways in which we can implement retries namely: Exponential-Backoff, Retry-After, Simple Retries and Alchemy SDK.
By implementing retries in your Alchemy application, you can help ensure that your application can handle errors and continue to function reliably even in the face of unexpected errors and network disruptions.
Updated 9 months ago