Short Summary
Cloud SQL exports can sometimes fail.
Can I make the export requests behave synchronously so the failures are easy to retry?
Or is there a good way to retry exports in an asynchronous approach?
Full Description
I'm migrating code from App Script into Node.js and I've encountered an issue. The code exports the results of a Cloud SQL query into a CSV file. Cloud SQL can't do parallel exports so this sometimes results in the following error being thrown:
Error: Operation failed because another operation was already in progress.
In App Script the approach was to wait for 6 seconds then try again, with a limit of 10 retries.
Doing this was simple because the code behaved synchronously:
for(exportAttempt=1; exportAttempt<=10; exportAttempt++) {
Utilities.sleep(6000);
// Use the url fetch service to issue the https request and capture the response
var response = UrlFetchApp.fetch(api, parameters);
response = JSON.parse(response);
if(exportAttempt == 10) {
throw('Exceeded the limit of 10 failed export requests to REST API.');
}
if(response.status != undefined) {
_log_('DEBUG', 'Export attempt ' + exportAttempt + ' successful.');
exportAttempt=10;
}
if(response.error != undefined) {
_log_('DEBUG', 'Attempt number ' + exportAttempt + ' errored. ' + JSON.stringify(response));
}
}
Replicating the export functionality in Node.js was possible with the following code but it behaves asynchronously:
var {google} = require('googleapis');
var sqladmin = google.sqladmin('v1beta4');
var uri = 'gs://' + csBucket + '/' + csFileName;
google.auth.getApplicationDefault(function(err, authClient) {
if (err) {
_log_('ERROR', 'Authentication failed because of ' + err);
return false;
}
if (authClient.createScopedRequired && authClient.createScopedRequired()) {
var scopes = [
'https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/sqlservice.admin'
];
authClient = authClient.createScoped(scopes);
}
var request = {
project: projectId,
instance: sqlInstance,
resource: {
exportContext: {
kind: "sql#exportContext",
fileType: fileType,
uri: uri,
databases: [sqlSchema],
csvExportOptions: {
selectQuery: exportSQL
}
}
},
auth: authClient
};
sqladmin.instances.export(request, function(err, result) {
if (err) {
//The problem with the exception is that it stops the Cloud Function.
//It isn't thrown up to the parent/calling function to be used for a retry.
_log_('ERROR', 'Export failed because of ' + err);
throw(err)
} else {
_log_('DEBUG', 'result');
_log_('DEBUG', result);
}
});
});
This means the error causes an immediate failure. I can't find a way to throw the error up to the parent/calling function to manage with a retry.