惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
T
Threatpost
P
Palo Alto Networks Blog
NISL@THU
NISL@THU
O
OpenAI News
Project Zero
Project Zero
G
GRAHAM CLULEY
P
Privacy International News Feed
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
D
Docker
aimingoo的专栏
aimingoo的专栏
博客园 - 【当耐特】
N
Netflix TechBlog - Medium
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
W
WeLiveSecurity
P
Proofpoint News Feed
腾讯CDC
Cloudbric
Cloudbric
S
Secure Thoughts
C
Check Point Blog
博客园 - Franky
T
The Exploit Database - CXSecurity.com
T
Troy Hunt's Blog
GbyAI
GbyAI
Security Archives - TechRepublic
Security Archives - TechRepublic
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
F
Fortinet All Blogs
博客园 - 叶小钗
C
CXSECURITY Database RSS Feed - CXSecurity.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
C
Cisco Blogs
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
Apple Machine Learning Research
Apple Machine Learning Research

Yusuf Aytas

When Code Is Cheap, Does Quality Still Matter? Why Crouching Tiger, Hidden Dragon Is a Masterpiece Why We Ignore Advice The Mirror Is Part of the Machine When Too Many Maps Overlap on One Person The Work Runs on Different Maps Your Work Introduces You Trial By Fire The Dude Why Headcount Math Lies Capacity Is the Roadmap The Roadmap Is Not the System Torres del Paine W Trek Escaping Status Theater Incentives Drive Everything Scaling Culture Without Dilution What Good Looks Like Why Airport Security Feels Random Why Politics Appear How to Work with Me The Janus Protocol Multi-Horizon Delivery Framework What Good Execution Looks Like Managing Your Manager Why Kingdom of Heaven’s Director’s Cut Is Better AI Broke Interviews Most of What We Call Progress Managers Have Been Vibe Coding All Along Stop Wasting Brainpower Why Over-Engineering Happens Prisoner's Dilemma Climbing No More The Weekly Win Mevlana Candy Brewing Turkish Tea Onboarding Your Engineering Manager Technical Deep Dives Yapay Zekâ Çağında Bilgisayar Mühendisliği Building Remote Teams From Idea to Launch in 2 Weeks Reflecting on Software Engineering Handbook Representing the Business New Manager Survival Guide Take Self Reviews Seriously Chasing Real Respect The Invisible Difference Learning the Johari Window Management is a Lonely Place Simple Task Management AI Balance in Work PIP Manager Insights Engineering Manager Interview Preparation Work-Life Balance as a Manager Bridging the Management Disconnect Tech Hiring Bubble Bursts Traits for EMs Simple Acts of Recognition Matter The Question I Ask Every New Report The Reality of an Employer's Market Bridging Ideals and Reality Hiring Red Flags Why The Godfather Is So Damn Good Subteam Tenets No Fluff Please Losing a Top Performer Balancing Act of Reliability Building Trust in Engineering Teams Ideal Number of Direct Reports Overriding a People Leader’s Decision From Misperception to Promotion Perception vs Perspective Setting Goals From Engineer to Manager Getting Delegation Right Interviewing Your Future Boss Celebrating Our Book in Iceland Operational Skills Needed On Writing Software Engineering Handbook Charlie Munger Quotes Working with Dependencies From Las Vegas to Canyons Navigating Layoffs Handling Competitive Dynamics A Weekend Getaway to Malta Engineering Health Essentials Should Dev Managers Code? Confronting the Life on Pause Winning Eleven Kindness is A Choice Bireysel Katılımcılar ve Yöneticiler Leading from Where You Are The Subtle Art of Listening Coding in Leadership The Power of Consistency The Making of a Leader The Path to Leadership Embracing TikTok Talent Sourcing Journey Leading Self Managing Teams Cracking Coding Bottlenecks
XA Transactions: A Simple Guide
Yusuf Aytas · 2012-10-04 · via Yusuf Aytas

Published · 8 min read

In early days of computing, there was no need for distributed transactions, everything lived in one place. As number of applications increased, synchronization of the data become an important issue, and consistency stopped being “free.” Companies paid a lot to maintain synchronized systems in terms of data flow, because broken data flow is just silent data corruption. As a result, 2 phase commit protocol referred as XA(eXtended Architecture) arose, as a practical answer to “how do we commit across multiple systems?” This protocol provides ACID like properties for global transaction processing, but at a cost (latency, complexity, failure modes). Throughout this article, I will try to explain details of XA transactions and use of XA Transactions in Spring framework, from the protocol mechanics to real-world usage.

