Skip to content

Commit 6e21df6

Browse files
committed
NoSQL: Node IDs - API, SPI + general implementation
This PR provides a mechanism to assign a Polaris-cluster-wide unique node-ID to each Polaris instance, which is then used when generating Polaris-cluster-wide unique Snowflake-IDs. The change is fundamental for the NoSQL work, but also demanded for the existing relational JDBC persistence. Does not include any persistence specific implementation.
1 parent abf1e35 commit 6e21df6

File tree

23 files changed

+1897
-0
lines changed

23 files changed

+1897
-0
lines changed

bom/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ dependencies {
4444
api(project(":polaris-idgen-impl"))
4545
api(project(":polaris-idgen-spi"))
4646

47+
api(project(":polaris-nodes-api"))
48+
api(project(":polaris-nodes-impl"))
49+
api(project(":polaris-nodes-spi"))
50+
4751
api(project(":polaris-config-docs-annotations"))
4852
api(project(":polaris-config-docs-generator"))
4953

gradle/projects.main.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,9 @@ polaris-idgen-api=persistence/nosql/idgen/api
5757
polaris-idgen-impl=persistence/nosql/idgen/impl
5858
polaris-idgen-mocks=persistence/nosql/idgen/mocks
5959
polaris-idgen-spi=persistence/nosql/idgen/spi
60+
# nodes
61+
polaris-nodes-api=persistence/nosql/nodes/api
62+
polaris-nodes-impl=persistence/nosql/nodes/impl
63+
polaris-nodes-spi=persistence/nosql/nodes/spi
6064
# persistence / database agnostic
6165
polaris-persistence-nosql-varint=persistence/nosql/persistence/varint

persistence/nosql/nodes/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
# Uniquely identify running Polaris nodes
21+
22+
Some ID generation mechanisms,
23+
like [Snowflake-IDs](https://medium.com/@jitenderkmr/demystifying-snowflake-ids-a-unique-identifier-in-distributed-computing-72796a827c9d),
24+
require unique integer IDs for each running node. This framework provides a mechanism to assign each running node a
25+
unique integer ID.
26+
27+
## Code structure
28+
29+
The code is structured into multiple modules. Consuming code should almost always pull in only the API module.
30+
31+
* `polaris-nodes-api` provides the necessary Java interfaces and immutable types.
32+
* `polaris-nodes-impl` provides the storage agnostic implementation.
33+
* `polaris-nodes-spi` provides the necessary interfaces to provide a storage specific implementation.
34+
* `polaris-nodes-store-nosql` provides the storage implementation based on `polaris-persistence-nosql-api`.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
plugins {
21+
id("org.kordamp.gradle.jandex")
22+
id("polaris-server")
23+
}
24+
25+
description = "Polaris nodes API, no concrete implementations"
26+
27+
dependencies {
28+
implementation(project(":polaris-idgen-api"))
29+
30+
compileOnly(project(":polaris-immutables"))
31+
annotationProcessor(project(":polaris-immutables", configuration = "processor"))
32+
33+
implementation(platform(libs.jackson.bom))
34+
implementation("com.fasterxml.jackson.core:jackson-databind")
35+
36+
compileOnly(libs.jakarta.annotation.api)
37+
compileOnly(libs.jakarta.validation.api)
38+
compileOnly(libs.jakarta.inject.api)
39+
compileOnly(libs.jakarta.enterprise.cdi.api)
40+
41+
compileOnly(libs.smallrye.config.core)
42+
compileOnly(platform(libs.quarkus.bom))
43+
compileOnly("io.quarkus:quarkus-core")
44+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.nodes.api;
20+
21+
import jakarta.annotation.Nullable;
22+
import java.time.Instant;
23+
import org.apache.polaris.immutables.PolarisImmutable;
24+
25+
/** Represents the local node's ID and informative, mutable state information. */
26+
@PolarisImmutable
27+
public interface Node {
28+
/**
29+
* Returns the ID of this node.
30+
*
31+
* @return ID of this node
32+
* @throws IllegalStateException if the lease is no longer valid, for example, expired before it
33+
* being renewed
34+
*/
35+
int id();
36+
37+
default boolean valid(long nowInMillis) {
38+
return nowInMillis < expirationTimestamp().toEpochMilli();
39+
}
40+
41+
Instant leaseTimestamp();
42+
43+
@Nullable
44+
Instant renewLeaseTimestamp();
45+
46+
/** Timestamp since which this node's lease is no longer valid. */
47+
Instant expirationTimestamp();
48+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.nodes.api;
20+
21+
import jakarta.annotation.Nullable;
22+
23+
public interface NodeLease {
24+
/**
25+
* Returns the {@link Node} representation for this lease if the lease has not been released or
26+
* {@code null}.
27+
*/
28+
@Nullable
29+
Node node();
30+
31+
/**
32+
* Permanently release the lease. Does nothing, if already released. Throws if persisting the
33+
* released state fails.
34+
*/
35+
void release();
36+
37+
/**
38+
* Force a lease renewal, generally not recommended nor necessary. Throws, if the lease is already
39+
* released.
40+
*/
41+
void renew();
42+
43+
/** Returns the node ID if the lease is active/valid or {@code -1}. */
44+
int nodeIdIfValid();
45+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.nodes.api;
20+
21+
import jakarta.annotation.Nonnull;
22+
import jakarta.enterprise.context.ApplicationScoped;
23+
import java.util.Optional;
24+
import org.apache.polaris.ids.api.IdGenerator;
25+
import org.apache.polaris.ids.api.SnowflakeIdGenerator;
26+
27+
/**
28+
* API to lease node IDs, primarily to generate {@linkplain SnowflakeIdGenerator snowflake IDs}.
29+
*
30+
* <p>The default configuration for the snowflake IDs allows generation of 4096 IDs per millisecond
31+
* (12 sequence bits), which should be more than enough. As a consequence, it is very likely enough
32+
* to have only one ID generator per JVM, across all realms and catalogs.
33+
*
34+
* <p>Implementation is provided as an {@link ApplicationScoped @ApplicationScoped} bean
35+
*/
36+
public interface NodeManagement extends AutoCloseable {
37+
/**
38+
* Build a <em>new</em> and <em>independent</em> ID generator instance of a {@linkplain #lease()
39+
* leased node} using the given clock.
40+
*
41+
* <p>This function must only be called from {@link ApplicationScoped @ApplicationScoped} CDI
42+
* producers providing the same {@link IdGenerator} for the lifetime of the given {@link Node},
43+
* aka at most once for a {@link Node} instance.
44+
*/
45+
IdGenerator buildIdGenerator(@Nonnull NodeLease leasedNode);
46+
47+
/** The maximum number of concurrently leased nodes that are supported. */
48+
int maxNumberOfNodes();
49+
50+
/** Retrieve information about a specific node. */
51+
Optional<Node> getNodeInfo(int nodeId);
52+
53+
/** Get the persistence ID for a node by its ID. */
54+
long systemIdForNode(int nodeId);
55+
56+
/**
57+
* Lease a node.
58+
*
59+
* <p>The implementation takes care of periodically renewing the lease. It is not necessary to
60+
* explicitly call {@link NodeLease#renew()}.
61+
*
62+
* <p>It is possible that the {@linkplain NodeLease#nodeIdIfValid() ID of the leased node} changes
63+
* over time.
64+
*
65+
* <p>Each invocation returns a new, independent lease.
66+
*
67+
* @return the leased node
68+
* @throws IllegalStateException if no node ID could be leased
69+
*/
70+
@Nonnull
71+
NodeLease lease();
72+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.nodes.api;
20+
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
22+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
23+
import io.smallrye.config.ConfigMapping;
24+
import io.smallrye.config.WithDefault;
25+
import java.time.Duration;
26+
import org.apache.polaris.ids.api.IdGenerator;
27+
import org.apache.polaris.ids.api.IdGeneratorSpec;
28+
import org.apache.polaris.ids.api.SnowflakeIdGenerator;
29+
import org.apache.polaris.immutables.PolarisImmutable;
30+
import org.immutables.value.Value;
31+
32+
/** Node management configuration. */
33+
@ConfigMapping(prefix = "polaris.node")
34+
@JsonSerialize(as = ImmutableBuildableNodeManagementConfig.class)
35+
@JsonDeserialize(as = ImmutableBuildableNodeManagementConfig.class)
36+
public interface NodeManagementConfig {
37+
/** Duration of a node-lease. */
38+
@WithDefault(DEFAULT_LEASE_DURATION)
39+
Duration leaseDuration();
40+
41+
/** Time window before the end of a node lease when the lease will be renewed. */
42+
@WithDefault(DEFAULT_RENEWAL_PERIOD)
43+
Duration renewalPeriod();
44+
45+
/**
46+
* Maximum number of concurrently active Polaris nodes. Do not change this value or the ID
47+
* generator spec, it is a rather internal property. See ID generator spec below.
48+
*/
49+
@WithDefault(DEFAULT_NUM_NODES)
50+
int numNodes();
51+
52+
/**
53+
* Configuration needed to build an {@linkplain IdGenerator ID generator}. This configuration
54+
* cannot be changed after one Polaris node has been successfully started. This specification will
55+
* be ignored if a persisted one exists, but a warning shall be logged if both are different.
56+
*/
57+
IdGeneratorSpec idGeneratorSpec();
58+
59+
String DEFAULT_LEASE_DURATION = "PT1H";
60+
String DEFAULT_RENEWAL_PERIOD = "PT15M";
61+
String DEFAULT_NUM_NODES = "" + (1 << SnowflakeIdGenerator.DEFAULT_NODE_ID_BITS);
62+
63+
@PolarisImmutable
64+
interface BuildableNodeManagementConfig extends NodeManagementConfig {
65+
66+
static ImmutableBuildableNodeManagementConfig.Builder builder() {
67+
return ImmutableBuildableNodeManagementConfig.builder();
68+
}
69+
70+
@Override
71+
@Value.Default
72+
default Duration leaseDuration() {
73+
return Duration.parse(DEFAULT_LEASE_DURATION);
74+
}
75+
76+
@Override
77+
@Value.Default
78+
default Duration renewalPeriod() {
79+
return Duration.parse(DEFAULT_RENEWAL_PERIOD);
80+
}
81+
82+
@Override
83+
default int numNodes() {
84+
return 1 << SnowflakeIdGenerator.DEFAULT_NODE_ID_BITS;
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)