Conversation
| * @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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Is this filter going to match the *.Leases tables?
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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 { | ||
|
|
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
If this is a multitenant table, then drop the corresponding physical table
| this.isMultitenant = isMultitenant; | ||
| } | ||
|
|
||
| public VirtualDynamoTableDescriptionImpl(MtTableDescription tableDescription) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
What is the purpose of this context?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Is this different from "no context" (as used in master for cross-tenant scans or change streaming)?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Are we creating physical tables synchronously here?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Do we need/want to namespace virtual mt tables and virtual tables? If not, would tenant-level tables shadow mt tables?
There was a problem hiding this comment.
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 Report
@@ 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
Continue to review full report at Codecov.
|
This is a first version. Notes:
MtAmazonDynamoDbinterface now has a new method,createMultitenantTable. It's supported only inMtAmazonDynamoDbBySharedTable.MtAmazonDynamoDbBySharedTable, a top-level context must have been defined when constructing the client, and the context when callingcreateMultitenantTablemust 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.TableDescriptionjsons in the table description repo, we now storeMtTableDescriptionjsons, whereMtTableDescriptionextendsTableDescriptionand has an extra flagisMultitenant(and more things in the future). This should make the new logic still be able to parse existing table description repo records.MtTableDescription) -- should be straightforward but I just haven't done it because it's in Kotlin which I'm not familiar with...