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
Issue 3223: RetentionTest failure #3272
Issue 3223: RetentionTest failure #3272
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3272 +/- ##
============================================
- Coverage 78.85% 78.85% -0.01%
- Complexity 8024 8032 +8
============================================
Files 609 609
Lines 31497 31512 +15
Branches 3046 3048 +2
============================================
+ Hits 24838 24849 +11
- Misses 4865 4870 +5
+ Partials 1794 1793 -1
Continue to review full report at Codecov.
|
From the curator java docs observing that
|
Yes @shrids, you are right. But apparently, this behavior is not completely honored. Even using The difference between In
In this PR, we first create the bucket as a znode before adding any Stream (in
This avoids that Curator creates the bucket parent ( This leads to two considerations:
|
@@ -45,6 +46,7 @@ | |||
|
|||
@Slf4j | |||
@RunWith(SystemTestRunner.class) | |||
@Ignore |
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.
Please remove.
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.
This was a temporary change for testing purposes, is reverted now.
@@ -321,6 +322,9 @@ public void unregisterBucketOwnershipListener() { | |||
public void registerBucketChangeListener(int bucket, BucketChangeListener listener) { | |||
Preconditions.checkNotNull(listener); | |||
|
|||
String bucketRoot = String.format(BUCKET_PATH, bucket); |
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.
This change does not seem right. There are other listeners in this class too, like the ownership one. Also, this is the wrong place to do this. If we are to create this bucket znode, it should be during the process of acquiring ownership, not while registering a listener.
I need to do some further investigation before making a concrete proposal here.
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.
Follow-up question: why not create all necessary persistent znodes during the initialization of the controller process?
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.
@fpj This is another possibility that may be interesting, given that I observed the same phenomenon related to other znodes. This can be done when instantiating a new ZKStreamMetadataStore
object. In fact, ZKHostStore
does something similar in the method tryInit()
, but instead of ensuring that the znode is created in the constructor, it is checked on every call to Zookeeper (but executed only once). I will try to find a better, broader znode initialization approach.
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.
Following your suggestion, I added a new method to StreamMetadataStore
interface called initializeMetadataStore()
. In the case of ZKStreamMetadataStore
, it contains the logic to initialize the Stream buckets. This method is invoked in ControllerServiceStarter
, just after the StreamMetadataStore
object gets created. This may be a suitable "centralized" point to initialize all the necessary metadata items to facilitate maintenance.
Note that this solution fixes the reported problem (system tests are passing with the current version of the code) and could be easily cherry-picked to previous versions. But it could subject to changes in PR #3230, given that the initialization of "buckets" will be done for multiple services, not just for the retention service.
@@ -460,6 +463,35 @@ public void unregisterBucketListener(int bucket) { | |||
}); | |||
} | |||
|
|||
/** | |||
* When managing the Controller's metadata in Zookeeper, we explicitly create parent bucket zNodes (so they are of | |||
* type "zNode"). Otherwise, they may be inadvertently created as Zookeeper "containers" by Curator. This would lead |
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.
Can you point in this comment to curator documentation that says this, please?
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.
Added a reference for this. Actually, the first line of the page says: NOTE: Most Curator recipes will autocreate parent nodes of paths given to the recipe as CreateMode.CONTAINER
.
Predicate<Throwable> isDataExistsException = ex -> Exceptions.unwrap(ex) instanceof StoreException.DataExistsException; | ||
for (int bucket = 0; bucket < bucketCount; bucket++) { | ||
final String bucketPath = String.format(BUCKET_PATH, bucket); | ||
initializationFutures.add(retryPolicy.retryWhen(isDataExistsException.negate()).run(() -> |
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.
Does curator have its own retry mechanism? Do we need to retry ourselves?
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.
Done.
controller/src/main/java/io/pravega/controller/store/stream/StreamMetadataStore.java
Outdated
Show resolved
Hide resolved
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.
@RaulGracia please remove the initializeStore method and have specific metadata creation apis that should be called from specific modules that need those metadata initialized.
controller/src/main/java/io/pravega/controller/store/stream/StreamMetadataStore.java
Outdated
Show resolved
Hide resolved
controller/src/main/java/io/pravega/controller/store/stream/ZKStreamMetadataStore.java
Outdated
Show resolved
Hide resolved
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.
+1, this current approach is fine by me, awaiting @shiveshr 's take.
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.
minor suggested improvements.
@Override | ||
public CompletableFuture<Void> createBucketsRoot() { | ||
List<CompletableFuture<Void>> initializationFutures = new ArrayList<>(); | ||
initializationFutures.add(initializeZNode(BUCKET_OWNERSHIP_PATH)); // Also initialize ownership zNode. |
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.
please also add the root path explicitly as well..
following should be done:
"/buckets"
"/buckets/ownership"
"buckets/<bucket-id>"
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.
we also want to chain bucketRootPath --> bucketOwnershipPath --> ListOf bucketPath
rather than run them concurrently as bucket path creation may fail if bucketRootPath is not yet created.
controller/src/main/java/io/pravega/controller/store/stream/ZKStreamMetadataStore.java
Outdated
Show resolved
Hide resolved
…type Container). Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
…ode. Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
…stances to contain metadata initialization tasks during Controller start. Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
6cc707f
to
6c0e3bc
Compare
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
@Override | ||
public CompletableFuture<Void> createBucketsRoot() { | ||
List<CompletableFuture<Void>> initializationFutures = new ArrayList<>(); | ||
initializationFutures.add(initializeZNode(BUCKET_ROOT_PATH).thenCompose(v -> initializeZNode(BUCKET_OWNERSHIP_PATH))); |
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.
following is what i was suggesting as it will ensure that parent znodes are created and only then do we attempt children znode creation:
return initializeZNode(BUCKET_ROOT_PATH)
.thenCompose(v -> initializeZNode(BUCKET_OWNERSHIP_PATH))
.thenCompose(v -> {
for (int bucket = 0; bucket < bucketCount; bucket++) {
final String bucketPath = String.format(BUCKET_PATH, bucket);
initializationFutures.add(initializeZNode(bucketPath));
}
return Futures.allOf(initializationFutures);
})
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
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 know we are testing this change primarily with system tests, but shouldn't we have (mocked) unit tests to exericize the code paths we are adding here?
Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
* Cherry pick changes from PR #3272 to branch r0.4. Signed-off-by: Raúl Gracia <raul.gracia@emc.com>
* master: (31 commits) Issue 3279: Client is performing blocking operations inside Netty Event Loop (pravega#3280) Issue 3200: Document Review: wire-protocol.md (pravega#3201) Issue 2933: (SegmentStore) testEndToEndWithFencing fixes (pravega#3294) Issue pravega#2768: Rename PravegaCredsWrapper class and its member variable (pravega#3302) Issue pravega#3299: Reduce dependencies in client (pravega#3300) Issue 2895: Wire Protocol implementation for TableStore (pravega#3283) Issue-3239: Make DYNAMIC_LOGGER non-static (pravega#3293) Issue 2956: (SegmentStore) TableSegment.deleteIfEmpty (pravega#3263) Issue pravega#3289: Use compile-time dependencies where possible (pravega#3290) Issue 3223: RetentionTest failure (pravega#3272) Issue 2001: Make a defensive copy of streamCut's position map (pravega#3281) Issue 3287: Exclude "passwd" extensions from rat. (pravega#3288) Issue 3285: Changed image pull policy. (pravega#3286) Issue 3266: Ensure bookkeeper.bkAckQuorumSize is 3 for BookieFailover test. (pravega#3267) Issue 3264: Return a SegmentHandle from Storage create method. (pravega#3276) Issue 3086: Controller Diagrams(new) included (pravega#3097) Issue 2570: Update segment metrics when a transaction merge happens (pravega#3239) Issue 3149: (SegmentStore) Pinned Segments (pravega#3215) Issue 3269: Fix invalid initialization order in AbstractScaleTests. (pravega#3270) Issue 3148: (Segment Store) Segment Metadata Store (pravega#3206) ...
Change log description
This PR explicitly creates Controller bucket zNodes before a change listener is registered. By doing this, we make sure that a bucket zNode is not of type "container", which may be the case if it is created by Curator as a parent of an actual zNode. This prevents Zookeeper from automatically deleting bucket zNodes in its internal process of garbage collection of empty containers.
Purpose of the change
Fixes #3223.
What the code does
This PR initializes the Stream bucket znode when we register a listener for changes on that bucket.
The reason for doing so is the following: Zookeeper recently (v
3.5+
) introduced a new kind of nodes calledcontainers
(ref). This kind of nodes differ from traditional zNodes in the following: once acontainer
is populated and then emptied (i.g., all its child nodes have been deleted), it is candidate for garbage collection in Zookeeper (see ContainerManager.java):By default, Curator exploits this feature when available. This implies that, when creating a zNode with a non-existent parent, Curator can create a
container
for the parent node and a zNode for the child one (i.e.,PERSISTENT
).This was the case for the issue that motivates this PR. Before the execution of
RetentionTest
, another test (RestAPITest
) created and deleted a Stream with a retention policy, so it was added and removed from a Stream bucket (buckets/0
). Curator seems to createbucket/0
as acontainer
and, according to the previous description ofContainerManager
, the bucket node was candidate for garbage collection (i.e., filled, and then emptied) before the execution ofRetentionTest
. Once Zookeeper removes that bucket, the retention service (StreamCutBucketService
) stops working, given that the watch on the bucket is broken.To avoid empty Stream buckets from being deleted, this PR explicitly creates the bucket node as a zNode. This prevents Curator from making it of type
container
and suffices to prevent Zookeeper from deleting it.Note: We also tried to use the option
CuratorFrameworkFactory.builder().dontUseContainerParents()
, but it seems to do not work as expected when used jointly withcreatingParentsIfNeeded()
.How to verify it
Locally replayed failure scenario of
RetentionTest
and works.System test build passes entirely (only
BookieFailoverTest
has been disabled).All tests should be passing as before.