-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[fix][broker] Prevent BookkeeperSchemaStorage, BookkeeperBucketSnapshotStorage, and compactors from bypassing namespace bookie affinity #26705
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: master
Are you sure you want to change the base?
Changes from all commits
314f70c
a7b2ccb
1fa761e
6498854
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,8 @@ | |
| import org.apache.pulsar.broker.stats.prometheus.PrometheusMetricsServlet; | ||
| import org.apache.pulsar.broker.stats.prometheus.PrometheusRawMetricsProvider; | ||
| import org.apache.pulsar.broker.stats.prometheus.PulsarPrometheusMetricsServlet; | ||
| import org.apache.pulsar.broker.storage.BookKeeperClientContext; | ||
| import org.apache.pulsar.broker.storage.BookKeeperPlacementPolicyConfigResolver; | ||
| import org.apache.pulsar.broker.storage.BookkeeperManagedLedgerStorageClass; | ||
| import org.apache.pulsar.broker.storage.ManagedLedgerStorage; | ||
| import org.apache.pulsar.broker.storage.ManagedLedgerStorageClass; | ||
|
|
@@ -171,6 +173,8 @@ | |
| import org.apache.pulsar.common.naming.NamespaceName; | ||
| import org.apache.pulsar.common.naming.TopicName; | ||
| import org.apache.pulsar.common.policies.data.ClusterDataImpl; | ||
| import org.apache.pulsar.common.policies.data.EnsemblePlacementPolicyConfig; | ||
| import org.apache.pulsar.common.policies.data.EnsemblePlacementPolicyConfig.ParseEnsemblePlacementPolicyConfigException; | ||
| import org.apache.pulsar.common.policies.data.InactiveTopicDeleteMode; | ||
| import org.apache.pulsar.common.policies.data.OffloadPoliciesImpl; | ||
| import org.apache.pulsar.common.protocol.schema.SchemaStorage; | ||
|
|
@@ -1659,6 +1663,64 @@ public BookKeeper getBookKeeperClient() { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Resolve the namespace placement policy for an auxiliary ledger using the broker's default BookKeeper storage. | ||
| * The topic's managed-ledger storage class is not used to select the client. | ||
| * | ||
| * @param topicName the topic that owns the ledger | ||
| * @return a future that completes with the BookKeeper client and matching placement metadata | ||
| */ | ||
| public CompletableFuture<BookKeeperClientContext> getBookKeeperClientContext(TopicName topicName) { | ||
| return getBookKeeperClientContext(topicName, this::getBookKeeperClient); | ||
| } | ||
|
|
||
| /** | ||
| * Resolve the namespace placement policy for an auxiliary ledger, using the caller's BookKeeper client when there | ||
| * is no custom policy. A custom policy uses the broker's default BookKeeper storage class, not the topic's | ||
| * managed-ledger storage class. The caller's client must access the same BookKeeper backend for later reads and | ||
| * deletion. | ||
| * | ||
| * @param topicName the topic that owns the ledger | ||
| * @param fallbackDefaultClient the caller's existing BookKeeper client, used only when there is no custom policy | ||
| * @return a future that completes with the BookKeeper client and matching placement metadata | ||
| */ | ||
| public CompletableFuture<BookKeeperClientContext> getBookKeeperClientContext( | ||
| TopicName topicName, Supplier<BookKeeper> fallbackDefaultClient) { | ||
| return CompletableFuture.completedFuture(topicName) | ||
| .thenCompose(name -> { | ||
| Objects.requireNonNull(name, "topicName"); | ||
| return getPulsarResources().getLocalPolicies() | ||
| .getLocalPoliciesAsync(name.getNamespaceObject()); | ||
| }).thenCompose(localPolicies -> { | ||
| EnsemblePlacementPolicyConfig placementPolicyConfig = | ||
| BookKeeperPlacementPolicyConfigResolver.resolve(getConfig(), topicName, localPolicies) | ||
| .orElse(null); | ||
| if (placementPolicyConfig == null) { | ||
| try { | ||
| return CompletableFuture.completedFuture( | ||
| BookKeeperClientContext.create(fallbackDefaultClient.get(), null)); | ||
| } catch (ParseEnsemblePlacementPolicyConfigException e) { | ||
| return CompletableFuture.failedFuture(e); | ||
| } | ||
| } | ||
| ManagedLedgerStorageClass defaultStorageClass = | ||
| getManagedLedgerStorage().getDefaultStorageClass(); | ||
| if (!(defaultStorageClass instanceof BookkeeperManagedLedgerStorageClass bkStorageClass)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. placementPolicyConfig can be null here, but we still require the default managed-ledger storage class to be BookkeeperManagedLedgerStorageClass. For schema/bucket this looks like a behavior regression unrelated to affinity: both components already own a BookKeeper client created through BookKeeperClientFactory, so they could work with a custom/non-BK ManagedLedgerStorage before this change. Could we require a policy-aware storage class only when an effective custom placement policy is present, and preserve the existing component/default client path when there is no custom policy? The PR description says unsupported storage should fail when it cannot honor a custom placement policy, but the current check also fails the no-policy case.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 6498854. When there is no effective custom placement policy, the new overload uses the caller's existing BookKeeper client; schema and bucket pass the clients they already own. A custom policy still requires a BookkeeperManagedLedgerStorageClass so that we fail rather than silently ignore it. I added tests for the no-policy/non-BK-default case and both custom-policy cases. |
||
| return CompletableFuture.failedFuture( | ||
| new UnsupportedOperationException("BookKeeper client is not available")); | ||
| } | ||
| return bkStorageClass.getBookKeeperClient(placementPolicyConfig) | ||
| .thenCompose(bookKeeper -> { | ||
| try { | ||
| return CompletableFuture.completedFuture( | ||
| BookKeeperClientContext.create(bookKeeper, placementPolicyConfig)); | ||
| } catch (ParseEnsemblePlacementPolicyConfigException e) { | ||
| return CompletableFuture.failedFuture(e); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| public ManagedLedgerFactory getDefaultManagedLedgerFactory() { | ||
| return getManagedLedgerStorage().getDefaultStorageClass().getManagedLedgerFactory(); | ||
| } | ||
|
|
@@ -1776,7 +1838,7 @@ public Compactor getNullableCompactor() { | |
| public StrategicTwoPhaseCompactor newStrategicCompactor() throws PulsarServerException { | ||
| return new StrategicTwoPhaseCompactor(this.getConfiguration(), | ||
| getClient(), getBookKeeperClient(), | ||
| getCompactorExecutor()); | ||
| getCompactorExecutor(), this::getBookKeeperClientContext); | ||
| } | ||
|
|
||
| public synchronized StrategicTwoPhaseCompactor getStrategicCompactor() throws PulsarServerException { | ||
|
|
||
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.
One related question: this always borrows the client from getDefaultStorageClass(), while normal topic loading honors PersistencePolicies.managedLedgerStorageClassName. With a multi-storage-class ManagedLedgerStorage, the owner topic can therefore use a different storage class/client than this "topic-aware" context.
Is the intended contract only "apply the namespace affinity to the broker's default BookKeeper storage", or should auxiliary ledgers follow the topic's actual storage class as well? If the former is intentional, I think it would be worth documenting that limitation explicitly.
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 intended contract is to apply namespace affinity within the existing BookKeeper auxiliary-ledger backend. This PR does not route auxiliary ledgers by the topic's managed-ledger storage class. With no custom policy, schema and bucket retain their existing clients; with a custom policy, the context borrows from the default BookKeeper storage class. Following the topic's actual storage class would also require persisting backend identity with each auxiliary-ledger reference and routing reads and deletes accordingly. I documented this boundary in the getBookKeeperClientContext Javadoc in 6498854.