Skip to content

Commit 6f28c70

Browse files
committed
Unique ID generation
Provides a framework and implementations for unique ID generation, including a monotonically increasing timestamp/clock source. Includes a [Snowflake-IDs](https://medium.com/@jitenderkmr/demystifying-snowflake-ids-a-unique-identifier-in-distributed-computing-72796a827c9d) implementation.
1 parent ba72997 commit 6f28c70

File tree

25 files changed

+3074
-0
lines changed

25 files changed

+3074
-0
lines changed

bom/build.gradle.kts

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ dependencies {
3939
api(project(":polaris-async-java"))
4040
api(project(":polaris-async-vertx"))
4141

42+
api(project(":polaris-idgen-api"))
43+
api(project(":polaris-idgen-impl"))
44+
api(project(":polaris-idgen-spi"))
45+
4246
api(project(":polaris-config-docs-annotations"))
4347
api(project(":polaris-config-docs-generator"))
4448

gradle/projects.main.properties

+4
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,7 @@ polaris-config-docs-site=tools/config-docs/site
4949
polaris-async-api=nosql/async/api
5050
polaris-async-java=nosql/async/java
5151
polaris-async-vertx=nosql/async/vertx
52+
# id generation
53+
polaris-idgen-api=nosql/idgen/api
54+
polaris-idgen-impl=nosql/idgen/impl
55+
polaris-idgen-spi=nosql/idgen/spi

nosql/idgen/README.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 code must only leverage the `IdGenerator` interface.
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-spi` provides the necessary interfaces to construct ID generators.

nosql/idgen/api/build.gradle.kts

+42
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+
alias(libs.plugins.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+
}
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+
package org.apache.polaris.ids.api;
20+
21+
public interface IdGenerator {
22+
/** Generate a new, unique ID. */
23+
long generateId();
24+
25+
/** Generate the system ID for a node, solely used by/for node management purposes. */
26+
long systemIdForNode(int nodeId);
27+
28+
default String describeId(long id) {
29+
return Long.toString(id);
30+
}
31+
32+
IdGenerator NONE =
33+
new IdGenerator() {
34+
@Override
35+
public long generateId() {
36+
throw new UnsupportedOperationException("NONE IdGenerator");
37+
}
38+
39+
@Override
40+
public long systemIdForNode(int nodeId) {
41+
throw new UnsupportedOperationException("NONE IdGenerator");
42+
}
43+
};
44+
}
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+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.time.ZoneId;
24+
import java.util.UUID;
25+
26+
public interface SnowflakeIdGenerator extends IdGenerator {
27+
/** Offset of the snowflake ID generator since the 1970-01-01T00:00:00Z epoch instant. */
28+
Instant EPOCH_OFFSET =
29+
Instant.EPOCH.atZone(ZoneId.of("GMT")).withYear(2025).withMonth(3).toInstant();
30+
31+
/**
32+
* Offset of the snowflake ID generator in milliseconds since the 1970-01-01T00:00:00Z epoch
33+
* instant.
34+
*/
35+
long EPOCH_OFFSET_MILLIS = EPOCH_OFFSET.toEpochMilli();
36+
37+
int DEFAULT_NODE_ID_BITS = 10;
38+
int DEFAULT_TIMESTAMP_BITS = 41;
39+
int DEFAULT_SEQUENCE_BITS = 12;
40+
41+
long constructId(long timestamp, long sequence, long node);
42+
43+
long timestampFromId(long id);
44+
45+
long timestampUtcFromId(long id);
46+
47+
long sequenceFromId(long id);
48+
49+
long nodeFromId(long id);
50+
51+
UUID idToTimeUuid(long id);
52+
53+
String idToString(long id);
54+
55+
long timeUuidToId(@Nonnull UUID uuid);
56+
57+
int timestampBits();
58+
59+
int sequenceBits();
60+
61+
int nodeIdBits();
62+
}

0 commit comments

Comments
 (0)