[MDEV-16346] Implementing two phase commit across two maria databases Created: 2018-05-30  Updated: 2018-10-04  Resolved: 2018-10-04

Status: Closed
Project: MariaDB Server
Component/s: XA
Affects Version/s: None
Fix Version/s: N/A

Type: Bug Priority: Major
Reporter: Nicholas Denning Assignee: Unassigned
Resolution: Not a Bug Votes: 0
Labels: None


 Description   

I hope flagging this as a bug is appropriate.

We are having a problem trying to implement two phase commit across two mariadb databases. Ultimately we want to implement 2PC across an Active MQ queue and a mariadb database but chose this approach to text it working. We are doing this from TomEE to utilise the XA manager in that platform.

Connections are fine, updates are fine, but when we try to rollback the transaction the rollback seems not to work.

We have changed the database connections as follows:

As discussed, I have got some Java code which is using JTA transactions making updates to two JDBC data sources. These are configured as normal except that I have changed the type to “javax.sql.XADataSource”, which I believe is required for XA support.

<Resource name="jdbc/ipswichdb" auth="Container"
type="javax.sql.XADataSource"
factory="uk.co.diegesis.ipswich.utils.IPDataSourceFactory"
username="ipswichdba"
password="K4Zt5XhAjAz38MZnGgfsTg=="
alternateUsernameAllowed="true"
driverClassName="org.mariadb.jdbc.Driver"
url="jdbc:mariadb://vmipswichdbdpe.diegesis.local:3306/ipswichdb?useSSL=true&trustServerCertificate=true"
/>

<Resource name="jdbc/sessiondb" auth="Container"
type="javax.sql.XADataSource"
factory="uk.co.diegesis.ipswich.utils.IPDataSourceFactory"
username="user"
password="encryptedpassword"
alternateUsernameAllowed="true"
driverClassName="org.mariadb.jdbc.Driver"
url="jdbc:mariadb://vmipswichdbdpe.diegesis.local:3306/sessiondb?useSSL=true&trustServerCertificate=true"
/>

Note in the above we are overriding the data source factory so we can encrypt our passwords in the context file so we have no passwords in clear in files on the server.

I am calling the EJBContext method “setRollbackOnly” in one of the update methods to cause the transaction to be rolled back, yet the DB changes in both databases are not rolled back. Turning on TomEE logging shows this call being made, and that the JTA then attempts to roll back the transaction:

MyBean2.updatePerson; trans status: STATUS_ACTIVE Id: [Xid:globalId=344bffffff8dffffffb16310047544d4944000000000000000000000000000000000000000000000000000,length=64,branchId=0000000000000000000000000000000000000000000000000000000000000000,length=64]
30-May-2018 15:57:17.326 FINE [http-nio-8080-exec-4] org.apache.openejb.core.transaction.JtaTransactionPolicy.setRollbackOnly TX Required: setRollbackOnly() on transaction org.apache.geronimo.transaction.manager.TransactionImpl@2dde6b39
Trans status: STATUS_MARKED_ROLLBACK
30-May-2018 15:57:17.331 FINE [http-nio-8080-exec-4] org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke EjbObjectProxyHandler: finished invoking method updatePerson. Return value:void
30-May-2018 15:57:17.332 FINE [http-nio-8080-exec-4] org.apache.openejb.core.transaction.JtaTransactionPolicy.rollbackTransaction TX Required: Rolling back transaction org.apache.geronimo.transaction.manager.TransactionImpl@2dde6b39
30-May-2018 15:57:17.334 FINE [http-nio-8080-exec-4] org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke EjbObjectProxyHandler: finished invoking method updatePerson. Return value:void

Should this be supported in the DB? Is there a way to check whether the JTA command is reaching the DB, and if so what is going wrong?

we are using MariaDB Java Client v2.2.2.



 Comments   
Comment by Andrew Hutchings (Inactive) [ 2018-05-30 ]

Did you mean to file this against MariaDB ColumnStore? This appears to be a MariaDB Server / API issue. If this is misfiled let me know and I can move it for you.

Comment by Nicholas Denning [ 2018-05-30 ]

Andrew, you are right its a standard Mariadb server / API issue only. sorry if I did something wrong in the registration process. Nick

Comment by Andrew Hutchings (Inactive) [ 2018-05-30 ]