2 phase commit protocol is an atomic commitment protocol for distributed systems, meaning everyone either commits together or nobody commits at all. This protocol as its name implies consists of two phases, a handshake before the actual decision. The first one is commit-request phase in which transaction manager coordinates all of the transaction resources to commit or abort, basically asking “are you ready?”. In the commit-phase, transaction manager decides to finalize operation by committing or aborting according to the votes of the each transaction resource, and at that point the system must be consistent even if someone drops mid-flight. We will next move on to implementation details of 2PC protocol, because the real story is in the calls and states.

XA transactions need a global transaction id and local transaction id(xid) for each XA resource, because every participant must be uniquely tracked across the whole commit story. Each XA Resource is enlisted to XA Manager by start(xid) method, which is effectively the “you are now part of this” call. This method tells that XA Resource is being involved in the transaction(be ready for operations), so the resource can switch to transactional behavior and begin recording what matters. After that, first phase of the 2PC protocol is realized by calling prepare(xid) method, the point where guessing ends and voting begins. This method requests OK or ABORT vote from XA Resource, meaning “can you commit what you have so far, if I ask you to?”. After receiving vote from each of XA Resource, XA Manager decides to execute a commit(xid) operation if all XA Resources send OK or decides to execute a rollback(xid) if an XA Resource sends ABORT, because one “no” poisons the entire global transaction. Finally, end(xid) method is called for each of XA Resource telling that the transaction is completed, and the resource can release locks and clean its transactional context. Look at the figure to understand better, because the call order matters more than the names. As we build a background in XA transaction implementation, we will next go deeper and see types of failures and possible solutions, where XA stops looking clean and starts looking real.

Two-phase commit diagramTwo-phase commit diagram

Failures can occur at any time due to network loss, and distributed systems are built on the assumption that something will eventually fail. Network partitions, crashes of transaction manager or XA resources, timeouts or partial message delivery can happen in any phase of the protocol, often at the worst possible moment. Because of this, XA protocol defines strict recovery rules and heuristics, not to prevent failures, but to survive them. Transaction manager must be able to determine final outcome by consulting its logs and communicating with XA resources during recovery, essentially reconstructing the past. XA resources, on the other hand, must remember prepared transactions and respond correctly after restart, even if they would rather forget them. In some extreme cases, heuristic decisions might be taken, where a resource commits or rolls back on its own to unblock itself, which can lead to temporary inconsistency. These scenarios highlight that XA transactions are not just a programming concern, they are an operational and organizational commitment.

For recovery, transaction manager calls recover method of each XA resource, basically asking “what do you remember?”. XA Resources trace the logs and tries to rebuild its latest condition, replaying intent rather than guessing state. Transaction Manager calls necessary rollback operations and mission is accomplished, at least in the ideal case. This process can seem to be happy path but there are a lot of exceptional situations where logs are problematic like being corrupted, and recovery suddenly becomes archaeology. In these kinds of situations, transaction manager follows some heuristics to solve the problem, making a decision that is correct enough to move forward. Moreover, the recovery process depends on the write-ahead logs where you write operation logs before applying, because durability always comes before execution. For performance issues these logs are written in their own format(not using any serialization) and system should better batch them if possible, otherwise the transaction manager becomes the bottleneck. We next go to fun part which is XA transaction support by Spring framework, where theory finally meets code.

Spring framework supports XA transactions via JTA(Java Transaction API), but it does so in a very opinionated way. You usually don’t deal with XA APIs directly, because Spring intentionally hides them behind abstractions. Instead, you configure a JTA-compliant transaction manager and XA-aware resources, and let Spring orchestrate the lifecycle. Once configured, @Transactional annotation works almost the same as local transactions, which is both powerful and slightly dangerous. Under the hood, Spring delegates transaction boundaries to the JTA manager, and your code remains blissfully unaware of the distributed complexity. This abstraction lowers the entry barrier significantly, but it also makes it easy to misuse XA without realizing the cost. Understanding what happens beneath Spring’s convenience layer is crucial, otherwise you are debugging distributed systems with a local-transaction mindset.

Bitronix is easily configured while providing good support for transaction management. It is not commonly used in stand alone applications but I will try to give configuration for stand-alone application as follows.

// build.gradle (or pom.xml equivalent)
// implementation "org.springframework:spring-jdbc:<version>"
// implementation "org.springframework:spring-tx:<version>"
// implementation "org.springframework:spring-context:<version>"
// implementation "org.springframework:spring-jms:<version>"              // if using JMS
// implementation "org.codehaus.btm:btm:<version>"
// implementation "org.codehaus.btm:btm-spring:<version>"
// implementation "com.zaxxer:HikariCP:<version>"                        // optional
// runtimeOnly   "<your JDBC driver>"

