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

Issue 1415 - DynamoDB store for table names & IDs #1418

Merged
merged 31 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
774934b
Set up basic structure for table ID store
patchwork01 Oct 9, 2023
0bd717c
Fail to create a table which already exists
patchwork01 Oct 10, 2023
91fd75e
Generate numeric table IDs
patchwork01 Oct 10, 2023
0923d72
Add methods to look up tables
patchwork01 Oct 10, 2023
e4f0c00
Test returned table order
patchwork01 Oct 10, 2023
d3afb1e
Reorder tests
patchwork01 Oct 10, 2023
eab21f7
Declare TableAlreadyExistsException on interface
patchwork01 Oct 10, 2023
0f28816
Handle lookup when no table is found
patchwork01 Oct 10, 2023
9fb1314
Organise table ID tests into nested classes
patchwork01 Oct 10, 2023
0daf37b
Test listing no tables
patchwork01 Oct 10, 2023
ad20d84
Set up DynamoDBTableIdStore
patchwork01 Oct 10, 2023
c23f760
Finish test for creating sleeper table
kr565370 Oct 10, 2023
23f4ab6
Test for failing to add duplicate table
kr565370 Oct 10, 2023
4b8ab79
Tests for looking up tables
kr565370 Oct 10, 2023
6f75a0b
Finish tests in DynamoDBTableIdStoreIT
kr565370 Oct 10, 2023
313cd5c
Generate a short table ID
patchwork01 Oct 10, 2023
4ce88ee
Rename transaction cancellation reason variable
patchwork01 Oct 10, 2023
14b95cc
Rename TableIdStore to TableIndex
patchwork01 Oct 10, 2023
53bdfce
Rename DynamoDB table index package
patchwork01 Oct 10, 2023
01bf39a
Add TableIndexStack to CDK
patchwork01 Oct 10, 2023
5031970
Regenerate properties templates
patchwork01 Oct 10, 2023
cc105b6
Merge remote-tracking branch 'origin/main' into 1415-table-name-store
patchwork01 Oct 11, 2023
980fabe
Fix merge
patchwork01 Oct 11, 2023
0e6fa25
Tweak naming to distinguish Dynamo tables from Sleeper tables
patchwork01 Oct 11, 2023
7e11268
Test generating different IDs from same generator
patchwork01 Oct 12, 2023
d13bedb
Rename tableId field to tableUniqueId
patchwork01 Oct 12, 2023
fd1112b
Merge remote-tracking branch 'origin/main' into 1415-table-name-store
patchwork01 Oct 12, 2023
702ee15
Fix merge
patchwork01 Oct 12, 2023
070ccd4
Build table ID DynamoDB item once
patchwork01 Oct 12, 2023
a335074
Use consistent reads in DynamoDBTableIndex.streamAllTables
patchwork01 Oct 12, 2023
1d3b1c6
Merge remote-tracking branch 'origin/main' into 1415-table-name-store
patchwork01 Oct 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions example/full/instance.properties
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ sleeper.metadata.dynamo.pointintimerecovery=false
# revision DynamoDB table.
sleeper.metadata.s3.dynamo.pointintimerecovery=false

# This specifies whether point in time recovery is enabled for the Sleeper table index. This is set on
# the DynamoDB tables.
sleeper.tables.index.dynamo.pointintimerecovery=false

