Skip to content
This repository was archived by the owner on Feb 12, 2022. It is now read-only.

Create and delete virtual multitenant tables#648

Open
hhkwong wants to merge 4 commits intomasterfrom
explicitly-define-mt-tables
Open

Create and delete virtual multitenant tables#648
hhkwong wants to merge 4 commits intomasterfrom
explicitly-define-mt-tables

Conversation

@hhkwong
Copy link
Copy Markdown
Collaborator

@hhkwong hhkwong commented Nov 15, 2019

This is a first version. Notes:

  • The MtAmazonDynamoDb interface now has a new method, createMultitenantTable. It's supported only in MtAmazonDynamoDbBySharedTable.
  • To use this in MtAmazonDynamoDbBySharedTable, a top-level context must have been defined when constructing the client, and the context when calling createMultitenantTable must match that top-level context. Similarly, when deleting a multitenant table, the context must match the top-level context. (And the same thing when we support cross-tenant scans.)
  • createMultitenantTable() results in a new physical table being created on the fly. The fact that we now not only have static physical tables makes it harder to know what the set of physical tables is at any given time. What I have right now is to assume each shared table client has a unique table prefix, and determine the set of physical tables based on prefix. Not sure if this is a good idea.
  • Instead of storing TableDescription jsons in the table description repo, we now store MtTableDescription jsons, where MtTableDescription extends TableDescription and has an extra flag isMultitenant (and more things in the future). This should make the new logic still be able to parse existing table description repo records.
  • Backup of table description repo records still needs work (each record stored in S3 now needs the extra information in MtTableDescription) -- should be straightforward but I just haven't done it because it's in Kotlin which I'm not familiar with...
  • Thinking about how to have broad test coverage for MT tables, maybe similar to having a new MT strategy in test ArgumentBuilder

* @return true if the given table name is a physical table associated with this instance, false otherwise.
*/
protected boolean isMtTable(String tableName) {
protected boolean isPhysicalTable(String tableName) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

To make this clear that by this we mean a physical table associated with the instance, as opposed to a virtual multitenant table.

* Takes a virtual table description and returns a CreateTableRequest for the corresponding physical table to be
* created dynamically.
*/
CreateTableRequest getDynamicPhysicalTable(DynamoTableDescription virtualTableDescription);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This tells us how to create a new physical table on the fly when there's a request to create a new multitenant table

/**
* Returns whether the given table name is of a physical table (static or dynamic) belonging to this factory.
*/
boolean isPhysicalTable(String tableName);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is based on table prefix, with the assumption that each mt-dynamo client uses a unique table prefix. Before, we know the exact set of physical tables because we have only static tables. But now we have tables created dynamically / on the fly, so it becomes tricky for each server to keep up-to-date with the current set of physical tables.

setDefaults();
withName("SharedTableBuilder");
validate();
PhysicalTableManager physicalTableManager = new PhysicalTableManager(amazonDynamoDb, pollIntervalSeconds);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Abstracted out physical table create/describe logic from TableMappingFactory into the new PhysicalTableManager

CreateTableRequestFactory createTableRequestFactory = new SharedTableCreateTableRequestFactory(
partitioningStrategy.getTablePrimaryKeyMapper(), createTableRequests, getTablePrefix());
partitioningStrategy.getTablePrimaryKeyMapper(), createTableRequests,
partitioningStrategy::toCompatiblePhysicalPrimaryKey, tablePrefix,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

TablePartitioningStrategy now has a new method toCompatiblePhysicalPrimaryKey() that converts a given virtual PK into a physical PK, so that when there's a request to create a new multitenant table, we know how to convert the virtual table CreateTableRequest into a physical one.

}

@Override
public CreateTableRequest getDynamicPhysicalTable(DynamoTableDescription virtualTable) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is called when we want to create a new multitenant table, which means we create a new physical table dynamically. To do this, we convert each table or secondary index PK of the virtual table into a physical PK, according to how the TablePartitioningStrategy defines the conversion.

