Skip to content
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

Rest Endpoint to Create ZNode #12497

Merged
merged 15 commits into from
Feb 29, 2024
Merged

Conversation

suddendust
Copy link
Contributor

@suddendust suddendust commented Feb 26, 2024

The only way to create a znode right now via the controller API is using /zk/put. However, this overwrites any previous data already existing at the given path. There are certain use-cases in which we want to fail creation of a node if it already exists. For example, creating an ephemeral znode at a path to acquire a mutex. For such cases, we want to create the lock atomically.

Behaviour:

Access Option: 0x40,0x80 (PERSISTENT_WITH_TTL, PERSISTENT_SEQUENTIAL_WITH_TTL)
TTL: Applies.
Range: > 0, will return a 400 if access option is 0x40 or 0x80 and range < 0.

TTL does not apply in other cases. In such cases, there is no check on TTL.

This endpoint will return an Bad Request if a node already exists (with the corresponding error message).

Note: For TTL nodes, enable this System.setProperty("zookeeper.extendedTypesEnabled", "true") else it'll throw a KeeperException: UNIMPLEMENTED.

@suddendust suddendust changed the title Rest Endpoint for Create ZNode Rest Endpoint to Create ZNode Feb 26, 2024
@codecov-commenter
Copy link

codecov-commenter commented Feb 26, 2024

Codecov Report

Attention: Patch coverage is 9.09091% with 20 lines in your changes are missing coverage. Please review.

Project coverage is 61.74%. Comparing base (59551e4) to head (bbf9d44).
Report is 31 commits behind head on master.

Files Patch % Lines
...ot/controller/api/resources/ZookeeperResource.java 0.00% 18 Missing ⚠️
...spi/utils/builder/ControllerRequestURLBuilder.java 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #12497      +/-   ##
============================================
- Coverage     61.75%   61.74%   -0.01%     
  Complexity      207      207              
============================================
  Files          2436     2450      +14     
  Lines        133233   133550     +317     
  Branches      20636    20692      +56     
============================================
+ Hits          82274    82463     +189     
- Misses        44911    44985      +74     
- Partials       6048     6102      +54     
Flag Coverage Δ
custom-integration1 <0.01% <0.00%> (-0.01%) ⬇️
integration <0.01% <0.00%> (-0.01%) ⬇️
integration1 <0.01% <0.00%> (-0.01%) ⬇️
integration2 0.00% <0.00%> (ø)
java-11 61.69% <9.09%> (-0.02%) ⬇️
java-21 61.60% <9.09%> (-0.03%) ⬇️
skip-bytebuffers-false 61.73% <9.09%> (-0.02%) ⬇️
skip-bytebuffers-true 61.57% <9.09%> (+33.84%) ⬆️
temurin 61.74% <9.09%> (-0.01%) ⬇️
unittests 61.74% <9.09%> (-0.01%) ⬇️
unittests1 46.91% <0.00%> (+0.02%) ⬆️
unittests2 27.69% <9.09%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