No problem, this has been moved to an MDEV ticket.

Comment by Elena Stepanova [ 2018-08-04 ]

nick.denning@diegesis.co.uk,
(without saying yet whether it's supposed to work or not),
sorry to start from an obvious question, but are you using a storage engine which supports XA?

Comment by Nicholas Denning [ 2018-08-09 ]

We have worked out how to do this.

We are using tomcat and that has the apache geronimo transaction manager included.

We needed to change our data source definitions in the context entries, or example

<Resource id="ipswichdb" type="javax.sql.DataSource">
DataSourceCreator = tomcat
JtaManaged = true
testOnBorrow = true
testOnReturn = false
testWhileIdle = false
validationQuery = SELECT 1

alternateUsernameAllowed = true
JdbcDriver = org.mariadb.jdbc.Driver
jdbcUrl = jdbc:mariadb://vmipswichdbdpe.diegesis.local:3306/ipswichdb?useSSL=true&trustServerCertificate=true
UserName = privileged_user
Password = privileged_password
</Resource>

Background references are:

XA transactions can be handled either by making explicit calls to JTA from the application Java code, or using J2EE container features to handle transactions implicitly. The latter approach seems preferable since J2EE provides those features, and this requires the minimum of application code.

A useful reference on JTA is:

https://www.progress.com/tutorials/jdbc/understanding-jta

and on Container Managed Transactions (CMT):

https://docs.oracle.com/javaee/6/tutorial/doc/bncij.html

https://examples.javacodegeeks.com/enterprise-java/ejb3/transactions/ejb-transaction-management-example/

Declaring Classes and Methods

The class containing the CMT code must be marked as an EJB class using the Stateless annotation:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class MyBean1

{ ... }
The Transaction Management Type is added here for clarity, but that is actually the default.

If this class is contained within another class such as a servlet, its inclusion there must be be annotated as an EJB to ensure that is is correctly initialised.

@EJB
private MyBean1 myBean;
Methods within the class may then be annotated to describe their transactional behaviour. If no annotations are used, the transaction setting is assumed to be REQUIRED, and a transaction is started by default when a method within this class is called, and terminated when that method returns. For example:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updatePerson(PrintWriter responseWriter) { ... }

REQUIRED is shown here for clarity, but is actually the default.

The data source to be used within the code is accessed using a Resource annotation, referencing the project name and the resource ID used in the resource declaration as shown above.

@Resource(name="IPXaTest/ipswichdb")
private DataSource dataSource;
The code within a method can then access the data source and perform SQL operations as follows:

Connection connection = dataSource.getConnection(USERNAME, PASSWORD);

Statement stmt = connection.createStatement();

stmt.executeUpdate("update PERSON_DETAILS set PERSON_CMM_LEVEL = " + level + " where PERSON_LOGIN_NAME = \'developer\'");

stmt.close();

connection.close();
By default if the method completes successfully then the transaction will be automatically committed when it returns.

If calls are made to other methods which have appropriate transaction attributes, e.g. REQUIRED or SUPPORTED, then they will be enrolled in the transaction, and all updates will either be committed or rolled back together when the top level method returns.

Transactions will be rolled back if a system exception is thrown, or if "setRollbackOnly" is called on the EJB context, allowing a rollback to be requested under application control.

InitialContext initialContext = new InitialContext();
EJBContext sessionContext = (SessionContext) initialContext.lookup("java:comp/EJBContext");
sessionContext.setRollbackOnly();
Methods involving JMS (ActiveMQ) queue objects may be included in a transaction in the same way, by writing methods within an EJB class and access the queue and connection factory as resources within the class.

@Resource(name = "IPXaTest/ipconnfactory")
private ConnectionFactory connFactory;

@Resource(name = "IPXaTest/ipqueue")
private Queue ipqueue;
Methods within that class can then be performed within a transaction in the same way described above.

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void sendMessage(String text) throws JMSException

{ ... }

Monitoring and Troubleshooting
The following code is useful to obtain the current transaction status, which can then be output for logging purposes to verify that this is being set as expected at any given point, i.e. whether a transaction is active or marked for rollback.

TransactionSynchronizationRegistry tsr = (TransactionSynchronizationRegistry)
new InitialContext().lookup("java:comp/TransactionSynchronizationRegistry");

int status = tsr.getTransactionStatus();

switch (status)

{ case 0: result = "STATUS_ACTIVE"; break; case 1: result = "STATUS_MARKED_ROLLBACK"; break; case 2: result = "STATUS_PREPARED"; break; case 3: result = "STATUS_COMMITTED"; break; case 4: result = "STATUS_ROLLEDBACK"; break; case 5: result = "STATUS_UNKNOWN"; break; case 6: result = "STATUS_NO_TRANSACTION"; break; case 7: result = "STATUS_PREPARING"; break; case 8: result = "STATUS_COMMITTING"; break; case 9: result = "STATUS_ROLLING_BACK"; break; default: result = "Unkown"; break; }

Useful logging information can be obtained if some transaction related logging levels are set in TomEE's logging.properties file, similar to the following:

OpenEJB.level = FINE
Transaction.level = FINE
org.apache.geronimo.level = FINE

OpenEJB.handlers = 5tomee.org.apache.juli.FileHandler, org.apache.tomee.jul.formatter.AsyncConsoleHandler
Transaction.handlers = 5tomee.org.apache.juli.FileHandler, org.apache.tomee.jul.formatter.AsyncConsoleHandler
org.apache.geronimo.handlers = 5tomee.org.apache.juli.FileHandler, org.apache.tomee.jul.formatter.AsyncConsoleHandler
With this logging configuration set, helpful information is output to the log showing when JTA transactions are started and committed:

19-Jun-2018 12:55:21.550 FINE [http-nio-8080-exec-2] org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke EjbObjectProxyHandler: invoking method updatePerson on MyBean1 with identity null

19-Jun-2018 12:55:21.597 FINE [http-nio-8080-exec-2] org.apache.openejb.core.transaction.JtaTransactionPolicy.beginTransaction TX Required: Started transaction org.apache.geronimo.transaction.manager.TransactionImpl@5a7e8d5b

19-Jun-2018 12:55:21.933 FINE [http-nio-8080-exec-2] org.apache.openejb.core.transaction.JtaTransactionPolicy.completeTransaction TX Required: Committing transaction org.apache.geronimo.transaction.manager.TransactionImpl@5a7e8d5b

19-Jun-2018 12:55:21.948 FINE [http-nio-8080-exec-2] org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke EjbObjectProxyHandler: finished invoking method updatePerson. Return value:void
or rolled back:

19-Jun-2018 12:51:39.584 FINE [http-nio-8080-exec-2] org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke EjbObjectProxyHandler: invoking method updatePerson on MyBean1 with identity null

19-Jun-2018 12:51:39.622 FINE [http-nio-8080-exec-2] org.apache.openejb.core.transaction.JtaTransactionPolicy.beginTransaction TX Required: Started transaction org.apache.geronimo.transaction.manager.TransactionImpl@36f371bf

19-Jun-2018 12:51:40.050 FINE [http-nio-8080-exec-2] org.apache.openejb.core.transaction.JtaTransactionPolicy.setRollbackOnly TX Required: setRollbackOnly() on transaction org.apache.geronimo.transaction.manager.TransactionImpl@36f371bf

19-Jun-2018 12:51:40.051 FINE [http-nio-8080-exec-2] org.apache.openejb.core.transaction.JtaTransactionPolicy.rollbackTransaction TX Required: Rolling back transaction org.apache.geronimo.transaction.manager.TransactionImpl@36f371bf

19-Jun-2018 12:51:40.061 FINE [http-nio-8080-exec-2] org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke EjbObjectProxyHandler: finished invoking method updatePerson. Return value:void

Hope this all helps.

Comment by Nicholas Denning [ 2018-08-09 ]

All the above investigated by my colleague Dave. Happy to close. Anyone who wants more details of an example eclipse project get in touch.

Comment by Nicholas Denning [ 2018-08-09 ]

We have tried this across ActiveMQ and Mariadb as well as just two Mariadb databases.

Comment by Nicholas Denning [ 2018-09-10 ]

all my previous comment was there to say was that we had fixed this and given example code of what was needed.

Nick

Comment by Elena Stepanova [ 2018-10-04 ]

Thanks for writing it up. Maybe it deserves an article.

Generated at Thu Feb 08 08:28:13 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.