import bitronix.tm.TransactionManagerServices;
import bitronix.tm.resource.jdbc.PoolingDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Configuration
@EnableTransactionManagement
public class BitronixXaConfig {

  /**
   * Bitronix will manage XA enlist/prepare/commit/rollback across all registered XA resources.
   * We expose it to Spring via JtaTransactionManager.
   */

  @Bean(destroyMethod = "close")
  public PoolingDataSource ordersXaDataSource() {
    PoolingDataSource ds = new PoolingDataSource();

    // Unique name is mandatory in Bitronix
    ds.setUniqueName("ordersDS");

    // XADataSource implementation class for your DB
    // Examples:
    // Postgres: org.postgresql.xa.PGXADataSource
    // MySQL:    com.mysql.cj.jdbc.MysqlXADataSource
    // Oracle:   oracle.jdbc.xa.client.OracleXADataSource
    ds.setClassName("org.postgresql.xa.PGXADataSource");

    // XA properties are passed as strings
    ds.getDriverProperties().put("serverName", "localhost");
    ds.getDriverProperties().put("portNumber", "5432");
    ds.getDriverProperties().put("databaseName", "orders");
    ds.getDriverProperties().put("user", "postgres");
    ds.getDriverProperties().put("password", "postgres");

    // Pool settings
    ds.setMinPoolSize(1);
    ds.setMaxPoolSize(10);
    ds.setAllowLocalTransactions(true); // handy for dev, but avoid mixing in prod

    return ds;
  }

  @Bean(destroyMethod = "close")
  public PoolingDataSource billingXaDataSource() {
    PoolingDataSource ds = new PoolingDataSource();
    ds.setUniqueName("billingDS");
    ds.setClassName("org.postgresql.xa.PGXADataSource");

    ds.getDriverProperties().put("serverName", "localhost");
    ds.getDriverProperties().put("portNumber", "5432");
    ds.getDriverProperties().put("databaseName", "billing");
    ds.getDriverProperties().put("user", "postgres");
    ds.getDriverProperties().put("password", "postgres");

    ds.setMinPoolSize(1);
    ds.setMaxPoolSize(10);
    ds.setAllowLocalTransactions(true);

    return ds;
  }

  @Bean
  public PlatformTransactionManager transactionManager() {
    TransactionManager tm = TransactionManagerServices.getTransactionManager();
    UserTransaction ut = TransactionManagerServices.getTransactionManager();
    return new JtaTransactionManager(ut, tm);
  }
}

And here how you use it.

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;

@Service
public class PaymentService {

  private final JdbcTemplate ordersJdbc;
  private final JdbcTemplate billingJdbc;

  public PaymentService(
      DataSource ordersXaDataSource,
      DataSource billingXaDataSource
  ) {
    this.ordersJdbc = new JdbcTemplate(ordersXaDataSource);
    this.billingJdbc = new JdbcTemplate(billingXaDataSource);
  }

  /**
   * Single @Transactional boundary, two different XA resources.
   * If either insert fails, Bitronix drives 2PC and rolls both back.
   */
  @Transactional
  public void createOrderAndInvoice(long orderId, long invoiceId, int amountCents) {
    ordersJdbc.update(
        "insert into orders(id, amount_cents) values (?, ?)",
        orderId, amountCents
    );

    billingJdbc.update(
        "insert into invoices(id, order_id, amount_cents) values (?, ?, ?)",
        invoiceId, orderId, amountCents
    );

    // throw new RuntimeException("boom"); // uncomment to see global rollback
  }
}

We can now have multiple data sources which can be configured as follows, each representing a separate transactional participant. Each data source should have a uniqueName property which is unique, because Bitronix uses this identifier to track and recover XA resources correctly. Below configuration is for PostgreSQL, used here purely as an example, other databases can have different XA configurations. For further details, you can check the Bitronix documentation, especially for database-specific XA properties and limitations.

To sum up, we have tried to explain what is XA Transactions, underlying protocols and Bitronix Transaction Management integration with Spring in a stand alone application, from theory to configuration to runtime behavior. To extend, XA Transactions provides modifying different data sources at the same time, under a single atomic boundary. Furthermore, XA Transactions are supported by web containers or hibernate like frameworks, typically via JTA abstractions that hide most of the plumbing. Nevertheless, we may need to integrate transaction management to a stand alone application in which we must configure transaction manager, and take responsibility for recovery, timeouts, and operational tuning. In consequence, XA transaction provides consistent operations on multiple data sources and companies make use of them, especially where strong consistency is non-negotiable and the complexity budget is justified.