ScalarDB 3.20 Release Notes
This page includes a list of release notes for ScalarDB 3.20.
v3.20.0​
Release date: October 30, 2026
Summary​
This release introduces global transaction support for ScalarDB SQL on ScalarDB Cluster, enabling transactions that span multiple processes and microservices. It also adds PostgreSQL wire protocol support, allowing applications and tools that use PostgreSQL drivers to connect to ScalarDB Cluster. Additionally, this release improves transaction performance and reliability in Consensus Commit, simplifies transaction lifecycle management, removes legacy transaction APIs, and enhances the correctness and reliability of storage adapter operations. It also adds support for SQL Server 2025, drops support for SQL Server 2017, and strengthens security through authentication and authorization fixes and vulnerability mitigations.
Community edition​
Enhancements​
- Consensus Commit now runs consecutive Get operations in a batch in parallel. You can configure this with
scalar.db.consensus_commit.parallel_batch_get.enabledandscalar.db.consensus_commit.parallel_batch_get.max_concurrency_per_batch. (#4053)
Improvements​
- Replaced
BranchTransaction.end()withBranchTransaction.end(BranchTransaction.Status), which requires a branch of a global transaction to declare the outcome of its work. Replace existingend()calls withend(BranchTransaction.Status.SUCCESS), and callend(BranchTransaction.Status.FAILURE)on failure paths. (#3806) - Added support for the AWS Advanced JDBC Wrapper, which enables fast Aurora failover for Aurora PostgreSQL and Aurora MySQL. Configuring HikariCP with an exception override is now rejected at startup, since it would cause a transaction with an unknown outcome to be reported as a definite failure. (#3805)
- Fixed an issue where the error message for a Cosmos DB error was excessively large because it included the Cosmos DB diagnostics, which could cause the error information to be lost. (#3816)
- Deprecated
DistributedTransactionManager.join()andDistributedTransactionManager.resume(), along with their implementations, for removal in 3.20.0. (#3817) - Allowed updating and deleting records that a scanner has already returned, so applications can mutate rows while iterating a scanner instead of buffering the whole result with
scan(). Inserting into the scanned range remains disallowed. (#3850) - Fixed an issue where a conflict error, such as a lock wait timeout or a serialization failure, in a single put or delete operation on JDBC databases was not classified as retriable. As a result, Consensus Commit can now report such a conflict during commit as
CommitConflictExceptioninstead ofCommitExceptionorUnknownTransactionStatusException. (#3852) - Fixed the Consensus Commit record recovery to retry conflict errors reported by the underlying storage, and to report a conflict that persists as a conflict so that the transaction can be retried. (#3853)
- Fixed an issue where a write operation that creates a record kept failing for a key that holds a record of a transaction that did not finish. Such a record is now recovered when the write fails, so that a retry succeeds. (#3868)
- Improved the performance of committing and rolling back a transaction. The records to roll back are now read in parallel, and, when asynchronous commit or rollback is enabled, committing and rolling back the records no longer run on the thread of the transaction. (#3888)
- Removed
DistributedTransactionManager.join()andDistributedTransactionManager.resume(), which were deprecated in 3.19.0. Renamedscalar.db.active_transaction_management.enabledtoscalar.db.two_phase_commit.active_transaction_management.enabled, which now applies only to the two-phase commit coordinator and participant. Changed the default ofscalar.db.active_transaction_management.expiration_time_millisfrom-1to60000. (#3889) - New Cosmos DB containers created by ScalarDB use partition key definition V2, including the metadata and namespaces containers. Existing containers keep their current partition key definition, even when their tables are repaired. To move an existing container to V2, copy its data to a new container. (#3870)
- Improved the performance of checking whether a table exists.
tableExists()now reads only the metadata of the specified table instead of listing the tables in the namespace, so its cost no longer grows with the number of tables. As a result, on DynamoDB, a table that has no ScalarDB metadata is no longer reported as existing. Also fixed an issue in whichgetTableMetadata()for DynamoDB and Cosmos DB threw an exception instead of returningnullwhen the ScalarDB metadata table or container did not exist. On these storages, a "table not found" error can therefore now also mean that the metadata table or container itself does not exist. If such a table is visible in the AWS console or the Azure portal, verify thatscalar.db.system_namespace_name(and, on DynamoDB, the namespace prefix) match across all configurations before runningrepairTable(). (#3947) - Changed a commit to end the transaction whatever its outcome, so you no longer need to roll back a transaction after a failed commit. A rollback or an abort after a commit, whether the commit succeeded or failed, now does nothing instead of rolling back again or throwing
IllegalStateException. (#3966) - Upgraded the SQL Server JDBC driver to 13.6.0 and fixed TIMESTAMPTZ values dated before October 15, 1582, being stored up to 10 days off in SQL Server. TIMESTAMPTZ values dated before October 15, 1582, that were stored in SQL Server by a previous ScalarDB version are read back up to 10 days earlier than the value that was written. If you have such data, consider migrating it to remove the offset. (#3971)
- Added support for SQL Server 2025 and dropped support for SQL Server 2017. (#3998)
- Changed upserts in the JDBC adapter to send each column value once, which allows larger BLOB values on MySQL and MariaDB. (#4040)
- Changed table import to reject column types that can't follow the TEXT collation order: SQL Server
nchar,nvarchar,text, andntext; OracleNCHARandNVARCHAR2; and Db2GRAPHIC,VARGRAPHIC, andDBCLOB. (#4051) - Added
BranchTransaction.getGlobalTransactionId(), which returns the ID of the global transaction that a branch belongs to.BranchTransaction.getId()now represents the ID of the branch itself, so usegetGlobalTransactionId()when you need the ID of the global transaction.getId()may return the same ID asgetGlobalTransactionId(), so do not use it to tell the branches of a global transaction apart. (#4059) - Fixed an issue where a mutation condition using
LIKEorNOT_LIKEpassed operation validation and then failed in the storage with anAssertionError. Such a condition is now rejected with anIllegalArgumentException. (#4072) - Changed the order of TEXT values that ScalarDB compares itself to Unicode code point order, which matches binary collations on UTF-8 data. Previously, supplementary characters (U+10000 and above) were ordered before characters in U+E000 to U+FFFF. For SQL Server, string parameters are now sent as
VARCHARso that range scans on TEXT columns follow the column's collation. (#4075) - Improved the S3 and Cloud Storage adapters to retry deleting objects that failed on transient errors when truncating or dropping tables. (#4077)
- Mitigate CVE-2026-91403 and CVE-2026-114820 (eMudhra CA compromise) by removing the affected emSign root CAs from the cacerts of all container images, and update the base image of the ScalarDB Schema Loader and ScalarDB Data Loader CLI container images from Java 8 (Eclipse Temurin 8) to Java 21 (Eclipse Temurin 21). The images set no JVM options by default, so this affects only users who pass Java 8-specific JVM options (for example,
-XX:+PrintGCDateStampsthroughJAVA_TOOL_OPTIONS), which now make the JVM fail to start. (#4112)
Bug fixes​
- Fixed an issue where schema operations could fail with a conflict error, such as
ORA-08177on Oracle when using the SERIALIZABLE isolation level. Conflict errors are now retried. (#3820) - Fixed the get operation in the Cloud Storage adapter to handle concurrent writes. (#3851)
- Fixed an issue where rolling back a Consensus Commit transaction with an open scanner, including the automatic rollback of an expired or evicted transaction, could fail with an IllegalArgumentException and leave the remaining scanners open and the group commit slot unreleased. (#3867)
- Fixed the Cassandra adapter so that an update applied by a conditional write is no longer silently discarded when it runs in the same millisecond as a preceding non-conditional write to the same record. The adapter now takes write timestamps from the server and sends every request for a partition to the same replica. (#3890)
- Fixed an issue where Consensus Commit accepted invalid conditions on conditional mutations instead of rejecting them. (#3871)
- Fixed an issue in DynamoDB where a single conditional write that the AWS SDK retried could be reported as not applied, even though the write had been applied. Such a write is now reported as having an unknown outcome. Also fixed an issue where some failures in DynamoDB operations were thrown as unchecked AWS SDK exceptions instead of
ExecutionException. (#3922) - Fixed an issue where committing a transaction could report a conflict and roll back its already-committed records when its Coordinator state write was retried and
finishTransactionwas called for the transaction concurrently. The commit now reports an unknown transaction status instead. (#3923) - Fixed an issue where a scan condition with the
!=,<, or<=operator was satisfied by a NULL column value in Cassandra, Cosmos DB, DynamoDB, and Object Storage. Also fixed an issue where a!=condition was satisfied by a NULL column value inPutIforDeleteIfin Cassandra, DynamoDB, and Consensus Commit, and inUpdateIfin Consensus Commit. (#3930) - Fixed an issue where
repairTable()on DynamoDB did not enable auto-scaling for existing global secondary indexes, for example aftercreateTable()orcreateIndex()failed before enabling auto-scaling (such as when Application Auto Scaling permissions were missing).repairTable()now also keeps the existing auto-scaling range of a table instead of resetting it to the given or default request unit, so running repair without--runo longer lowers the capacity of existing tables, including the Coordinator table. To change the auto-scaling range of an existing table, use the AWS console or CLI. IfcreateTable()orcreateIndex()with auto-scaling enabled failed before, runrepairTable()again after upgrading, with the schema that includes the index and the same request unit as at creation. IfrepairTable()(or Schema Loader with--repair-all) was run before without--ruor with a smaller request unit, check the auto-scaling range of existing tables, including the Coordinator table, and restore it with the AWS console or CLI, becauserepairTable()no longer changes the range of existing tables. (#3949) - Fixed an issue in the Object Storage adapter with Amazon S3 where a conditional write that the AWS SDK retried could be reported as a conflict, even though the write had been applied. Such a write is now reported as having an unknown outcome. (#3958)
- Fixed an issue in the Object Storage adapter with Azure Blob Storage where a conditional write that the Azure SDK retried could be reported as a conflict, even though the write had been applied. Such a write is now reported as having an unknown outcome. (#3964)
- Fixed an issue in the Object Storage adapter with Google Cloud Storage where a deletion that the Cloud Storage client retried could be reported as a conflict, even though the deletion had been applied. Such a deletion is now sent only once, and a failure of it other than an unsatisfied condition is reported as having an unknown outcome. (#3965)
- Fixed an issue where a scanner opened outside a transaction was never closed if a client abandoned it while active transaction management kept it across requests. Such a scanner kept its transaction and storage resources, for example, a database connection and an open database transaction. Now, such a scanner is closed when it has not been used for the active transaction expiration time, or when more such scanners than the maximum number of active transactions are open. (#3992)
- Fixed an issue in the Object Storage adapter with Amazon S3 and Google Cloud Storage where truncating or dropping a table could succeed while leaving some of the table's objects undeleted. Such a failure is now reported as an error. (#4043)
- Fixed an issue where the
scalar.db.s3.multipart_upload_max_concurrencyoption limited the number of concurrent requests of all S3 operations, not only multipart uploads, in the S3 adapter. (#4061)
Enterprise edition​
Enhancements​
ScalarDB Cluster​
- Added support for global transactions to ScalarDB SQL on ScalarDB Cluster. With the new
GLOBAL_TRANSACTIONmode ofSqlSessionand the JDBC driver, applications can run a single transaction that spans multiple processes, such as microservice transactions: one process begins a global transaction, and each process runs a branch of it withBEGIN BRANCHandEND BRANCH. The Transaction Coordinator drives two-phase commit across ScalarDB Clusters on their behalf whenscalar.db.sql.enabledis set totrueon it. The same application code also works for transactions within a single ScalarDB Cluster, with only configuration selecting which. - Added support for the PostgreSQL wire protocol to ScalarDB Cluster. When
scalar.db.postgres.enabledis set totrue, each node accepts PostgreSQL connections on the port specified byscalar.db.postgres.port(5432by default), so psql, PostgreSQL drivers such as the PostgreSQL JDBC driver, and other PostgreSQL tools can run SQL on ScalarDB Cluster. When authentication is enabled, clients log in as ScalarDB Cluster users with their passwords or with OIDC access tokens sent as passwords, and ScalarDB Cluster enforces those users' privileges as it does for other clients. By usingscalar.db.postgres.auth, you can allow only passwords (userpass) or only OIDC access tokens (oidc_jwt). With authentication enabled, connections must use TLS by default, and the node's TLS certificate and private key are used unless others are specified.
ScalarDB SQL​
- Added support for referencing a joined table's column in the WHERE clause of a SELECT statement with a JOIN.
- Fixed
Connection.rollback()in the JDBC driver to throwSQLExceptionwhen the rollback fails. Previously, it threw an unchecked ScalarDB SQL exception as is.Connection.commit()now throwsSQLNonTransientExceptionwith the SQLStateSDB01, instead ofSQLExceptionwithSDB99, when no transaction is in progress, for example in auto-commit mode. Also fixed the JDBC driver so that a failure to create a connection, for example because the default namespace does not exist, no longer prevents the cached session factory from expiring.
Improvements​
ScalarDB Cluster​
- Reduced the number of queries issued when the attribute-based access control metadata caches are refreshed, which removes a latency spike on the first request after each refresh.
- Adopted the ScalarDB Core change that replaced
BranchTransaction.end()withBranchTransaction.end(BranchTransaction.Status), which requires a branch of a global transaction to declare the outcome of its work. Replace existingend()calls withend(BranchTransaction.Status.SUCCESS), and callend(BranchTransaction.Status.FAILURE)on failure paths. - Fixed OIDC JWT validation so that an exhausted JWKS retrieval rate limit is reported as an authentication failure instead of an internal error.
- Removed
ClusterClientTransactionManager.join(),ClusterClientTransactionManager.resume(), andClusterClientSqlTransactionManager.resume(), which were deprecated in 3.19.0. Removed theSINGLETONcluster client mode (singleton:contact points), which was for internal use. - Fixed the Global Transaction API so that the auth attributes passed to
GlobalTransactionManager.beginchoose the credential for the begin and the initiator's commit and rollback, and are no longer forwarded to the cluster as transaction attributes. - Fixed an issue where a transaction or a transaction participant's context could remain in a cluster node's memory indefinitely when its rollback or another terminal operation was rejected because the authentication token had expired. Also, a cluster pause no longer waits for the transaction expiration when an in-flight transaction's commit fails.
- Changed the Rollback RPC so that rolling back a transaction that the cluster node does not know, such as one that has already ended, succeeds instead of returning a
NOT_FOUNDerror. - Simplified how the associated data of encrypted columns is generated. The generated values are unchanged.
- Changed a commit to end the transaction whatever its outcome, so you no longer need to roll back a transaction after a failed commit. A rollback or an abort after a commit, whether the commit succeeded or failed, now does nothing and sends no request to the cluster.
- Fixed the Transaction Coordinator backing of the Global Transaction API so that branches of the same transaction that are open at the same time in one
GlobalTransactionManagereach run as their own credential. Previously, such a branch could run as another branch's credential or as the default credential.
ScalarDB GraphQL​
- Removed two-phase commit support: the
@twoPhaseCommitdirective and theprepare,validateandcommitmutation fields are gone, along with theGraphQlFactoryconstructor parameter for the two-phase commit transaction manager. This surface was not deprecated in an earlier release. The@transactiondirective and theabortandrollbackmutation fields are unaffected. - Added a
GraphQlServerconstructor that takes aResumableDistributedTransactionManagerand aDistributedTransactionAdminfor embedding. TheGraphQlFactoryandTransactionInstrumentationconstructors now take aResumableDistributedTransactionManagerinstead of aDistributedTransactionManager. The behavior of the@transactiondirective does not change. - Changed a commit to end the transaction whatever its outcome, so you no longer need to abort a transaction after a failed commit. An
abortor arollbackof a transaction that has already ended, for example by a commit, whether the commit succeeded or failed, now returnstrueinstead of an error.
ScalarDB SQL​
- Removed
SqlTransactionManager.join(),SqlTransactionManager.resume(),SqlSession.join(),SqlSession.suspend(),SqlSession.resume(),StatementBuilder.join(),StatementBuilder.suspend(),StatementBuilder.resume(), and theJOIN,SUSPEND, andRESUMEstatements, which were deprecated in 3.19.0. Removedbegin(),commit(),rollback(),join(),resume(), andsuspend()fromScalarDbRepositoryImplin the Spring Data integration. Also removed the same methods fromScalarDbJdbcAggregateTemplate, whose public constructors no longer take aNamedParameterJdbcOperations. - Changed a commit to end the transaction whatever its outcome, so you no longer need to roll back a transaction after a failed commit. A rollback or an abort after a commit, whether the commit succeeded or failed, now does nothing. A statement or a commit on a transaction that has already ended now fails with
IllegalStateExceptionin every connection mode. - Changed DDL and DCL statements to be rejected while a transaction is in progress. Previously, such a statement took effect immediately and was not rolled back with the transaction. Commit or roll back the transaction before executing DDL or DCL.
- Changed the order of TEXT values that ScalarDB SQL compares itself, in
ORDER BY,MIN,MAX, and range conditions inHAVINGand inWHEREafter a join, to Unicode code point order, which matches binary collations on UTF-8 data. Previously, characters above U+FFFF were ordered before characters in U+E000 to U+FFFF.
Bug fixes​
ScalarDB Cluster​
- Upgraded the grpc_health_probe binary to fix security issues: CVE-2026-33818, CVE-2026-39821, CVE-2026-46600, CVE-2026-56852, CVE-2026-56853, CVE-2026-56858, CVE-2026-56859, CVE-2026-56860, CVE-2026-56862, and GHSA-hrxh-6v49-42gf
- Fixed an issue where a user whose authentication methods did not include
USERPASScould log in with username/password authentication. If such a user needs username/password authentication, addUSERPASSto the user's authentication methods and, unless passwordless login is intended, set a password. The fix takes full effect after all nodes in the cluster have been upgraded. - Fixed an issue where, with authentication enabled, a client could keep reading from an open scanner after its auth token had expired or been revoked, or after its READ privilege had been revoked. Fetching from a scanner now re-checks the READ privilege. For OIDC clients, a JWT that expires during a scan now causes an authentication error at the next fetch, as with get and scan; a client that supplies the JWT through
OidcJwtAccessTokenHoldercan set a fresh JWT and continue the scan. - Fixed an issue where batch
GetandScanoperations on tables with encrypted columns failed withIllegalStateExceptionwhenassociated_data_requiredwas enabled and their projections did not include all primary key columns. - Upgraded the grpc_health_probe binary to fix security issues: CVE-2026-56851, CVE-2026-78667, CVE-2026-78669, CVE-2026-84304, CVE-2026-84445, and CVE-2026-97031
- Fixed an issue where closing a ScalarDB Cluster client in the
direct-kubernetesclient mode left behind a thread and a connection to the Kubernetes API server. An application that repeatedly created and closed clients, including through the ScalarDB SQL JDBC driver, could eventually fail to create new threads. - Mitigate CVE-2026-91403 and CVE-2026-114820 (eMudhra CA compromise) by removing the affected emSign root CAs from the cacerts of all container images
ScalarDB SQL​
- Fixed BLOB comparisons that ScalarDB SQL evaluates itself, in
ORDER BYand in range conditions inHAVINGand inWHEREafter a join, treating bytes from0x80up as smaller than0x00to0x7F. - Fixed
MINandMAXonFLOATandDOUBLEcolumns returning a result that depended on row order when the values includedNaN, or both0.0and-0.0.