Transaction retry in Ballerina 2201.x: work that falls outside the transaction manager
0 reputation · 10 May 2021, 18:21 UTC
0 reputation · 10 May 2021, 18:21 UTC
Ballerina's transaction statement in Swan Lake 2201.x wraps a block of work in a local or distributed transaction and supports optional retry, rollback and commit clauses. Connectors such as mysql:Client and postgresql:Client participate through the transaction manager, and transactional named workers extend that participation across workers.
The unresolved part is the boundary. Operations issued through clients that do not participate in the transaction — an HTTP call, a file write, a message publish — are not enrolled, so a rollback does not undo them and a retry can execute them again. The language does not prescribe how such effects should be reconciled with the transactional work.
Verification is a design decision as well. Confirming that a rolled-back transaction left no rows behind requires a read performed outside the transaction, and the isolation level or snapshot appropriate for that read is not fixed by the language.
retry re-executes a block, what guarantees apply to side effects from non-participating clients?35775 reputation · 11 May 2021, 00:04 UTC
In Ballerina Swan Lake 2201.x, only database clients explicitly documented as transaction-aware (e.g., mysql:Client, postgresql:Client) enroll in the transaction manager. HTTP calls, file I/O, and message publishes (Kafka, RabbitMQ, NATS) execute immediately and are never rolled back. A retry clause re-runs the entire transaction block, so any non-participating side effect inside the block runs again — at-least-once semantics with no automatic compensation.
mysql:Client, postgresql:Client) when invoked inside a transaction block; transactional named workers extend this across strands.http:Client calls, file:* writes, outbound messaging producers, any connector without explicit transaction enrollment in its 2201.x documentation.Assume non-participation unless the connector's release notes for your exact 2201.x patch version state otherwise.
When retry triggers (e.g., on a transient DB deadlock), the whole block re-executes. Effects from non-participating clients are not undone by the prior rollback and are re-issued on retry. This yields at-least-once delivery. The language provides no built-in deduplication or compensation.
on-commit handler or separate worker.on-commit clause (runs after successful commit) or after the transaction block — but note on-commit is not itself transactional; a crash between commit and handler loses the effect.To confirm a rollback left no committed rows, you must read outside the transaction:
// Inside transaction - rolled back
transaction {
dbClient->execute("INSERT INTO t VALUES (1)");
// force rollback
rollback;
}
// Verification: NEW client, NO transaction block
mysql:Client verifyClient = check new (url);
stream<record {}> rows = verifyClient->query("SELECT * FROM t WHERE id = 1");
check rows.hasNext() == false; // Confirms no committed row
verifyClient.close();
Use the database's default isolation (typically READ COMMITTED). Reading inside the same transaction sees its own uncommitted/rolled-back view and proves nothing.
Which exact 2201.x patch version (e.g., 2201.4.1, 2201.6.0) and which connector versions are in your distribution? Enrollment behavior can differ across patch releases; confirm against the connector docs for that version before assuming uniform participation.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.