# The timeout in minutes for when the table properties provider cache should be cleared, forcing table
# properties to be reloaded from S3.
sleeper.table.properties.provider.timeout.minutes=60
Expand Down
2 changes: 2 additions & 0 deletions java/cdk/src/main/java/sleeper/cdk/SleeperCdkApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import sleeper.cdk.stack.S3StateStoreStack;
import sleeper.cdk.stack.StateStoreStacks;
import sleeper.cdk.stack.TableDataStack;
import sleeper.cdk.stack.TableIndexStack;
import sleeper.cdk.stack.TableMetricsStack;
import sleeper.cdk.stack.TopicStack;
import sleeper.cdk.stack.VpcStack;
Expand Down Expand Up @@ -124,6 +125,7 @@ public void create() {

// Stack for tables
dataStack = new TableDataStack(this, "TableData", instanceProperties);
new TableIndexStack(this, "TableIndex", instanceProperties);
stateStoreStacks = new StateStoreStacks(
new DynamoDBStateStoreStack(this, "DynamoDBStateStore", instanceProperties),
new S3StateStoreStack(this, "S3StateStore", instanceProperties, dataStack));
Expand Down
81 changes: 81 additions & 0 deletions java/cdk/src/main/java/sleeper/cdk/stack/TableIndexStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2022-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sleeper.cdk.stack;

import software.amazon.awscdk.NestedStack;
import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.services.dynamodb.Attribute;
import software.amazon.awscdk.services.dynamodb.AttributeType;
import software.amazon.awscdk.services.dynamodb.BillingMode;
import software.amazon.awscdk.services.dynamodb.ITable;
import software.amazon.awscdk.services.dynamodb.Table;
import software.constructs.Construct;

import sleeper.configuration.properties.instance.InstanceProperties;
import sleeper.table.index.dynamodb.DynamoDBTableIndex;

import static sleeper.cdk.Utils.removalPolicy;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.TABLE_ID_INDEX_DYNAMO_TABLENAME;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.TABLE_NAME_INDEX_DYNAMO_TABLENAME;
import static sleeper.configuration.properties.instance.CommonProperty.ID;
import static sleeper.configuration.properties.instance.CommonProperty.TABLE_INDEX_DYNAMO_POINT_IN_TIME_RECOVERY;

public class TableIndexStack extends NestedStack {

private final ITable indexByNameDynamoTable;
private final ITable indexByIdDynamoTable;

public TableIndexStack(Construct scope, String id, InstanceProperties instanceProperties) {
super(scope, id);
String instanceId = instanceProperties.get(ID);
RemovalPolicy removalPolicy = removalPolicy(instanceProperties);

indexByNameDynamoTable = Table.Builder
.create(this, "IndexByNameDynamoDBTable")
.tableName(String.join("-", "sleeper", instanceId, "table-index-by-name"))
.removalPolicy(removalPolicy)
.billingMode(BillingMode.PAY_PER_REQUEST)
.partitionKey(Attribute.builder()
.name(DynamoDBTableIndex.TABLE_NAME_FIELD)
.type(AttributeType.STRING)
.build())
.pointInTimeRecovery(instanceProperties.getBoolean(TABLE_INDEX_DYNAMO_POINT_IN_TIME_RECOVERY))
.build();
instanceProperties.set(TABLE_NAME_INDEX_DYNAMO_TABLENAME, indexByNameDynamoTable.getTableName());

indexByIdDynamoTable = Table.Builder
.create(this, "IndexByIdDynamoDBTable")
.tableName(String.join("-", "sleeper", instanceId, "table-index-by-id"))
.removalPolicy(removalPolicy)
.billingMode(BillingMode.PAY_PER_REQUEST)
.partitionKey(Attribute.builder()
.name(DynamoDBTableIndex.TABLE_ID_FIELD)
.type(AttributeType.STRING)
.build())
.pointInTimeRecovery(instanceProperties.getBoolean(TABLE_INDEX_DYNAMO_POINT_IN_TIME_RECOVERY))
.build();
instanceProperties.set(TABLE_ID_INDEX_DYNAMO_TABLENAME, indexByIdDynamoTable.getTableName());
}

public ITable getIndexByNameDynamoTable() {
return indexByNameDynamoTable;
}

public ITable getIndexByIdDynamoTable() {
return indexByIdDynamoTable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ public interface CdkDefinedInstanceProperty extends InstanceProperty {
.propertyGroup(InstancePropertyGroup.COMMON)
.build();

// Tables
CdkDefinedInstanceProperty TABLE_NAME_INDEX_DYNAMO_TABLENAME = Index.propertyBuilder("sleeper.tables.name.index.dynamo.table")
.description("The name of the DynamoDB table indexing tables by their name.")
.propertyGroup(InstancePropertyGroup.COMMON)
.build();
CdkDefinedInstanceProperty TABLE_ID_INDEX_DYNAMO_TABLENAME = Index.propertyBuilder("sleeper.tables.id.index.dynamo.table")
.description("The name of the DynamoDB table indexing tables by their ID.")
.propertyGroup(InstancePropertyGroup.COMMON)
.build();

// DynamoDBStateStore
CdkDefinedInstanceProperty ACTIVE_FILEINFO_TABLENAME = Index.propertyBuilder("sleeper.metadata.dynamo.active.table")
.description("The name of the DynamoDB table holding metadata of active files in Sleeper tables.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ public interface CommonProperty {
.validationPredicate(Utils::isTrueOrFalse)
.propertyGroup(InstancePropertyGroup.COMMON)
.runCdkDeployWhenChanged(true).build();
UserDefinedInstanceProperty TABLE_INDEX_DYNAMO_POINT_IN_TIME_RECOVERY = Index.propertyBuilder("sleeper.tables.index.dynamo.pointintimerecovery")
.description("This specifies whether point in time recovery is enabled for the Sleeper table index. " +
"This is set on the DynamoDB tables.")
.defaultValue("false")
.validationPredicate(Utils::isTrueOrFalse)
.propertyGroup(InstancePropertyGroup.COMMON)
.runCdkDeployWhenChanged(true).build();

UserDefinedInstanceProperty TABLE_PROPERTIES_PROVIDER_TIMEOUT_IN_MINS = Index.propertyBuilder("sleeper.table.properties.provider.timeout.minutes")
.description("The timeout in minutes for when the table properties provider cache should be cleared, " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.PARTITION_TABLENAME;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.READY_FOR_GC_FILEINFO_TABLENAME;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.REVISION_TABLENAME;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.TABLE_ID_INDEX_DYNAMO_TABLENAME;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.TABLE_NAME_INDEX_DYNAMO_TABLENAME;
import static sleeper.configuration.properties.instance.CdkDefinedInstanceProperty.VERSION;
import static sleeper.configuration.properties.instance.CommonProperty.ACCOUNT;
import static sleeper.configuration.properties.instance.CommonProperty.ID;
Expand Down Expand Up @@ -80,6 +82,8 @@ public static InstanceProperties createTestInstanceProperties() {
instanceProperties.set(READY_FOR_GC_FILEINFO_TABLENAME, id + "-rfgcf");
instanceProperties.set(PARTITION_TABLENAME, id + "-p");
instanceProperties.set(REVISION_TABLENAME, id + "-rv");
instanceProperties.set(TABLE_NAME_INDEX_DYNAMO_TABLENAME, id + "-tni");
instanceProperties.set(TABLE_ID_INDEX_DYNAMO_TABLENAME, id + "-tii");
return instanceProperties;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2022-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sleeper.core.table;

public class TableAlreadyExistsException extends RuntimeException {

public TableAlreadyExistsException(String tableName) {
super("Table already exists: " + tableName);
}
}
67 changes: 67 additions & 0 deletions java/core/src/main/java/sleeper/core/table/TableId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2022-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sleeper.core.table;

import java.util.Objects;

public class TableId {
Copy link
Member

Choose a reason for hiding this comment

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

Is this the right name for this class, given it contains a field called tableId? It means that TableIndex has the method Optional<TableId> getTableById(String tableId); which looks wrong. Could call this class TableIdAndName? Alternatively rename the field tableId to tableUniqueId and then the method on TableIndex would be Optional<TableId> getTableByUniqueId(String tableUniqueId);?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've renamed the field, thanks.


private final String tableUniqueId;
private final String tableName;

private TableId(String tableUniqueId, String tableName) {
this.tableUniqueId = tableUniqueId;
this.tableName = tableName;
}

public static TableId uniqueIdAndName(String tableUniqueId, String tableName) {
return new TableId(tableUniqueId, tableName);
}

public String getTableName() {
return tableName;
}

public String getTableUniqueId() {
return tableUniqueId;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
TableId tableId1 = (TableId) object;
return Objects.equals(tableUniqueId, tableId1.tableUniqueId) && Objects.equals(tableName, tableId1.tableName);
}

@Override
public int hashCode() {
return Objects.hash(tableUniqueId, tableName);
}

@Override
public String toString() {
return "TableId{" +
"tableUniqueId='" + tableUniqueId + '\'' +
", tableName='" + tableName + '\'' +
'}';
}
}
45 changes: 45 additions & 0 deletions java/core/src/main/java/sleeper/core/table/TableIdGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2022-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sleeper.core.table;

import org.apache.commons.codec.binary.Hex;

import java.security.SecureRandom;
import java.util.Random;

public class TableIdGenerator {

private final Random random;

public TableIdGenerator() {
random = new SecureRandom();
}

private TableIdGenerator(int seed) {
random = new Random(seed);
}

public static TableIdGenerator fromRandomSeed(int seed) {
return new TableIdGenerator(seed);
}

public String generateString() {
byte[] bytes = new byte[4];
random.nextBytes(bytes);
return Hex.encodeHexString(bytes);
}
}
30 changes: 30 additions & 0 deletions java/core/src/main/java/sleeper/core/table/TableIndex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2022-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sleeper.core.table;

import java.util.Optional;
import java.util.stream.Stream;

public interface TableIndex {
TableId createTable(String tableName) throws TableAlreadyExistsException;

Stream<TableId> streamAllTables();

Optional<TableId> getTableByName(String tableName);

Optional<TableId> getTableByUniqueId(String tableUniqueId);
}
57 changes: 57 additions & 0 deletions java/core/src/test/java/sleeper/core/table/InMemoryTableIndex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2022-2023 Crown Copyright
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sleeper.core.table;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Stream;

public class InMemoryTableIndex implements TableIndex {

private final Map<String, TableId> idByName = new TreeMap<>();
private final Map<String, TableId> nameById = new HashMap<>();
private int nextIdNumber = 1;

@Override
public TableId createTable(String tableName) {
if (idByName.containsKey(tableName)) {
throw new TableAlreadyExistsException(tableName);
}
TableId id = TableId.uniqueIdAndName("table-" + nextIdNumber, tableName);
nextIdNumber++;
idByName.put(tableName, id);
nameById.put(id.getTableUniqueId(), id);
return id;
}

@Override
public Stream<TableId> streamAllTables() {
return idByName.values().stream();
}

@Override
public Optional<TableId> getTableByName(String tableName) {
return Optional.ofNullable(idByName.get(tableName));
}

@Override
public Optional<TableId> getTableByUniqueId(String tableUniqueId) {
return Optional.ofNullable(nameById.get(tableUniqueId));
}
}
Loading