-
Notifications
You must be signed in to change notification settings - Fork 8.9k
feature: integrate Sonata's global serializability support in xa mode #7754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2.x
Are you sure you want to change the base?
feature: integrate Sonata's global serializability support in xa mode #7754
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for Sonata's global serializability feature to Seata's XA transaction mode. The implementation introduces configuration for Sonata-specific behavior, including dummy table writes for MySQL (S2PL) and PostgreSQL (SSI) databases to enable strict serializable isolation.
Key changes:
- New configuration properties for Sonata features (enable flag, dummy table settings, retry thresholds, batch sizes)
- Database-specific shim logic in XA datasource and connection proxies to handle dummy writes and helper transactions
- Enhanced XA error handling for MySQL and PostgreSQL edge cases (deadlocks, non-existing branches, etc.)
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| StarterConstants.java | Added SONATA_PREFIX constant for Spring Boot autoconfiguration |
| SonataProperties.java | New configuration properties class for Sonata settings |
| ResourceManagerXA.java | Added XA_RETRY error code handling for retryable commit/rollback failures |
| DataSourceProxyXA.java | Added Sonata shim initialization, helper transaction management, and data structures for tracking dummy keys |
| ConnectionProxyXA.java | Implemented Sonata pre-prepare logic with dummy writes, helper transaction lifecycle, and enhanced error handling |
| pom.xml | Added PostgreSQL JDBC driver dependency |
| DefaultValues.java | Added default values for Sonata configuration properties |
| ConfigurationKeys.java | Added configuration keys for Sonata features |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| String SONATA_PREFIX = "sonata."; | ||
| String SONATA_ENABLE_GLOBAL_SERIALIZABILITY = SONATA_PREFIX + "enableGlobalSerializability"; | ||
| String SONATA_DUMMY_TABLE = SONATA_PREFIX + "dummyTable"; | ||
| String SONATA_DUMMY_TABLE_SIZE = SONATA_PREFIX + "dummyTableSize"; | ||
| String SONATA_S2PL_DUMMY_WRITE_RETRY_WARNING_THRESHOLD = SONATA_PREFIX + "s2plDummyWriteRetryWarningThreshold"; | ||
| String SONATA_SSI_HELPER_BATCH_SIZE = SONATA_PREFIX + "ssiHelperBatchSize"; |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SONATA_PREFIX uses a different pattern than other configuration prefixes in this file. Most other prefixes in ConfigurationKeys don't include a trailing dot and use hierarchical structure (e.g., SERVER_PREFIX, CLIENT_PREFIX). However, in StarterConstants.java line 46, SONATA_PREFIX is defined as SEATA_PREFIX + '.sonata', which would make it 'seata.sonata'. This inconsistency could lead to configuration key mismatches. Consider either removing the trailing dot here to match the pattern, or ensure the prefix properly aligns with the StarterConstants definition.
| String SONATA_PREFIX = "sonata."; | |
| String SONATA_ENABLE_GLOBAL_SERIALIZABILITY = SONATA_PREFIX + "enableGlobalSerializability"; | |
| String SONATA_DUMMY_TABLE = SONATA_PREFIX + "dummyTable"; | |
| String SONATA_DUMMY_TABLE_SIZE = SONATA_PREFIX + "dummyTableSize"; | |
| String SONATA_S2PL_DUMMY_WRITE_RETRY_WARNING_THRESHOLD = SONATA_PREFIX + "s2plDummyWriteRetryWarningThreshold"; | |
| String SONATA_SSI_HELPER_BATCH_SIZE = SONATA_PREFIX + "ssiHelperBatchSize"; | |
| String SONATA_PREFIX = "sonata"; | |
| String SONATA_ENABLE_GLOBAL_SERIALIZABILITY = SONATA_PREFIX + ".enableGlobalSerializability"; | |
| String SONATA_DUMMY_TABLE = SONATA_PREFIX + ".dummyTable"; | |
| String SONATA_DUMMY_TABLE_SIZE = SONATA_PREFIX + ".dummyTableSize"; | |
| String SONATA_S2PL_DUMMY_WRITE_RETRY_WARNING_THRESHOLD = SONATA_PREFIX + ".s2plDummyWriteRetryWarningThreshold"; | |
| String SONATA_SSI_HELPER_BATCH_SIZE = SONATA_PREFIX + ".ssiHelperBatchSize"; |
| public class SonataProperties { | ||
| private boolean enableGlobalSerializability; | ||
| private String dummyTable; | ||
| private int dummyTableSize; | ||
| private int s2plDummyWriteRetryWarningThreshold; |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SonataProperties class lacks documentation explaining the purpose of each configuration property. Consider adding JavaDoc comments to describe what 'enableGlobalSerializability', 'dummyTable', 'dummyTableSize', 's2plDummyWriteRetryWarningThreshold', and 'ssiHelperBatchSize' represent and how they affect Sonata's behavior.
| public class SonataProperties { | |
| private boolean enableGlobalSerializability; | |
| private String dummyTable; | |
| private int dummyTableSize; | |
| private int s2plDummyWriteRetryWarningThreshold; | |
| public class SonataProperties { | |
| /** | |
| * Whether to enable global serializability for transactions. | |
| * If true, Sonata will enforce serializability across all transactions, which may impact performance. | |
| */ | |
| private boolean enableGlobalSerializability; | |
| /** | |
| * The name of the dummy table used for certain concurrency control mechanisms. | |
| * This table is used internally by Sonata for lock management or dummy writes. | |
| */ | |
| private String dummyTable; | |
| /** | |
| * The number of rows in the dummy table. | |
| * This determines the size of the dummy table used for lock striping or contention reduction. | |
| */ | |
| private int dummyTableSize; | |
| /** | |
| * The threshold for logging a warning when dummy write retries exceed this value in S2PL (Strict Two-Phase Locking) mode. | |
| * Helps to detect and alert on excessive contention or retry attempts. | |
| */ | |
| private int s2plDummyWriteRetryWarningThreshold; | |
| /** | |
| * The batch size used by the SSI (Serializable Snapshot Isolation) helper. | |
| * Controls how many records are processed in a single batch for SSI-related operations. | |
| */ |
| throw new RuntimeException( | ||
| String.format("Global txn branch (%s) already associated with dummy key (%d)", xid, prev)); |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message in getS2plDummyKey includes a comment (line 577) stating 'We don't know why this would happen (though programmatically possible) so we do not handle it.' This suggests the error is unexpected. Consider using a more specific exception type (e.g., IllegalStateException) and providing more actionable guidance in the error message for debugging purposes.
| throw new RuntimeException( | |
| String.format("Global txn branch (%s) already associated with dummy key (%d)", xid, prev)); | |
| throw new IllegalStateException( | |
| String.format("Invariant violation: Global txn branch (%s) is already associated with dummy key (%d) in XID_TO_DUMMY_KEY. This indicates a possible logic error or concurrency issue. Please check the transaction lifecycle and report this stack trace to the Seata maintainers. [ACTIVE_DUMMY_KEYS=%s, XID_TO_DUMMY_KEY.size=%d]", | |
| xid, prev, ((DataSourceProxyXA) resource).ACTIVE_DUMMY_KEYS, ((DataSourceProxyXA) resource).XID_TO_DUMMY_KEY.size())); |
| throw new RuntimeException( | ||
| String.format("Global txn branch (%s) already associated with dummy key (%d)", xid, prevDummy)); |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to getS2plDummyKey, this RuntimeException in getSsiDummyKey should use a more specific exception type like IllegalStateException to better convey that this represents an unexpected internal state violation.
| Integer prevHelper = | ||
| ((DataSourceProxyXA) resource).XID_TO_HELPER_ID.putIfAbsent(xid, keyAndHelperId.getValue()); | ||
| if (prevHelper != null) { | ||
| throw new RuntimeException(String.format( |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This RuntimeException should also use a more specific exception type like IllegalStateException for consistency with similar state violation errors.
| throw new RuntimeException(String.format( | |
| throw new IllegalStateException(String.format( |
| // release the pending helpers. | ||
| public void releaseAllHelpers() throws SQLException { | ||
| if (!sonataSsiShimEnabled) { | ||
| throw new RuntimeException("Sonata SSI helper transactions are not allowed for the datasource"); |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message in releaseAllHelpers should be more specific about why helper transactions are not allowed. Consider including information about the database type requirement (PostgreSQL) and the configuration state (sonataSsiShimEnabled).
| throw new RuntimeException("Sonata SSI helper transactions are not allowed for the datasource"); | |
| throw new RuntimeException("Sonata SSI helper transactions are only allowed when using a PostgreSQL database and when sonataSsiShimEnabled is true."); |
|
|
||
| protected Connection getSsiHelperConnection() throws SQLException { | ||
| if (!sonataSsiShimEnabled) { | ||
| throw new RuntimeException("Sonata SSI helper transactions are not allowed for the datasource"); |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message in getSsiHelperConnection should be more specific about the requirements, similar to the issue in releaseAllHelpers. Additionally, both methods throw the same generic error message, which could be extracted as a constant for consistency.
| while (affected == 0) { | ||
| affected = | ||
| stmt.executeUpdate("insert into `" + DUMMY_TABLE + "` (`key`, value) values (" + key + "," | ||
| + value + ") on duplicate key update value=" | ||
| + value); | ||
| retry++; | ||
| if (retry > S2PL_UPDATE_RETRY_WARNING_THRESHOLD) { | ||
| LOGGER.warn( | ||
| "MySQL random dummy writes generated conflict with existing rows in {} consecutive attempts! This is super unlikely to occur, please investigate.", | ||
| retry); | ||
| } | ||
| } |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SQL query construction in sonataPrePrepare uses string concatenation with user-controlled values ('key' and 'value'), which could potentially lead to SQL injection if DUMMY_TABLE name is user-configurable. Consider using PreparedStatement instead of Statement with string concatenation for better security and performance.
| try (Statement stmt = createStatement()) { | ||
| // Unlike MySQL, PostgreSQL does not distinguish matched but value-unchanged rows. | ||
| int affected; | ||
| try { | ||
| affected = stmt.executeUpdate( | ||
| "insert into \"" + DUMMY_TABLE + "\" (key, value) values (" + key + "," + value | ||
| + ") on conflict (key) do update set value=" | ||
| + value); |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the MySQL case, this PostgreSQL SQL query uses string concatenation which poses a SQL injection risk. Use PreparedStatement instead for safer and more efficient query execution.
| try (Statement stmt = createStatement()) { | |
| // Unlike MySQL, PostgreSQL does not distinguish matched but value-unchanged rows. | |
| int affected; | |
| try { | |
| affected = stmt.executeUpdate( | |
| "insert into \"" + DUMMY_TABLE + "\" (key, value) values (" + key + "," + value | |
| + ") on conflict (key) do update set value=" | |
| + value); | |
| String sql = "insert into \"" + DUMMY_TABLE + "\" (key, value) values (?, ?) " | |
| + "on conflict (key) do update set value = ?"; | |
| try (java.sql.PreparedStatement pstmt = getTargetConnection().prepareStatement(sql)) { | |
| // Unlike MySQL, PostgreSQL does not distinguish matched but value-unchanged rows. | |
| int affected; | |
| try { | |
| pstmt.setInt(1, key); | |
| pstmt.setInt(2, value); | |
| pstmt.setInt(3, value); | |
| affected = pstmt.executeUpdate(); |
| try (Statement helperStmt = helperConn.createStatement()) { | ||
| for (Integer dummyKey : newBatch) { | ||
| try (ResultSet rs = helperStmt.executeQuery( | ||
| "select count(*) from \"" + DUMMY_TABLE + "\" where key=" + dummyKey)) { |
Copilot
AI
Nov 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The query construction in getSsiDummyKey uses string concatenation for the dummy key value. While 'dummyKey' is an integer (less risky), using PreparedStatement would be more consistent with SQL best practices and provide better protection against potential issues.
Ⅰ. Describe what this PR did
This PR integrates the global serializability protocols from the Soanta paper to offer better isolation/consistency guarantees in XA mode. This discussion gives more details.
Ⅱ. Does this pull request fix one issue?
No.
Ⅲ. Why don't you add test cases (unit test/integration test)?
Little familiarity with the test structure and limited time budget.
Ⅳ. Describe how to verify it
The correctness of the protocols is proven in the research paper and the implementation is mainly a rework of the research prototype, which has been extensively evaluated under various complex workloads, including TPC-C.
Ⅴ. Special notes for reviews