-
Notifications
You must be signed in to change notification settings - Fork 3
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
feat: Idempotent Provisioning #1004
Conversation
bb3e948
to
c975259
Compare
private tablesInSource: string[], | ||
) {} | ||
|
||
static async loadProvisioningState (hasuraClient: HasuraClient, provisioningConfig: ProvisioningConfig): Promise<ProvisioningState> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided not to have the hasuraClient as a direct dependency for this class as it usually does not need access to the client, once initialized.
return new ProvisioningState(provisioningConfig, hasuraMetadata, tablesInSource); | ||
} | ||
|
||
async reload (hasuraClient: HasuraClient): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, you can just recreate the state class to reload it, but I thought adding a specific function felt more semantically correct.
Tested locally with the following cases:
In all above cases, provisioning properly handled or healed the state. |
import { type HasuraTableMetadata, type HasuraMetadata, type HasuraSource, HASURA_PERMISSION_TYPES } from '../hasura-client'; | ||
import type HasuraClient from '../hasura-client'; | ||
|
||
export default class ProvisioningState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention with this class is to be a lightweight interface which exposes the state of resources currently, and can be used to bring the current state closer to the intended one.
const accountId = 'morgs.near'; | ||
const functionName = 'test-function'; | ||
const databaseSchema = 'CREATE TABLE blocks (height numeric)'; | ||
indexerConfig = new IndexerConfig('', accountId, functionName, 0, '', databaseSchema, LogLevel.INFO); | ||
const emptyHasuraMetadata = generateDefaultHasuraMetadata(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose to make the state class a variable rather than a class variable because the provisioner class is reused for all provisioning tasks and I did not want to manage cleaning the state between calls. If I re-created the state, it would make DI much harder. So, I decided to keep things simple and have it used in line. This does make mocking a little bit tougher as I need to mock metadata instead, but it's done in multiple places, so I felt this was fine.
const databaseName = indexerConfig.databaseName(); | ||
const schemaName = indexerConfig.schemaName(); | ||
|
||
await this.runIndexerSql(databaseName, schemaName, indexerConfig.schema); | ||
const onlySystemTablesCreated = provisioningState.getCreatedTables().every((table) => this.SYSTEM_TABLES.includes(table)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's only two states in which we reach this point in the code. Either the system provisioning was successful in this current pass, or we are revisiting. If we are in the initial pass, the state will actually say we have created and tracked nothing since we have not reloaded. That's fine for this line through, as we are really just interested if any non system tables have already been created or not. It's not important if the state is up to date following system provisioning, as the outcome would be the same.
Pointed my local Provisioner to prod and ran provisioning over the broken strachu.near/easy-poll-v0.3_testing indexer. It completely fixed it and the indexer now runs. |
Provisioning is occasionally finnicky, failing on different parts of the process. Having retryable provisioning allows us to heal indexer state through retries rather than manually actioning this. To this, the provisioning step needs to have access to accurate status of provisioning in order to make the correct decisions on which non-idempotent actions to take.
This PR introduces a ProvisioningState class which represents the status of provisioning, and can be used to make decisions on what parts of provisioning need to be completed.