* Creates the table mapping for a new virtual table, creating the physical table if it doesn't exist.
*
*/
TableMapping createNewTableMapping(CreateTableRequest virtualCreateTableRequest, boolean isMultitenant) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Now in addition to the existing getTableMapping(), we also have createNewTableMapping(). createNewTableMapping() is called when MtAmazonDynamoDbBySharedTable.createTable() or createMultitenantTable() is called, and creates the corresponding physical table if needed. getTableMapping() used to create the physical table if it doesn't exist, but now it doesn't -- it only calls describe.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Upon createTable, we used to only validate the virtual and physical table definitions, without creating the physical table if it doesn't exist. Now we make sure the physical table is created right away, instead of possibly waiting until the first DML.

private CreateTableRequest lookupPhysicalTable(VirtualDynamoTableDescription virtualTable) {
// if this is a multitenant table, then create a new physical table dedicated to this purpose.
// otherwise, find the corresponding static physical table.
return virtualTable.isMultitenant()
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If this is a multitenant table, then create a dedicated physical table for it on the fly. Thought about making this not hard-coded here, and letting CreateTableRequestFactory decide whether to use one of the static tables or create a new physical table, but if we make it more flexible, then deleteTable becomes trickier (if the MT table doesn't necessarily have its own physical table, then we can't just delete the physical table).

private String name;
private AmazonDynamoDB amazonDynamoDb;
private MtAmazonDynamoDbContextProvider mtContext;
private Optional<String> topLevelContext = empty();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

A shared table client must define a top-level context to be able to create multitenant tables. Creating and deleting multitenant tables can be done only when the context matches the top-level context.

@Override
protected boolean isMtTable(String tableName) {
return mtTables.containsKey(tableName) && !tableName.startsWith(backupTablePrefix);
protected boolean isPhysicalTable(String tableName) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Again, this is now based on table prefix, instead of looking at the set of static physical tables, because now we have physical tables created on the fly. Need to exclude the table description repo table because that also has the same prefix but isn't a data table.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this filter going to match the *.Leases tables?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh yeah it is going to match that too... Maybe it'd be better to make dynamically created tables have an extra suffix to the prefix, e.g., [clientTablePrefix].d, or something like that, and look for only the static data tables and dynamic data tables found in this way.

What are the rules for mt-dynamo table names and prefixes? Are they defined somewhere?

this.physicalTableManager = physicalTableManager;
this.deleteTableAsync = deleteTableAsync;
this.truncateOnDeleteTable = truncateOnDeleteTable;
this.mtTables = tableMappingFactory.getCreateTableRequestFactory().getPhysicalTables().stream()
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think we don't need this because PhysicalTableManager already has a cache of the physical table descriptions that this server knows about (and that cache used to be in TableMappingFactory)

import org.slf4j.LoggerFactory;

public class PhysicalTableManager {

Copy link
Copy Markdown
Collaborator Author

@hhkwong hhkwong Nov 16, 2019

Choose a reason for hiding this comment

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

Moved the create & describe table logic plus the physical table description cache from TableMappingFactory to here. Also now have a delete table method, called when we delete a multitenant table.

}

private CreateTableResult createTable(CreateTableRequest createTableRequest, boolean isMultitenant) {
TableMapping tableMapping = tableMappingFactory.createNewTableMapping(createTableRequest, isMultitenant);
Copy link
Copy Markdown
Collaborator Author

@hhkwong hhkwong Nov 16, 2019

Choose a reason for hiding this comment

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

TableMappingFactory.createNewTableMapping() is a new method on TableMappingFactory. It validates the request and creates the physical table if it doesn't already exist.
(We used to create the physical table lazily upon the first DML. TableMappingFactory.getTableMapping() used to create the physical table if it doesn't exist, but it no longer does this.)

deletedCount += scanResult.getItems().size();
if (scanResult.getLastEvaluatedKey() == null) {
break;
if (tableDescription.isMultitenant()) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If this is a multitenant table, then drop the corresponding physical table

this.isMultitenant = isMultitenant;
}

public VirtualDynamoTableDescriptionImpl(MtTableDescription tableDescription) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

MtTableDescription extends TableDescription, with an extra flag isMultitenant (and more properties in the future), and that's what we store in the table description repo now. This should make us still be able to parse existing TableDescription records.

}

private TableDescription getTableDescriptionNoCache(String tableName) {
private MtTableDescription getTableDescriptionNoCache(String tableName) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is where we first try to get a description for the given virtual table name at the current context, and if it doesn't exist, see if there's virtual multitenant table with this name at the top-level context.


@Override
public CreateTableRequest getDynamicPhysicalTable(DynamoTableDescription virtualTable) {
String physicalTableName = prefix(tablePrefix, virtualTable.getTableName());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need/want to enforce namespacing between the static physical tables configured on the adapter and the dynamic physical tables created for virtual mt tables? If not, what would happen if we create a virtual table with the same name as a static physical table?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes I think that's a good idea. Perhaps dynamic physical tables can have a different prefix, which would make isPhysicalTables() more robust / make it less likely that we accidentally pick up other tables like .leases tables.


private final String name;

private final Optional<String> topLevelContext;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is the purpose of this context?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is so that we know whether we're allowed to create/delete/update a multitenant table or to do a cross-tenant scan on a multitenant table. For example this would be the Core cloud context.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this different from "no context" (as used in master for cross-tenant scans or change streaming)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes this is different from "no context". It seems like when for example the Core cloud does DDL on a virtual multitenant table, or does a cross-tenant scan on a virtual mt table, there would still be a context.
But maybe we shouldn't make the distinction -- that would make things simpler.

// table. set the returned physical table description back onto the table mapping, so it includes things that
// can only be determined after the physical table is created, like the streamArn.
physicalTable = createPhysicalTableIfNotExists
? physicalTableManager.createTableIfNotExists(physicalCreateTableRequest)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we creating physical tables synchronously here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes it's still synchronous

private TableDescription getTableDescriptionNoCache(String tableName) {
private MtTableDescription getTableDescriptionNoCache(String tableName) {
// first get description for table name at the current context
Optional<MtTableDescription> tableDescription = getTableDescriptionFromDb(tableName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need/want to namespace virtual mt tables and virtual tables? If not, would tenant-level tables shadow mt tables?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

To me it seems perhaps more confusing for a user to be able to define both a virtual mt table and a virtual tenant-level table with the same name, so I went with making them share the same namespace. What do you mean by tenant-level tables shadowing mt tables?

@codecov-io
Copy link
Copy Markdown

codecov-io commented Dec 2, 2019

Codecov Report

Merging #648 into master will decrease coverage by 0.15%.
The diff coverage is 89.15%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #648      +/-   ##
============================================
- Coverage     83.17%   83.02%   -0.16%     
- Complexity      987     1021      +34     
============================================
  Files            67       70       +3     
  Lines          4346     4437      +91     
  Branches        534      551      +17     
============================================
+ Hits           3615     3684      +69     
- Misses          478      493      +15     
- Partials        253      260       +7
Impacted Files Coverage Δ Complexity Δ
...namodbv2/mt/mappers/MtAmazonDynamoDbByAccount.java 0% <ø> (ø) 0 <0> (ø) ⬇️
...force/dynamodbv2/mt/repo/MtTableDescriptionRepo.kt 76.47% <100%> (-11.77%) 0 <0> (ø)
.../dynamodbv2/mt/admin/AmazonDynamoDbAdminUtils.java 69.11% <100%> (+1.42%) 15 <1> (+1) ⬆️
...namodbv2/mt/mappers/MtAmazonDynamoDbComposite.java 97.77% <100%> (+0.05%) 24 <1> (+1) ⬆️
...modbv2/mt/mappers/MtAmazonDynamoDbStreamsBase.java 78.4% <100%> (ø) 17 <1> (ø) ⬇️
...namodbv2/mt/mappers/CreateTableRequestBuilder.java 98.63% <100%> (+0.03%) 33 <1> (+1) ⬆️
...v2/mt/context/MtAmazonDynamoDbContextProvider.java 93.33% <100%> (ø) 4 <1> (ø) ⬇️
...ers/sharedtable/impl/HashPartitioningStrategy.java 91.66% <100%> (+0.75%) 10 <1> (+1) ⬆️
...dynamodbv2/mt/mappers/MtAmazonDynamoDbByTable.java 90.98% <100%> (ø) 26 <0> (ø) ⬇️
...va/com/salesforce/dynamodbv2/mt/cache/MtCache.java 44.44% <100%> (+5.55%) 5 <1> (+1) ⬆️
... and 17 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ea79c3f...49eedf1. Read the comment docs.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants