aggregate
aggregate copied to clipboard
Possible replacement of TaskLocks with Transactional support
According to official documentation, we could leverage transactions on GAE: https://cloud.google.com/appengine/docs/standard/java/datastore/transactions
After studying the current TaskLock related code I think we could replace it with a servlet filter that would wrap all POST request inside a transaction.
We would need to implement a TransactionProvider for GAE and another for JDBC. The servlet filter would start a transaction before calling downstream on the chain and would catch certain exceptions to rollback the transaction or commit it if everything goes well.
The TransactionProvider would be injected by Spring and we would choose which one to inject on installation time by replacing its bean definition on the spring XML conf files.
Effort estimation
First, we'd have to focus on validating the hypothesis. I think I could hack a version that does everything for GAE and PostgresSQL and just for one servlet in one or two days. I would work on the servlet that receives submissions, which is very representative from a transactionality standpoint.
With those results, we would be able to estimate the cost of a full migration of all POST requests. On paper, it should be relatively easy to bring the transactional wrapper code to a servlet filter that would affect automatically to all POSTs
About current TaskLock exceptions management
It's reeeeeeeeeally difficult to know what's going on with exceptions. It's quite evident that most TaskLock exceptions are safely handled but there are certain cases that are hard to follow i.e. there is some TaskLock releasing code that happens on finally blocks that may produce new exceptions...
It feels like all the TaskLock code has been carefully designed to recover on failure but I can't be 100% sure that some of those exceptions won't raise and break normal flow.
It's very probable, though, that the duplicated row problems some users are experiencing is due to some problem in this locking code. The way I see it, I'd say that currently the code is non-transactional and transactionality is a requirement to solve this problem.
Extra work / risks I can foresee
- Some GET requests could need transactional support. We can migrate them to POST requests or add exceptions in the wrapper servlet filter
- While a transaction is running, any concurrent read will get the previously saved value. If transactions are too slow and this becomes a problem, we would need to change strategy and make transactions "thinner"
- On GAE, the DataStore might have some delay after the changes are committed and before they're applied. If this causes problems, the recommendation is to make idempotent transactions and we might need to redesign any request that needs this.
Transactions on GAE
Basic snippet from the docs:
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Transaction txn = datastore.beginTransaction();
try {
Key employeeKey = KeyFactory.createKey("Employee", "Joe");
Entity employee = datastore.get(employeeKey);
employee.setProperty("vacationDays", 10);
datastore.put(txn, employee);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
We would have to pass the txn
object downstream through the ServletContext
object.
Transactions on JDBC
Here we would leverage Spring's support for transactions. Here's an example: https://examples.javacodegeeks.com/enterprise-java/spring/jdbc/spring-transaction-management-example-with-jdbc-example/
Basically we'd proceed like this:
-
Declare a bean of type
DataSourceTransactionManager
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
-
Create a servlet filter that uses this transaction manager bean
-
The servlet filter would begin a transaction and run downstream chain inside a try-catch block
-
Our
DataStoreImpl
objects receive the transaction manager and can produce a JdbcTemplate injecting the data source from the transaction manager transparently:return new JdbcTemplate(transactionManager.getDataSource());
-
The rest of the code doesn't need to be changed