Skip to content

Commit c783de9

Browse files
adam-christian-softwaresnazydimas-b
authored
feat(idgen): Start Implementation of NoSQL with the ID Generation Framework (#2131)
Create an ID Generation Framework. Related to #650 & #844 Co-authored-by: Robert Stupp <snazy@snazy.de> Co-authored-by: Dmitri Bourlatchkov <dmitri.bourlatchkov@gmail.com>
1 parent 6137e42 commit c783de9

File tree

27 files changed

+3165
-0
lines changed

27 files changed

+3165
-0
lines changed

gradle/libs.versions.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ hive = "3.1.3"
2424
iceberg = "1.9.2" # Ensure to update the iceberg version in regtests to keep regtests up-to-date
2525
quarkus = "3.25.4"
2626
immutables = "2.11.3"
27+
jmh = "1.37"
2728
picocli = "4.7.7"
2829
scala212 = "2.12.19"
2930
spark35 = "3.5.6"
@@ -76,6 +77,9 @@ jandex = { module = "io.smallrye.jandex:jandex", version ="3.4.0" }
7677
javax-servlet-api = { module = "javax.servlet:javax.servlet-api", version = "4.0.1" }
7778
junit-bom = { module = "org.junit:junit-bom", version = "5.13.4" }
7879
keycloak-admin-client = { module = "org.keycloak:keycloak-admin-client", version = "26.0.6" }
80+
jcstress-core = { module = "org.openjdk.jcstress:jcstress-core", version = "0.16" }
81+
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
82+
jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
7983
logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.5.18" }
8084
micrometer-bom = { module = "io.micrometer:micrometer-bom", version = "1.15.3" }
8185
microprofile-fault-tolerance-api = { module = "org.eclipse.microprofile.fault-tolerance:microprofile-fault-tolerance-api", version = "4.1.2" }
@@ -102,6 +106,8 @@ testcontainers-keycloak = { module = "com.github.dasniko:testcontainers-keycloak
102106
threeten-extra = { module = "org.threeten:threeten-extra", version = "1.8.0" }
103107

104108
[plugins]
109+
jcstress = { id = "io.github.reyerizo.gradle.jcstress", version = "0.8.15" }
110+
jmh = { id = "me.champeau.jmh", version = "0.7.3" }
105111
openapi-generator = { id = "org.openapi.generator", version = "7.12.0" }
106112
quarkus = { id = "io.quarkus", version.ref = "quarkus" }
107113
rat = { id = "org.nosphere.apache.rat", version = "0.8.1" }

gradle/projects.main.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,9 @@ polaris-extensions-federation-hive=extensions/federation/hive
4848
polaris-config-docs-annotations=tools/config-docs/annotations
4949
polaris-config-docs-generator=tools/config-docs/generator
5050
polaris-config-docs-site=tools/config-docs/site
51+
52+
# id generation
53+
polaris-idgen-api=persistence/nosql/idgen/api
54+
polaris-idgen-impl=persistence/nosql/idgen/impl
55+
polaris-idgen-mocks=persistence/nosql/idgen/mocks
56+
polaris-idgen-spi=persistence/nosql/idgen/spi

persistence/nosql/idgen/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
# Unique ID generation framework and monotonic clock
21+
22+
Provides a framework and implementations for unique ID generation, including a monotonically increasing timestamp/clock
23+
source.
24+
25+
Provides a
26+
[Snowflake-IDs](https://medium.com/@jitenderkmr/demystifying-snowflake-ids-a-unique-identifier-in-distributed-computing-72796a827c9d)
27+
implementation.
28+
29+
Consuming production should primarily leverage the `IdGenerator` and `MonotonicClock` interfaces.
30+
31+
## Snowflake ID source
32+
33+
The Snowflake ID source is configurable for each backend instance, but cannot be modified for an existing backend
34+
instance to prevent ID conflicts.
35+
36+
The epoch of these timestamps is 2025-03-01-00:00:00.0 GMT. Timestamps occupy 41 bits at
37+
millisecond precision, which lasts for about 69 years. Node-IDs are 10 bits, which allows 1024 concurrently active
38+
"JVMs running Polaris". 12 bits are used by the sequence number, which then allows each node to generate 4096 IDs per
39+
millisecond. One bit is reserved for future use.
40+
41+
Node IDs are leased by every "JVM running Polaris" for a period of time. The ID generator implementation guarantees
42+
that no IDs will be generated for a timestamp that exceeds the "lease time". Leases can be extended. The implementation
43+
leverages atomic database operations (CAS) for the lease implementation.
44+
45+
ID generators must not use timestamps before or after the lease period nor must they re-use an older timestamp. This
46+
requirement is satisfied using a monotonic clock implementation.
47+
48+
## Code structure
49+
50+
The code is structured into multiple modules. Consuming code should almost always pull in only the API module.
51+
52+
* `polaris-idgen-api` provides the necessary Java interfaces and immutable types.
53+
* `polaris-idgen-impl` provides the storage agnostic implementation.
54+
* `polaris-idgen-mocks` provides mocks for testing.
55+
* `polaris-idgen-spi` provides the necessary interfaces to construct ID generators.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 ID generation API"
26+
27+
dependencies {
28+
compileOnly(libs.jakarta.annotation.api)
29+
compileOnly(libs.jakarta.validation.api)
30+
compileOnly(libs.jakarta.inject.api)
31+
compileOnly(libs.jakarta.enterprise.cdi.api)
32+
33+
compileOnly(libs.smallrye.config.core)
34+
compileOnly(platform(libs.quarkus.bom))
35+
compileOnly("io.quarkus:quarkus-core")
36+
37+
compileOnly(project(":polaris-immutables"))
38+
annotationProcessor(project(":polaris-immutables", configuration = "processor"))
39+
40+
implementation(platform(libs.jackson.bom))
41+
implementation("com.fasterxml.jackson.core:jackson-databind")
42+
}
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.ids.api;
20+
21+
/** The primary interface for generating a contention-free ID. */
22+
public interface IdGenerator {
23+
/** Generate a new, unique ID. */
24+
long generateId();
25+
26+
/** Generate the system ID for a node, solely used for node management. */
27+
long systemIdForNode(int nodeId);
28+
29+
default String describeId(long id) {
30+
return Long.toString(id);
31+
}
32+
33+
IdGenerator NONE =
34+
new IdGenerator() {
35+
@Override
36+
public long generateId() {
37+
throw new UnsupportedOperationException("NONE IdGenerator cannot generate IDs.");
38+
}
39+
40+
@Override
41+
public long systemIdForNode(int nodeId) {
42+
throw new UnsupportedOperationException("NONE IdGenerator cannot generate IDs.");
43+
}
44+
};
45+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.ids.api;
20+
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
22+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
23+
import io.smallrye.config.WithDefault;
24+
import java.util.Map;
25+
import org.apache.polaris.immutables.PolarisImmutable;
26+
import org.immutables.value.Value;
27+
28+
@PolarisImmutable
29+
@JsonSerialize(as = ImmutableIdGeneratorSpec.class)
30+
@JsonDeserialize(as = ImmutableIdGeneratorSpec.class)
31+
public interface IdGeneratorSpec {
32+
@WithDefault("snowflake")
33+
String type();
34+
35+
Map<String, String> params();
36+
37+
@PolarisImmutable
38+
interface BuildableIdGeneratorSpec extends IdGeneratorSpec {
39+
static ImmutableBuildableIdGeneratorSpec.Builder builder() {
40+
return ImmutableBuildableIdGeneratorSpec.builder();
41+
}
42+
43+
@Override
44+
Map<String, String> params();
45+
46+
@Override
47+
@Value.Default
48+
default String type() {
49+
return "snowflake";
50+
}
51+
}
52+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.ids.api;
20+
21+
import java.time.Instant;
22+
23+
/**
24+
* Provides a clock providing the current time in milliseconds, microseconds and instant since
25+
* 1970-01-01-00:00:00.000. The returned timestamp values increase monotonically.
26+
*
27+
* <p>The functions provide nanosecond/microsecond/millisecond precision, but not necessarily the
28+
* same resolution (how frequently the value changes) - no guarantees are made.
29+
*
30+
* <p>Implementation <em>may</em> adjust to wall clocks advancing faster than the real time. If and
31+
* how exactly depends on the implementation, as long as none of the time values available via this
32+
* interface "goes backwards".
33+
*
34+
* <p>Implementer notes: {@link System#nanoTime() System.nanoTime()} does not guarantee that the
35+
* values will be monotonically increasing when invocations happen from different
36+
* CPUs/cores/threads.
37+
*
38+
* <p>A default implementation of {@link MonotonicClock} can be injected as an application scoped
39+
* bean in CDI.
40+
*/
41+
public interface MonotonicClock extends AutoCloseable {
42+
/**
43+
* Current timestamp as microseconds since epoch, can be used as a monotonically increasing wall
44+
* clock.
45+
*/
46+
long currentTimeMicros();
47+
48+
/**
49+
* Current timestamp as milliseconds since epoch, can be used as a monotonically increasing wall
50+
* clock.
51+
*/
52+
long currentTimeMillis();
53+
54+
/**
55+
* Current instant with nanosecond precision, can be used as a monotonically increasing wall
56+
* clock.
57+
*/
58+
Instant currentInstant();
59+
60+
/** Monotonically increasing timestamp with nanosecond precision, not related to wall clock. */
61+
long nanoTime();
62+
63+
void sleepMillis(long millis);
64+
65+
@Override
66+
void close();
67+
68+
void waitUntilTimeMillisAdvanced();
69+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.ids.api;
20+
21+
import jakarta.annotation.Nonnull;
22+
import java.time.Instant;
23+
import java.util.UUID;
24+
25+
public interface SnowflakeIdGenerator extends IdGenerator {
26+
/** Offset of the snowflake ID generator since the 1970-01-01T00:00:00Z epoch instant. */
27+
Instant ID_EPOCH = Instant.parse("2025-03-01T00:00:00Z");
28+
29+
/**
30+
* Offset of the snowflake ID generator in milliseconds since the 1970-01-01T00:00:00Z epoch
31+
* instant.
32+
*/
33+
long ID_EPOCH_MILLIS = ID_EPOCH.toEpochMilli();
34+
35+
int DEFAULT_NODE_ID_BITS = 10;
36+
int DEFAULT_TIMESTAMP_BITS = 41;
37+
int DEFAULT_SEQUENCE_BITS = 12;
38+
39+
long constructId(long timestamp, long sequence, long node);
40+
41+
long timestampFromId(long id);
42+
43+
long timestampUtcFromId(long id);
44+
45+
long sequenceFromId(long id);
46+
47+
long nodeFromId(long id);
48+
49+
UUID idToTimeUuid(long id);
50+
51+
String idToString(long id);
52+
53+
long timeUuidToId(@Nonnull UUID uuid);
54+
55+
int timestampBits();
56+
57+
int sequenceBits();
58+
59+
int nodeIdBits();
60+
}

0 commit comments

Comments
 (0)