Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3737,6 +3737,17 @@ public double getLoadBalancerBandwidthOutResourceWeight() {
)
private boolean transactionCoordinatorEnabled = false;

@FieldContext(
category = CATEGORY_TRANSACTION,
doc = "Enable the metadata-driven transaction coordinator used by scalable topics."
+ " When true, wire commands (NEW_TXN / END_TXN / etc.) are served by the"
+ " metadata-store-backed coordinator instead of the legacy"
+ " TransactionMetadataStoreService. Requires transactionCoordinatorEnabled"
+ " = true, and must be enabled together with the scalable-topic transaction"
+ " buffer and pending-ack store providers."
)
private boolean transactionCoordinatorScalableTopicsEnabled = false;

@FieldContext(
category = CATEGORY_TRANSACTION,
doc = "Class name for transaction metadata store provider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
import org.apache.pulsar.broker.storage.ManagedLedgerStorageClass;
import org.apache.pulsar.broker.transaction.buffer.TransactionBufferProvider;
import org.apache.pulsar.broker.transaction.buffer.impl.TransactionBufferClientImpl;
import org.apache.pulsar.broker.transaction.coordinator.v5.TransactionCoordinatorV5;
import org.apache.pulsar.broker.transaction.pendingack.TransactionPendingAckStoreProvider;
import org.apache.pulsar.broker.transaction.pendingack.impl.MLPendingAckStoreProvider;
import org.apache.pulsar.broker.validator.MultipleListenerValidator;
Expand Down Expand Up @@ -283,6 +284,7 @@ public class PulsarService implements AutoCloseable, ShutdownService {
private OpenTelemetryTransactionPendingAckStoreStats openTelemetryTransactionPendingAckStoreStats;

private TransactionMetadataStoreService transactionMetadataStoreService;
private TransactionCoordinatorV5 transactionCoordinatorV5;
private TransactionBufferProvider transactionBufferProvider;
private TransactionBufferClient transactionBufferClient;
private HashedWheelTimer transactionTimer;
Expand Down Expand Up @@ -1030,6 +1032,10 @@ public void start() throws PulsarServerException {
.newProvider(config.getTransactionMetadataStoreProviderClassName()), this,
transactionBufferClient, transactionTimer);

if (config.isTransactionCoordinatorScalableTopicsEnabled()) {
transactionCoordinatorV5 = new TransactionCoordinatorV5(this);
}

transactionBufferProvider = TransactionBufferProvider
.newProvider(config.getTransactionBufferProviderClassName());
transactionPendingAckStoreProvider = TransactionPendingAckStoreProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3348,6 +3348,21 @@ protected void handleTcClientConnectRequest(CommandTcClientConnectRequest comman
return;
}

if (service.getPulsar().getConfig().isTransactionCoordinatorScalableTopicsEnabled()) {
service.pulsar().getTransactionCoordinatorV5().handleClientConnect(tcId)
.whenComplete((__, e) -> {
if (e == null) {
commandSender.sendTcClientConnectResponse(requestId);
} else {
log.error().attr("requestId", requestId).attr("tcId", tcId).exception(e)
.log("v5 TC client connect failed");
commandSender.sendTcClientConnectResponse(requestId,
BrokerServiceException.getClientErrorCode(e), e.getMessage());
}
});
return;
}

TransactionMetadataStoreService transactionMetadataStoreService =
service.pulsar().getTransactionMetadataStoreService();

Expand Down Expand Up @@ -3414,6 +3429,22 @@ protected void handleNewTxn(CommandNewTxn command) {
return;
}

if (service.getPulsar().getConfig().isTransactionCoordinatorScalableTopicsEnabled()) {
final String v5Owner = getPrincipal();
service.pulsar().getTransactionCoordinatorV5()
.newTransaction(tcId, command.getTxnTtlSeconds() * 1000L, v5Owner)
.whenComplete((txnId, e) -> {
if (e == null) {
commandSender.sendNewTxnResponse(requestId, txnId, tcId.getId());
} else {
Throwable cause = handleTxnException(e, BaseCommand.Type.NEW_TXN.name(), requestId);
commandSender.sendNewTxnErrorResponse(requestId, tcId.getId(),
BrokerServiceException.getClientErrorCode(cause), cause.getMessage());
}
});
return;
}

TransactionMetadataStoreService transactionMetadataStoreService =
service.pulsar().getTransactionMetadataStoreService();
final String owner = getPrincipal();
Expand Down Expand Up @@ -3465,6 +3496,28 @@ protected void handleAddPartitionToTxn(CommandAddPartitionToTxn command) {
return;
}

if (service.getPulsar().getConfig().isTransactionCoordinatorScalableTopicsEnabled()) {
// v5: TC doesn't need pre-registration — participants advertise themselves by writing
// /txn/op records when they actually apply ops. Still verify ownership before acking,
// matching the legacy authorization surface.
verifyTxnOwnership(txnID)
.thenCompose(isOwner -> isOwner ? CompletableFuture.<Void>completedFuture(null)
: failedFutureTxnNotOwned(txnID))
.whenComplete((v, ex) -> {
if (ex == null) {
writeAndFlush(Commands.newAddPartitionToTxnResponse(requestId,
txnID.getLeastSigBits(), txnID.getMostSigBits()));
} else {
Throwable cause = handleTxnException(ex,
BaseCommand.Type.ADD_PARTITION_TO_TXN.name(), requestId);
writeAndFlush(Commands.newAddPartitionToTxnResponse(requestId,
txnID.getLeastSigBits(), txnID.getMostSigBits(),
BrokerServiceException.getClientErrorCode(cause), cause.getMessage()));
}
});
return;
}

TransactionMetadataStoreService transactionMetadataStoreService =
service.pulsar().getTransactionMetadataStoreService();
verifyTxnOwnership(txnID)
Expand Down Expand Up @@ -3525,6 +3578,27 @@ protected void handleEndTxn(CommandEndTxn command) {
return;
}

if (service.getPulsar().getConfig().isTransactionCoordinatorScalableTopicsEnabled()) {
verifyTxnOwnership(txnID)
.thenCompose(isOwner -> {
if (!isOwner) {
return failedFutureTxnNotOwned(txnID);
}
return service.pulsar().getTransactionCoordinatorV5()
.endTransaction(txnID, txnAction);
})
.whenComplete((__, e) -> {
if (e == null) {
commandSender.sendEndTxnResponse(requestId, txnID, txnAction);
} else {
Throwable cause = handleTxnException(e, BaseCommand.Type.END_TXN.name(), requestId);
commandSender.sendEndTxnErrorResponse(requestId, txnID,
BrokerServiceException.getClientErrorCode(cause), cause.getMessage());
}
});
return;
}

TransactionMetadataStoreService transactionMetadataStoreService =
service.pulsar().getTransactionMetadataStoreService();

Expand Down Expand Up @@ -3569,8 +3643,13 @@ private CompletableFuture<Boolean> isSuperUser() {

private CompletableFuture<Boolean> verifyTxnOwnership(TxnID txnID) {
assert ctx.executor().inEventLoop();
return service.pulsar().getTransactionMetadataStoreService()
.verifyTxnOwnership(txnID, getPrincipal())
CompletableFuture<Boolean> ownerCheck =
service.getPulsar().getConfig().isTransactionCoordinatorScalableTopicsEnabled()
? service.pulsar().getTransactionCoordinatorV5()
.verifyTxnOwnership(txnID, getPrincipal())
: service.pulsar().getTransactionMetadataStoreService()
.verifyTxnOwnership(txnID, getPrincipal());
return ownerCheck
.thenComposeAsync(isOwner -> {
if (isOwner) {
return CompletableFuture.completedFuture(true);
Expand Down Expand Up @@ -3839,6 +3918,28 @@ protected void handleAddSubscriptionToTxn(CommandAddSubscriptionToTxn command) {
return;
}

if (service.getPulsar().getConfig().isTransactionCoordinatorScalableTopicsEnabled()) {
// v5: TC doesn't need pre-registration — participants advertise themselves by writing
// /txn/op records when they actually apply ops. Still verify ownership before acking,
// matching the legacy authorization surface.
verifyTxnOwnership(txnID)
.thenCompose(isOwner -> isOwner ? CompletableFuture.<Void>completedFuture(null)
: failedFutureTxnNotOwned(txnID))
.whenComplete((v, ex) -> {
if (ex == null) {
writeAndFlush(Commands.newAddSubscriptionToTxnResponse(requestId,
txnID.getLeastSigBits(), txnID.getMostSigBits()));
} else {
Throwable cause = handleTxnException(ex,
BaseCommand.Type.ADD_SUBSCRIPTION_TO_TXN.name(), requestId);
writeAndFlush(Commands.newAddSubscriptionToTxnResponse(requestId,
txnID.getLeastSigBits(), txnID.getMostSigBits(),
BrokerServiceException.getClientErrorCode(cause), cause.getMessage()));
}
});
return;
}

TransactionMetadataStoreService transactionMetadataStoreService =
service.pulsar().getTransactionMetadataStoreService();

Expand Down
Loading
Loading