data = payload;
}
if (StringUtils.isEmpty(data)) {
throw new ControllerApplicationException(LOGGER, "Must provide data through query parameter or payload",
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel it's ok to create an empty znode. You can put a warning for this.

Copy link
Contributor Author

@suddendust suddendust Feb 26, 2024

Choose a reason for hiding this comment

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

I'd not want to change the behaviour of the existing API right now.

Edit: I mean if we are making changes to the existing /zk/put API.

if (result) {
return new SuccessResponse("Successfully updated path: " + path);
} else {
throw new ControllerApplicationException(LOGGER, "ZNode already exists at path: " + path,
Copy link
Contributor

Choose a reason for hiding this comment

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

how do you know is due to znode already exists?
Shall we check the znode existence first then throw or check znode exist inside the else then throw?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_pinotHelixResourceManager.createZKNode(path, znRecord, accessOption, ttl); returns a false if the node already exists, can't think of other scenarios in which it would return a false. But sure we can check for the existence of the path first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added explicit path check

@ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 204, message = "No Content"),
@ApiResponse(code = 400, message = "Bad Request"), @ApiResponse(code = 500, message = "Internal server error")
})
public SuccessResponse createNode(
Copy link
Contributor

Choose a reason for hiding this comment

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

This is pretty much the same as putData API, except ttl option.
Should you add a new parameter like force default to true into putData API instead of creating a new one?
You can add a new exception into it like NodeAlreadyExist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This complicates the existing API. Now we have 1. expectedVersion which is not required for creating new node. 2. ttl which is not required to update a new node. Not to mention the POST vs PUT semantics would also be incorrect. Wdyt?

Response.Status.INTERNAL_SERVER_ERROR, e);
}
if (result) {
return new SuccessResponse("Successfully updated path: " + path);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: updated -> created

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

public SuccessResponse createNode(
@ApiParam(value = "Zookeeper Path, must start with /", required = true) @QueryParam("path") String path,
@ApiParam(value = "Content") @QueryParam("data") @Nullable String data,
@ApiParam(value = "ttl", defaultValue = "-1") @QueryParam("ttl") @DefaultValue("-1") int ttl,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we please add an explanation: -1 means no ttl, and positive values means ephemeral znode

Copy link
Contributor Author

@suddendust suddendust Feb 26, 2024

Choose a reason for hiding this comment

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

Ephemeral actually is controlled by the access option, not the ttl. TTLs only make sense for pesistent znodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add an explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


boolean result;
try {
result = _pinotHelixResourceManager.createZKNode(path, znRecord, accessOption, ttl);
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel that we should check the range of ttl before making the call (or maybe check the range inside the createZKNode method)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There isn't a range of tll specified in the zk client. -1L means it's not set, any positive value means it's the TTL.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if the value is -2L? Will it treat it as not set?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With -2, node creation fails with an ISE, but I'll put a range check to make it clearer.

Copy link
Contributor

@zhtaoxiang zhtaoxiang left a comment

Choose a reason for hiding this comment

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

LGTM

})
public SuccessResponse createNode(
@ApiParam(value = "Zookeeper Path, must start with /", required = true) @QueryParam("path") String path,
@ApiParam(value = "Content") @QueryParam("data") @Nullable String data,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have both data and payload? Should it always be set with payload?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This signature was just copied from the zk/put API as I want to keep them similar. But we can def do with just payload.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess that keep payload only for now. We can add data if that is really useful, but removing it is much harder.

Response.Status.BAD_REQUEST);
}
//check if node already exists
if (_pinotHelixResourceManager.getZKStat(path) != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this check? Will createZKNode() throw exception when the node already exists? We should rely on ZK to handle conflict, or there will be potential race condition

Copy link
Contributor Author

@suddendust suddendust Feb 28, 2024

Choose a reason for hiding this comment

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

This was put in to explicitly detect if a path already exists. ZK does not return any error codes to detect it. Yes, a race condition exists, but that should be fine. Two cases:

  1. This check passes but node exists by the time ZK tries to create this node: It'll fail anyway transparently to the user (with an ISE instead of Bad Request though, which should be okay as the user would retry).
  2. This check fails but node is deleted by another process before user receives a response: This is possible even if we leave this check to ZK.

Copy link
Contributor

Choose a reason for hiding this comment

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

How about we reverse this check? When creation fails, we check if it exists. This should give better handling, and in most cases we can reduce one ZK access

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's a good idea. Flipped it.

})
public SuccessResponse createNode(
@ApiParam(value = "Zookeeper Path, must start with /", required = true) @QueryParam("path") String path,
@ApiParam(value = "Content") @QueryParam("data") @Nullable String data,
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess that keep payload only for now. We can add data if that is really useful, but removing it is much harder.

Response.Status.BAD_REQUEST);
}
//check if node already exists
if (_pinotHelixResourceManager.getZKStat(path) != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about we reverse this check? When creation fails, we check if it exists. This should give better handling, and in most cases we can reduce one ZK access

@Jackie-Jiang Jackie-Jiang merged commit 2249be3 into apache:master Feb 29, 2024
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants