-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[Broker] Add operation timeout to metadata store #13596
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ | |
| import org.apache.pulsar.metadata.api.GetResult; | ||
| import org.apache.pulsar.metadata.api.MetadataCache; | ||
| import org.apache.pulsar.metadata.api.MetadataSerde; | ||
| import org.apache.pulsar.metadata.api.MetadataStoreConfig; | ||
| import org.apache.pulsar.metadata.api.MetadataStoreException; | ||
| import org.apache.pulsar.metadata.api.Notification; | ||
| import org.apache.pulsar.metadata.api.NotificationType; | ||
|
|
@@ -64,6 +65,8 @@ public abstract class AbstractMetadataStore implements MetadataStoreExtended, Co | |
| private final AsyncLoadingCache<String, List<String>> childrenCache; | ||
| private final AsyncLoadingCache<String, Boolean> existsCache; | ||
| private final CopyOnWriteArrayList<MetadataCacheImpl<?>> metadataCaches = new CopyOnWriteArrayList<>(); | ||
| @Getter | ||
| private final MetadataStoreConfig metadataStoreConfig; | ||
|
|
||
| // We don't strictly need to use 'volatile' here because we don't need the precise consistent semantic. Instead, | ||
| // we want to avoid the overhead of 'volatile'. | ||
|
|
@@ -75,6 +78,11 @@ public abstract class AbstractMetadataStore implements MetadataStoreExtended, Co | |
| protected abstract CompletableFuture<Boolean> existsFromStore(String path); | ||
|
|
||
| protected AbstractMetadataStore() { | ||
|
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. I think you should config |
||
| this(null); | ||
| } | ||
|
|
||
| protected AbstractMetadataStore(MetadataStoreConfig metadataStoreConfig) { | ||
| this.metadataStoreConfig = metadataStoreConfig; | ||
| this.executor = Executors | ||
| .newSingleThreadScheduledExecutor(new DefaultThreadFactory("metadata-store")); | ||
| registerListener(this); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ | |
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.CountDownLatch; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.TimeoutException; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.stream.Collectors; | ||
| import lombok.SneakyThrows; | ||
|
|
@@ -69,7 +71,6 @@ public class ZKMetadataStore extends AbstractBatchedMetadataStore | |
| implements MetadataStoreExtended, MetadataStoreLifecycle { | ||
|
|
||
| private final String metadataURL; | ||
| private final MetadataStoreConfig metadataStoreConfig; | ||
| private final boolean isZkManaged; | ||
| private final ZooKeeper zkc; | ||
| private Optional<ZKSessionWatcher> sessionWatcher; | ||
|
|
@@ -80,7 +81,6 @@ public ZKMetadataStore(String metadataURL, MetadataStoreConfig metadataStoreConf | |
|
|
||
| try { | ||
| this.metadataURL = metadataURL; | ||
| this.metadataStoreConfig = metadataStoreConfig; | ||
| isZkManaged = true; | ||
| zkc = PulsarZooKeeperClient.newBuilder().connectString(metadataURL) | ||
| .connectRetryPolicy(new BoundExponentialBackoffRetryPolicy(100, 60_000, Integer.MAX_VALUE)) | ||
|
|
@@ -109,7 +109,6 @@ public ZKMetadataStore(ZooKeeper zkc) { | |
| super(MetadataStoreConfig.builder().build()); | ||
|
|
||
| this.metadataURL = null; | ||
| this.metadataStoreConfig = null; | ||
| this.isZkManaged = false; | ||
| this.zkc = zkc; | ||
| this.sessionWatcher = Optional.of(new ZKSessionWatcher(zkc, this::receivedSessionEvent)); | ||
|
|
@@ -145,7 +144,10 @@ protected void receivedSessionEvent(SessionEvent event) { | |
| @Override | ||
| protected void batchOperation(List<MetadataOp> ops) { | ||
| try { | ||
| AtomicBoolean callback = new AtomicBoolean(false); | ||
|
|
||
| zkc.multi(ops.stream().map(this::convertOp).collect(Collectors.toList()), (rc, path, ctx, results) -> { | ||
| callback.set(true); | ||
| if (results == null) { | ||
| Code code = Code.get(rc); | ||
| if (code == Code.CONNECTIONLOSS) { | ||
|
|
@@ -186,6 +188,12 @@ protected void batchOperation(List<MetadataOp> ops) { | |
| } | ||
| } | ||
| }, null); | ||
|
|
||
| executor.schedule(() -> { | ||
| if (!callback.get()) { | ||
| ops.forEach(n -> n.getFuture().completeExceptionally(new TimeoutException())); | ||
| } | ||
| }, getMetadataStoreConfig().getOperationTimeoutSeconds(), TimeUnit.SECONDS); | ||
|
Comment on lines
+192
to
+196
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. I think we don't need to change here? The batch operation just groups a few ops to one batch, each single ops has a future and if the caller requires an operation timeout, the caller can only use
Member
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. If the caller use
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. Yes, using I think the main point is to find the root cause of why the the zk doesn't call the callback, is the performance bottleneck or deadlock?
Member
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.
We cannot determine the order, maybe we should avoid using
I have found the root cause of zk doesn't call the callback, and I submitted #13809 to fix this. |
||
| } catch (Throwable t) { | ||
| ops.forEach(o -> o.getFuture().completeExceptionally(new MetadataStoreException(t))); | ||
| } | ||
|
|
@@ -501,7 +509,7 @@ public CompletableFuture<Void> initializeCluster() { | |
| if (this.metadataURL == null) { | ||
| return FutureUtil.failedFuture(new MetadataStoreException("metadataURL is not set")); | ||
| } | ||
| if (this.metadataStoreConfig == null) { | ||
| if (this.getMetadataStoreConfig() == null) { | ||
| return FutureUtil.failedFuture(new MetadataStoreException("metadataStoreConfig is not set")); | ||
| } | ||
| int chrootIndex = metadataURL.indexOf("/"); | ||
|
|
@@ -510,10 +518,10 @@ public CompletableFuture<Void> initializeCluster() { | |
| String zkConnectForChrootCreation = metadataURL.substring(0, chrootIndex); | ||
| try (ZooKeeper chrootZk = PulsarZooKeeperClient.newBuilder() | ||
| .connectString(zkConnectForChrootCreation) | ||
| .sessionTimeoutMs(metadataStoreConfig.getSessionTimeoutMillis()) | ||
| .sessionTimeoutMs(getMetadataStoreConfig().getSessionTimeoutMillis()) | ||
| .connectRetryPolicy( | ||
| new BoundExponentialBackoffRetryPolicy(metadataStoreConfig.getSessionTimeoutMillis(), | ||
| metadataStoreConfig.getSessionTimeoutMillis(), 0)) | ||
| new BoundExponentialBackoffRetryPolicy(getMetadataStoreConfig().getSessionTimeoutMillis(), | ||
| getMetadataStoreConfig().getSessionTimeoutMillis(), 0)) | ||
| .build()) { | ||
| if (chrootZk.exists(chrootPath, false) == null) { | ||
| createFullPathOptimistic(chrootZk, chrootPath, new byte[0], CreateMode.PERSISTENT); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /** | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.apache.pulsar.metadata.impl; | ||
|
|
||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertThrows; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.Mockito.doNothing; | ||
| import static org.mockito.Mockito.spy; | ||
|
|
||
| import java.util.concurrent.CompletionException; | ||
| import java.util.concurrent.TimeoutException; | ||
| import org.apache.pulsar.metadata.BaseMetadataStoreTest; | ||
| import org.apache.pulsar.metadata.api.MetadataStore; | ||
| import org.apache.zookeeper.ZooKeeper; | ||
| import org.testng.annotations.Test; | ||
|
|
||
| public class ZKMetadataStoreTest extends BaseMetadataStoreTest { | ||
| @Test | ||
| public void testOperationTimeout() { | ||
| ZooKeeper zooKeeper = spy(zkc); | ||
| doNothing().when(zooKeeper).multi(any(), any(), any()); | ||
|
|
||
| MetadataStore zkMetadataStore = new ZKMetadataStore(zooKeeper); | ||
| CompletionException ex = assertThrows(CompletionException.class, () -> zkMetadataStore.get("/").join()); | ||
| assertEquals(TimeoutException.class, ex.getCause().getClass()); | ||
| } | ||
| } |
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.
Will
sessionTimeoutMillisapply ? Why do we needoperationTimeoutSeconds?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
sessionTimeoutMillisis used for heartbeat checks between zk client and server, so I don't think we should use this field as config of operation timeout.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.
Do we have any specific case for "the zk doesn't call the callback"?
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.
I made an issue for this, you can try to reproduce this by issue: #13211. This issue cannot find by the Unit test or Integration test.