12.6. Handling Errors

Documentation

VoltDB Home » Documentation » Guide to Performance and Customization

12.6. Handling Errors

Error handling in application programs is always important to avoid incorrect or incomplete data modifications from occurring. However, it is even more important for compound procedures for three reasons:

  • Unlike transactional stored procedures, which either succeed or fail as a whole, compound procedures are non-transactional. The individual procedures the compound procedure calls will succeed or fail individually. But if one procedure called by a compound procedure succeeds and a subsequent procedure fails, changes made by the first procedure are not rolled back. So an incomplete update can result. It is up to the compound procedure's program logic to handle this situation.

  • Even if the compound procedure does catch the error and report it (for example, through the abortProcedure method), if the procedure is invoked as part of an inbound topic, there is no calling application to receive and respond to the failure message.

  • Finally, it is necessary to take into account the fact that the procedure itself could be interrupted if the node it is running on crashes. In this situation, any transactional procedures it invoked will either succeed or rollback as part of the normal handling of transactions by VoltDB. But the execution of the compound procedure itself will simply stop. Meaning that any error handling defined in the procedure may not have an opportunity to complete.

One way to avoid irreconcilable changes is to avoid write operations in early stages of a compound procedure that are dependent on write operations that occur in later stages. However, even if two write operations are queued in the same stage there is no guarantee they will both succeed. Take, for example, the ProcessMessage compound procedure described earlier in this chapter. Although inserts into both the ExtendedMessage topic stream and the ACCOUNT_LOG table are queued in the same stage, there is no guarantee they will both succeed. So it is recommended to always check the response status of every call in the subsequent stage. In this case, the finish method might include the following code to handle potential issues:

    // stage 4 
    private void finish(ClientResponse[] resp) {
    
        // Check the response status
        String alert = "";    
        if (resp[0].getStatus() != ClientResponse.SUCCESS) {
            alert += resp[0].getStatusString() + "\n";
        }
        if (resp[1].getStatus() != ClientResponse.SUCCESS) {
            alert += resp[1].getStatusString() + "\n";
        }
        
        // Report any errors as part of the closure.
        if (alert.length() > 0) {
            abortProcedure(alert);
        } else {
            completeProcedure(1L);       
        }                                      
    }

This is a fairly simplistic example; it does not do anything to correct the failure or address the case where there is no calling application to respond to the abort message. In actual business applications, additional code and additional stages might be necessary to requeue a failed insert or manually roll back a successful insert if its partner fails. Similarly, it may be necessary to write failure messages to a log file or other notification system for those cases where the procedure is invoked by a topic.