I am using FDB Java Binding to develop the Java application. The application invokes getRange() call and checks the FDBException raised. If the method call “isRetryable” from the raised FDBException returns true, the application will use the same FDBTransaction object to re-invoke getRange() call again. The application controls the number of the retries based on the given time budget.
I just found that my application encounters “Transaction Too Old” (that is,Transaction is too old to perform reads or be committed, with error code 1007), likely due to heavy workloads injected. And according to the call returns from fdb_c.cpp’s method call:
fdb_bool_t fdb_error_predicate( int predicate_test, fdb_error_t code ) {
…
return code == error_code_not_committed ||
code == error_code_transaction_too_old || …
}
The error of “transaction too old” is retry-able. With my application logic, it ends up that my code keeps invoking the getRange() call, and every time the same error “transaction too old” returns, until the code runs out of the retry time budget (say, 10 seconds). Clearly, my retry logic is incorrect to handle this particular error.
I checked the entire Java binding Package of 6.0.15 and 6.1.8, to try to find the right way to handle such “retry-able” errors. However, there are really no function calls used in the Java Binding that explicitly makes use of FDBException’s isRetryable method call.
What I found is the run() or runAsync() method defined in FDBDatabase.java. Whenever a FDBException is raised, the associated FDBTransaction object will invoke its onError() method to return a new Java FDBTransaction Object (that wraps around the native C’s transaction object’s pointer, via the “transfer” method). The call to the newly created Java Transaction object will continue, until no runtime exception raised. However, in both method implementations for run() and runAsync(), the “isRetryable” call is not invoked at all.
If I follow the run() or runAsync()'s retry logic + transfer the native C++ object pointer from the old transaction to the new transaction, to handle “transaction too old”, the retry logic would never succeed, because it will use the same underlying native C’s transaction object. Once “transaction too old” happens, over the same native transaction object, it will always return the same error code.
My questions are the following:
(1) In FDBDatabase’s run() or runAsync() method, when the error “transaction too old” occurs, what is the termination logic to terminate the method call, as the retry will keep return the same error and thus keep raises the same FDBException (subclass to RuntimeException)?
(2) What is the recommenced way to handle “retry-able” transaction, with some pseudo code to show the implementation logic?
(3) why not just create a brand-new transaction object, to handle for example, getRange() call, without transferring the native C pointer as onError(.) does. My view is that what really get saved is to avoid getReadVersion(.) , if we keep re-using the old C transaction object. But getReadVersion() is typically very fast, in the order of ~1 ms or less. For error handling (the rare situation), saving such a short-latency call is not worth.