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

Retry creating unique index #613

Merged
merged 11 commits into from
Jan 24, 2025
Merged

Retry creating unique index #613

merged 11 commits into from
Jan 24, 2025

Conversation

agedemenli
Copy link
Contributor

@agedemenli agedemenli commented Jan 21, 2025

Introduce a retry mechanism for creating unique indexes.
First, create unique index concurrently. Then wait until Postgres is done with the index creation, periodically checking the view pg_stat_progress_create_index. Once it is done, lookup pg_index to see if the index is validated or not. If it is, we are good to go. If not, drop the index and try again.

Since we are running select queries on pg_stat_progress_create_index and pg_index and we are expecting a real output to see the progress, this approach doesn't work with fake db. I have added hardcoded responses for the fake db scenario, so that we can safely process migrations to update virtual schema as well.
Not sure if this is a good solution or not. Open for discussions.

Tested manually with high load, while 10m rows being inserted. But I wonder if there's a way to add a test for the "high load" scenario.

@agedemenli agedemenli force-pushed the high-load-unique-index branch from 3b8f549 to 64815e8 Compare January 21, 2025 19:58
@agedemenli agedemenli changed the title WIP: high load unique index Retry creating unique index Jan 22, 2025
@agedemenli agedemenli marked this pull request as ready for review January 22, 2025 13:05
}

// Make sure Postgres is done creating the index
isInProgress := true
Copy link
Contributor

Choose a reason for hiding this comment

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

Using a time.Ticker here is more idiomatic to Golang: https://pkg.go.dev/time#Ticker

We could write something like this (it might not compile, but this is the gist of what I mean):

ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()

var err error
isIndexInProgress := true
for isIndexInProgress {
	select {
	case t := <-ticker.C:
		isInProgress, err = isIndexInProgress(ctx, conn, s.Name, o.Name)
		if err != nil {
			return nil, err
		}
	}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, thanks!

@@ -83,3 +115,48 @@ func addUniqueIndex(ctx context.Context, conn db.DB, table, column, name string)

return err
}

func isIndexInProgress(ctx context.Context, conn db.DB, schemaname string, indexname string) (bool, error) {
var isInProgress bool
Copy link
Contributor

@kvch kvch Jan 22, 2025

Choose a reason for hiding this comment

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

Nit: please move inProgress to line 135 (before using it in ScanFirstValue).

}

func isIndexValid(ctx context.Context, conn db.DB, schemaname string, indexname string) (bool, error) {
var isValid bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: please move isValid to line 157 (before using it in ScanFirstValue).

// Add a unique index to the new column
if err := addUniqueIndex(ctx, conn, table.Name, column.Name, o.Name); err != nil {
return nil, fmt.Errorf("failed to add unique index: %w", err)
for retryCount := 5; retryCount > 0; retryCount-- {
Copy link
Contributor

Choose a reason for hiding this comment

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

This format is more idiomatic:

Suggested change
for retryCount := 5; retryCount > 0; retryCount-- {
for retryCount := 0; retryCount < 5; retryCount++ {

Copy link
Collaborator

@andrew-farries andrew-farries left a comment

Choose a reason for hiding this comment

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

This looks good 👍

There are other places where we create indexes concurrently that might fail in a similar way; #485 lists another (under Reproduction 2), so we might want to extract this to something that we can re-use for these other cases too in later PRs

pkg/migrations/op_set_unique.go Outdated Show resolved Hide resolved
pkg/migrations/op_set_unique.go Outdated Show resolved Hide resolved
return err
}
}
ticker.Stop()
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move ticker.Stop after the line where you initialize it, and add defer:

ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()

With this change we make sure that if isIndexInProgress returns an error, the ticker is stopped.

continue
}
if duplicatedMember, constraintColumns := d.stmtBuilder.allConstraintColumns(uc.Columns, colNames...); duplicatedMember {
if err := createUniqueIndexConcurrently(ctx, d.conn, "", DuplicationName(uc.Name), d.stmtBuilder.table.Name, constraintColumns); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice

@agedemenli agedemenli merged commit 070e388 into main Jan 24, 2025
28 checks passed
@agedemenli agedemenli deleted the high-load-unique-index branch January 24, 2025 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants