Abstract
This program demonstrates handling errors and retrying failed transactions using the NDB API.
The source code for this example can be found in
storage/ndb/ndbapi-examples/ndbapi_retries/ndbapi_retries.cpp
in the MySQL Cluster source tree.
There are many ways to program using the NDB API. In this example,
we perform two inserts in the same transaction using
NdbConnection::execute(NoCommit)
.
In NDB API applications, there are two types of failures to be taken into account:
Transaction failures: If nonpermanent, these can be handled by re-executing the transaction.
Application errors: These are
indicated by APIERROR
; they must be handled
by the application programmer.
/* * ndbapi_retries.cpp: Error handling and retrying transactions */ #include <mysql.h> #include <NdbApi.hpp> // Used for cout #include <iostream> // Used for sleep (use your own version of sleep) #include <unistd.h> #define TIME_TO_SLEEP_BETWEEN_TRANSACTION_RETRIES 1 #define PRINT_ERROR(code,msg) \ std::cout << "Error in " << __FILE__ << ", line: " << __LINE__ \ << ", code: " << code \ << ", msg: " << msg << "." << std::endl #define MYSQLERROR(mysql) { \ PRINT_ERROR(mysql_errno(&mysql),mysql_error(&mysql)); \ exit(-1); } // // APIERROR prints an NdbError object // #define APIERROR(error) \ { std::cout << "API ERROR: " << error.code << " " << error.message \ << std::endl \ << " " << "Status: " << error.status \ << ", Classification: " << error.classification << std::endl\ << " " << "File: " << __FILE__ \ << " (Line: " << __LINE__ << ")" << std::endl \ ; \ } // // TRANSERROR prints all error info regarding an NdbTransaction // #define TRANSERROR(ndbTransaction) \ { NdbError error = ndbTransaction->getNdbError(); \ std::cout << "TRANS ERROR: " << error.code << " " << error.message \ << std::endl \ << " " << "Status: " << error.status \ << ", Classification: " << error.classification << std::endl \ << " " << "File: " << __FILE__ \ << " (Line: " << __LINE__ << ")" << std::endl \ ; \ printTransactionError(ndbTransaction); \ } void printTransactionError(NdbTransaction *ndbTransaction) { const NdbOperation *ndbOp = NULL; int i=0; /**************************************************************** * Print NdbError object of every operations in the transaction * ****************************************************************/ while ((ndbOp = ndbTransaction->getNextCompletedOperation(ndbOp)) != NULL) { NdbError error = ndbOp->getNdbError(); std::cout << " OPERATION " << i+1 << ": " << error.code << " " << error.message << std::endl << " Status: " << error.status << ", Classification: " << error.classification << std::endl; i++; } } // // Example insert // @param myNdb Ndb object representing NDB Cluster // @param myTransaction NdbTransaction used for transaction // @param myTable Table to insert into // @param error NdbError object returned in case of errors // @return -1 in case of failures, 0 otherwise // int insert(int transactionId, NdbTransaction* myTransaction, const NdbDictionary::Table *myTable) { NdbOperation *myOperation; // For other operations myOperation = myTransaction->getNdbOperation(myTable); if (myOperation == NULL) return -1; if (myOperation->insertTuple() || myOperation->equal("ATTR1", transactionId) || myOperation->setValue("ATTR2", transactionId)) { APIERROR(myOperation->getNdbError()); exit(-1); } return myTransaction->execute(NdbTransaction::NoCommit); } // // Execute function which re-executes (tries 10 times) the transaction // if there are temporary errors (e.g. the NDB Cluster is overloaded). // @return -1 failure, 1 success // int executeInsertTransaction(int transactionId, Ndb* myNdb, const NdbDictionary::Table *myTable) { int result = 0; // No result yet int noOfRetriesLeft = 10; NdbTransaction *myTransaction; // For other transactions NdbError ndberror; while (noOfRetriesLeft > 0 && !result) { /********************************* * Start and execute transaction * *********************************/ myTransaction = myNdb->startTransaction(); if (myTransaction == NULL) { APIERROR(myNdb->getNdbError()); ndberror = myNdb->getNdbError(); result = -1; // Failure } else if (insert(transactionId, myTransaction, myTable) || insert(10000+transactionId, myTransaction, myTable) || myTransaction->execute(NdbTransaction::Commit)) { TRANSERROR(myTransaction); ndberror = myTransaction->getNdbError(); result = -1; // Failure } else { result = 1; // Success } /********************************** * If failure, then analyze error * **********************************/ if (result == -1) { switch (ndberror.status) { case NdbError::Success: break; case NdbError::TemporaryError: std::cout << "Retrying transaction..." << std::endl; sleep(TIME_TO_SLEEP_BETWEEN_TRANSACTION_RETRIES); --noOfRetriesLeft; result = 0; // No completed transaction yet break; case NdbError::UnknownResult: case NdbError::PermanentError: std::cout << "No retry of transaction..." << std::endl; result = -1; // Permanent failure break; } } /********************* * Close transaction * *********************/ if (myTransaction != NULL) { myNdb->closeTransaction(myTransaction); } } if (result != 1) exit(-1); return result; } /********************************************************* * Create a table named MYTABLENAME if it does not exist * *********************************************************/ static void create_table(MYSQL &mysql) { if (mysql_query(&mysql, "CREATE TABLE" " MYTABLENAME" " (ATTR1 INT UNSIGNED NOT NULL PRIMARY KEY," " ATTR2 INT UNSIGNED NOT NULL)" " ENGINE=NDB")) MYSQLERROR(mysql); } /*********************************** * Drop a table named MYTABLENAME ***********************************/ static void drop_table(MYSQL &mysql) { if (mysql_query(&mysql, "DROP TABLE" " MYTABLENAME")) MYSQLERROR(mysql); } int main(int argc, char** argv) { if (argc != 3) { std::cout << "Arguments are <socket mysqld> <connect_string cluster>.\n"; exit(-1); } char * mysqld_sock = argv[1]; const char *connectstring = argv[2]; ndb_init(); Ndb_cluster_connection *cluster_connection= new Ndb_cluster_connection(connectstring); // Object representing the cluster int r= cluster_connection->connect(5 /* retries */, 3 /* delay between retries */, 1 /* verbose */); if (r > 0) { std::cout << "Cluster connect failed, possibly resolved with more retries.\n"; exit(-1); } else if (r < 0) { std::cout << "Cluster connect failed.\n"; exit(-1); } if (cluster_connection->wait_until_ready(30,30)) { std::cout << "Cluster was not ready within 30 secs." << std::endl; exit(-1); } // connect to mysql server MYSQL mysql; if ( !mysql_init(&mysql) ) { std::cout << "mysql_init failed\n"; exit(-1); } if ( !mysql_real_connect(&mysql, "localhost", "root", "", "", 0, mysqld_sock, 0) ) MYSQLERROR(mysql); /******************************************** * Connect to database via mysql-c * ********************************************/ mysql_query(&mysql, "CREATE DATABASE TEST_DB_1"); if (mysql_query(&mysql, "USE TEST_DB_1") != 0) MYSQLERROR(mysql); create_table(mysql); Ndb* myNdb= new Ndb( cluster_connection, "TEST_DB_1" ); // Object representing the database if (myNdb->init() == -1) { APIERROR(myNdb->getNdbError()); exit(-1); } const NdbDictionary::Dictionary* myDict= myNdb->getDictionary(); const NdbDictionary::Table *myTable= myDict->getTable("MYTABLENAME"); if (myTable == NULL) { APIERROR(myDict->getNdbError()); return -1; } /************************************ * Execute some insert transactions * ************************************/ for (int i = 10000; i < 20000; i++) { executeInsertTransaction(i, myNdb, myTable); } delete myNdb; delete cluster_connection; drop_table(mysql); ndb_end(0); return 0; }