Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -301,11 +301,26 @@ private void handleMetadataSessionEvent(SessionEvent e) {
lastMetadataSessionEvent = e;
}

@VisibleForTesting
boolean isLeader() {
return pulsar.getLeaderElectionService() != null && pulsar.getLeaderElectionService().isLeader();
}

private LoadSheddingStrategy createLoadSheddingStrategy() {
return Reflections.createInstance(conf.getLoadBalancerLoadSheddingStrategy(), LoadSheddingStrategy.class,
Thread.currentThread().getContextClassLoader());
}

@VisibleForTesting
void setLoadSheddingStrategy(LoadSheddingStrategy loadSheddingStrategy) {
this.loadSheddingStrategy = loadSheddingStrategy;
}

@VisibleForTesting
LoadData getLoadData() {
return loadData;
}

/**
* Initialize this load manager.
*
Expand Down Expand Up @@ -635,6 +650,10 @@ public void disableBroker() throws PulsarServerException {
*/
@Override
public synchronized void doLoadShedding() {
if (!isLeader()) {
log.debug().log("Skipping load shedding because this broker is not the leader");
return;
}
if (!LoadManagerShared.isLoadSheddingEnabled(pulsar)) {
return;
}
Expand Down Expand Up @@ -684,12 +703,14 @@ public synchronized void doLoadShedding() {
return;
}

log.info().attr("class", loadSheddingStrategy.getClass().getSimpleName())
.attr("bundle", bundle).attr("sourceBroker", broker).attr("destBroker", destBroker.get())
.log("Unloading bundle from source broker to dest broker");
try {
pulsar.getAdminClient().namespaces()
.unloadNamespaceBundle(namespaceName, bundleRange, destBroker.get());
if (!isLeader()) {
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return is inside nested forEach lambdas, so it only skips the current foreach.This could be optimized.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 68869bc. Replaced the nested lambdas with regular loops so leadership loss exits the load-shedding operation.

}
log.info().attr("class", loadSheddingStrategy.getClass().getSimpleName())
.attr("bundle", bundle).attr("sourceBroker", broker).attr("destBroker", destBroker.get())
.log("Unloading bundle from source broker to dest broker");
unloadNamespaceBundle(namespaceName, bundleRange, destBroker.get());
loadData.getRecentlyUnloadedBundles().put(bundle, System.currentTimeMillis());
unloadBundleCount++;
unloadBundleForBroker.set(true);
Expand All @@ -706,6 +727,12 @@ public synchronized void doLoadShedding() {
updateBundleUnloadingMetrics();
}

@VisibleForTesting
void unloadNamespaceBundle(String namespaceName, String bundleRange, String destinationBroker)
throws PulsarServerException, PulsarAdminException {
pulsar.getAdminClient().namespaces().unloadNamespaceBundle(namespaceName, bundleRange, destinationBroker);
}

/**
* As leader broker, update bundle unloading metrics.
*/
Expand Down Expand Up @@ -1204,20 +1231,34 @@ private int selectTopKBundle() {
*/
@Override
public void writeBundleDataOnZooKeeper() {
if (!isLeader()) {
log.debug().log("Skipping bundle data write because this broker is not the leader");
return;
}
updateBundleData();
if (!isLeader()) {
return;
}
// Write the bundle data to metadata store.
List<CompletableFuture<Void>> futures = new ArrayList<>();

// use synchronized to protect bundleArr.
synchronized (bundleArr) {
int updateBundleCount = selectTopKBundle();
bundleArr.stream().limit(updateBundleCount).forEach(entry -> futures.add(
pulsarResources.getLoadBalanceResources().getBundleDataResources().updateBundleData(
entry.getKey(), (BundleData) entry.getValue())));
for (Map.Entry<String, ? extends Comparable> entry : bundleArr.subList(0, updateBundleCount)) {
if (!isLeader()) {
break;
}
futures.add(pulsarResources.getLoadBalanceResources().getBundleDataResources().updateBundleData(
entry.getKey(), (BundleData) entry.getValue()));
}
}

// Write the time average broker data to metadata store.
for (Map.Entry<String, BrokerData> entry : loadData.getBrokerData().entrySet()) {
if (!isLeader()) {
break;
}
final String broker = entry.getKey();
final TimeAverageBrokerData data = entry.getValue().getTimeAverageData();
futures.add(pulsarResources.getLoadBalanceResources()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
Expand All @@ -34,6 +35,7 @@
import static org.testng.Assert.fail;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.hash.Hashing;
Expand All @@ -56,6 +58,8 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import lombok.Cleanup;
Expand All @@ -66,6 +70,7 @@
import org.apache.pulsar.broker.loadbalance.LoadBalancerTestingUtils;
import org.apache.pulsar.broker.loadbalance.LoadData;
import org.apache.pulsar.broker.loadbalance.LoadManager;
import org.apache.pulsar.broker.loadbalance.LoadSheddingStrategy;
import org.apache.pulsar.broker.loadbalance.ResourceUnit;
import org.apache.pulsar.broker.loadbalance.impl.LoadManagerShared.BrokerTopicLoadingPredicate;
import org.apache.pulsar.client.admin.Namespaces;
Expand Down Expand Up @@ -295,6 +300,75 @@ private String mockBundleName(final int i) {
return String.format("%d/%d/0x00000000_0xffffffff", i, i);
}

@Test
public void testFollowerSkipsLoadShedding() {
Awaitility.await().until(() -> pulsar1.getLeaderElectionService().isLeader()
|| pulsar2.getLeaderElectionService().isLeader());

ModularLoadManagerImpl followerLoadManager = pulsar1.getLeaderElectionService().isLeader()
? secondaryLoadManager : primaryLoadManager;

LoadSheddingStrategy loadSheddingStrategy = Mockito.mock(LoadSheddingStrategy.class);
followerLoadManager.setLoadSheddingStrategy(loadSheddingStrategy);

followerLoadManager.doLoadShedding();

verifyNoInteractions(loadSheddingStrategy);
}

@Test
public void testLoadSheddingStopsWhenLeadershipChangesBeforeUnload() throws Exception {
Awaitility.await().until(() -> primaryLoadManager.getAvailableBrokers().size() > 1);

AtomicBoolean leader = new AtomicBoolean(true);
ModularLoadManagerImpl loadManagerSpy = spy(primaryLoadManager);
doAnswer(invocation -> leader.get()).when(loadManagerSpy).isLeader();

LoadSheddingStrategy loadSheddingStrategy = Mockito.mock(LoadSheddingStrategy.class);
loadManagerSpy.setLoadSheddingStrategy(loadSheddingStrategy);
when(loadSheddingStrategy.findBundlesForUnloading(any(), any()))
.thenReturn(ImmutableMultimap.of(primaryBrokerId, mockBundleName(1)));
doAnswer(invocation -> true).when(loadManagerSpy).shouldNamespacePoliciesUnload(
Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
doAnswer(invocation -> true).when(loadManagerSpy).shouldAntiAffinityNamespaceUnload(
Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
doAnswer(invocation -> {
leader.set(false);
return Optional.of(secondaryBrokerId);
}).when(loadManagerSpy).selectBroker(any());
doNothing().when(loadManagerSpy).unloadNamespaceBundle(
Mockito.anyString(), Mockito.anyString(), Mockito.anyString());

loadManagerSpy.doLoadShedding();

verify(loadManagerSpy).selectBroker(any());
verify(loadManagerSpy, Mockito.never()).unloadNamespaceBundle(
Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
}

@Test
public void testBundleDataWriteStopsWhenLeadershipChangesBeforeMetadataWrite() throws Exception {
String bundle = mockBundleName(99);
BundleData bundleData = new BundleData(10, 1000);
String bundleDataPath = String.format("%s/%s", BUNDLE_DATA_BASE_PATH, bundle);
MetadataCache<BundleData> metadataCache = pulsar1.getLocalMetadataStore().getMetadataCache(BundleData.class);
metadataCache.create(bundleDataPath, bundleData).join();

Awaitility.await().until(() -> primaryLoadManager.getLoadData().getBrokerData().containsKey(primaryBrokerId));
AtomicInteger leaderChecks = new AtomicInteger();
ModularLoadManagerImpl loadManagerSpy = spy(primaryLoadManager);
LoadData loadData = loadManagerSpy.getLoadData();
loadData.getBundleData().clear();
loadData.getBundleData().put(bundle, bundleData);
loadData.getBrokerData().get(primaryBrokerId).getLocalData().getLastStats()
.put(bundle, new NamespaceBundleStats());
doAnswer(invocation -> leaderChecks.getAndIncrement() < 2).when(loadManagerSpy).isLeader();

loadManagerSpy.writeBundleDataOnZooKeeper();

assertEquals(metadataCache.getWithStats(bundleDataPath).get().get().getStat().getVersion(), 0);
}

// Test disabled since it's depending on CPU usage in the machine
@Test(enabled = false)
public void testCandidateConsistency() throws Exception {
Expand Down