API calls to Key Management Service (KMS) can occasionally fail due to server errors or throttling. This topic describes how to implement exponential backoff to retry failed requests and provides a Java code example.
When to retry
Retry a request when you receive one of the following transient errors:
Server errors (5xx): The server encountered an internal problem. For example, HTTP 500 or 503.
Throttling: The request was denied because the API call rate exceeded the allowed limit. The error code is
Rejected.Throttling.
Do not retry requests that fail due to client errors such as invalid parameters, missing permissions, or incorrect credentials. These errors require you to fix the request before you try again.
Dedicated KMS gateways do not impose an upper limit on API calls. They use the compute and storage resources of the instance to handle as many calls as possible, so throttling errors do not occur with dedicated gateways.
Retry strategies
| Strategy | How it works | Best for |
|---|---|---|
| Simple retry | Retry at fixed intervals within a specified time window. | Low call volume, infrequent failures |
| Exponential backoff | Double the wait time after each consecutive failure, up to a maximum number of retries. | Throttling scenarios, high call volume, or when multiple clients compete for the same resource |
Exponential backoff is preferred because it progressively reduces pressure on the service. When a request is denied due to throttling, spacing out retries gives the service time to recover and reduces the chance of further throttling within a short period.
SDK auto-retry support
Some Alibaba Cloud SDKs have built-in retry policies. For example, the Alibaba Cloud SDK for .NET lets you configure automatic retries directly. If your SDK does not support automatic retries, implement the exponential backoff approach described in the following sections.
Exponential backoff algorithm
Pseudocode
The following pseudocode shows the core logic. Each retry waits twice as long as the previous one:
initialDelay = 200
retries = 0
DO
wait for (2^retries * initialDelay) milliseconds
status = CallSomeAPI()
IF status == SUCCESS
retry = false // Succeeded, stop calling the API again.
ELSE IF status = THROTTLED || status == SERVER_NOT_READY
retry = true // Failed because of throttling or server busy, try again.
ELSE
retry = false // Some other error occurred, stop calling the API again.
END IF
retries = retries + 1
WHILE (retry AND (retries < MAX_RETRIES))With initialDelay = 200 ms, the wait times progress as follows:
| Retry | Wait time |
|---|---|
| 1 | 400 ms |
| 2 | 800 ms |
| 3 | 1,600 ms |
| 4 | 3,200 ms |
| 5 | 6,400 ms |
Best practices
Consider the following practices to make your retry logic more robust:
Cap the maximum backoff: The formula
2^retries * initialDelaygrows without bound. Set a maximum wait time (for example, 30 seconds) so that retries do not stall your application indefinitely.Add jitter: When many clients back off on the same schedule, they can all retry at the same time and trigger another wave of throttling. This is known as the thundering herd problem. Add a small random component to each wait time, such as
waitTime + random(0, waitTime / 2), to spread out retries and reduce collisions.
Retryable and non-retryable errors
Not every error warrants a retry. The following table provides guidance on common KMS error categories.
| Error type | Example | Retry? | Action |
|---|---|---|---|
| Throttling | Rejected.Throttling | Yes | Retry with exponential backoff |
| Server error | HTTP 500, 503 | Yes | Retry with exponential backoff |
| Authentication failure | InvalidAccessKeyId.NotFound, SignatureDoesNotMatch | No | Fix credentials |
| Authorization failure | Forbidden.NoPermission | No | Grant the required permissions |
| Invalid parameter | InvalidParameter, MissingParameter | No | Fix the request parameters |
| Resource not found | Forbidden.KeyNotFound | No | Verify the resource identifier |
Java example: retry throttling errors for the Decrypt operation
The following Java code shows how to use exponential backoff to handle Rejected.Throttling errors when you call the Decrypt operation. You can adapt this code to handle server errors such as HTTP 503.
Estimate the number of requests that the client will initiate within a specific period of time. Then, adjust initialDelay and maxRetries based on the estimation result.
The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. Using the AccessKey pair to perform operations is a high-risk operation. We recommend that you use a RAM user to call API operations or perform routine operations. Do not save the AccessKey ID and AccessKey secret in your project code. Otherwise, the AccessKey pair may be leaked and the security of all resources in your account may be compromised.
In this example, the AccessKey pair is saved in the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables for identity authentication.
For more information about how to configure authentication, see Manage access credentials.
The method for configuring environment variables varies by operating system. For more information, see Configure environment variables in Linux, macOS, and Windows.
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpClientConfig;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.kms.model.v20160120.DecryptRequest;
import com.aliyuncs.kms.model.v20160120.DecryptResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
public class CmkDecrypt {
private static DefaultAcsClient kmsClient;
private static DefaultAcsClient kmsClient(String regionId, String accessKeyId, String accessKeySecret) {
IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
HttpClientConfig clientConfig = HttpClientConfig.getDefault();
profile.setHttpClientConfig(clientConfig);
return new DefaultAcsClient(profile);
}
private static String kmsDecrypt(String cipherTextBlob) throws ClientException {
final DecryptRequest request = new DecryptRequest();
request.setSysProtocol(ProtocolType.HTTPS);
request.setAcceptFormat(FormatType.JSON);
request.setSysMethod(MethodType.POST);
request.setCiphertextBlob(cipherTextBlob);
DecryptResponse response = kmsClient.getAcsResponse(request);
return response.getPlaintext();
}
public static long getWaitTimeExponential(int retryCount) {
final long initialDelay = 200L;
long waitTime = ((long) Math.pow(2, retryCount) * initialDelay);
return waitTime;
}
public static void main(String[] args) {
String regionId = "xxxxx"; //"cn-shanghai"
String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
String cipherTextBlob = "xxxxxx";
int maxRetries = 5;
kmsClient = kmsClient(regionId, accessKeyId, accessKeySecret);
for (int i = 0; i < maxRetries; i++) {
try {
String plainText = kmsDecrypt(cipherTextBlob);
return;
} catch (ClientException e) {
if (e.getErrCode().contains("Rejected.Throttling")) {//need retry
try {
Thread.sleep(getWaitTimeExponential(i + 1));
} catch (InterruptedException ignore) {
}
}
}
}
}
}