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

Use a separate unit to manage the cache of prepared statements #2937

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

px3303
Copy link
Contributor

@px3303 px3303 commented Mar 17, 2023

This PR is an improved version of #2936, with the following changes:

1.Use a separate unit (StmtCache) to manage the cache of prepared statements.
2.Wrap sql.Stmt struct to handle monitoring prepared statement execution errors.
3.When a statement execution error occurs, only clear the corresponding prepared statement cache, preventing circular cache clearing problems.
4.Add prepared statement execution error metrics.

Checklist

@AlCutter
Copy link
Member

/gcbrun

Copy link
Contributor

@Martin2112 Martin2112 left a comment

Choose a reason for hiding this comment

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

How good is the test coverage for this? Not sure if it's worth having dedicated tests but worth considering perhaps.

@@ -0,0 +1,246 @@
package sqlutil
Copy link
Contributor

Choose a reason for hiding this comment

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

The package name is generic. I'd suggest naming it after the main functionality it contains (the statement cache).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The package name is generic. I'd suggest naming it after the main functionality it contains (the statement cache).
fixed

@@ -7,6 +7,8 @@
different version can lead to presubmits failing due to unexpected
diffs.

* Use a separate unit (StmtCache) to manage the cache of prepared statements, which wraps the sql.Stmt struct to handle and monitor the execution errors of the prepared statement. When an error occurs during statement execution, it closes the statement and clears the cache, as well as increments the error monitoring indicator.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure what 'unit' means?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The word 'separate unit' comes from the current TODO comment:

// TODO(al,martin): consider pulling this all out as a separate unit for reuse

}

// errHandler handling and monitoring SQL errors
// The err parameter standby
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure what 'standby' means?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comments have been updated.

// var name string
// err := nameByUseridStmt.QueryRow(id).Scan(&name)
//
// QueryRow uses context.Background internally; to specify the context, use
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there cases where the background context is still used? Might be worth deprecating this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

}

// NewStmtCache creates a StmtCache instance.
func NewStmtCache(db *sql.DB, mf monitoring.MetricFactory) *StmtCache {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can just be 'New'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

return strings.Replace(sql, PlaceholderSQL, parameters, 1)
}

// GetStmt creates and caches sql.Stmt structs and returns their wrapper structs Stmt.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think 'structs' can be removed as this should be clear from context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

Copy link
Member

@AlCutter AlCutter left a comment

Choose a reason for hiding this comment

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

The behaviour of this PR is slightly different to #2936 in that I think here we'll only remove the statement which errored from the cache (the others will remain until they too error out) - is that intentional?

The other thing which I think is worth discussing is the relative size of the two PRs - this one feels like it's going for a "cleaner" way of approaching it, but the trade-off is that it's 10x the LoC.
#2936 is very small in comparison, and very explicit, but could miss a call site (although since the entire cache is flushed on any error that would likely mitigate the effect missed call sites).
Any thoughts, @px3303 @Martin2112 @mhutchinson @google/trillian-team?

// _, err := cache.GetStmt(ctx, sql, 1, "?", "")
// if err != nil {
// t.Fatalf("failed to cache.GetStmt: %s", err)
// }
Copy link
Member

Choose a reason for hiding this comment

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

Would calling db.Close() after creating the stmt but before using it let you trigger the cache clear logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, that part was submitted before it was completed. It has now been completed.

sc.statementMutex.Lock()
defer sc.statementMutex.Unlock()

if sc.statements[s.statement] != nil && sc.statements[s.statement][s.placeholderNum] == s.stmt {
Copy link
Member

Choose a reason for hiding this comment

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

I think you might be able to write this as:

if s, ok := sc.statements[s.statement][s.placeholderNum]; ok && s == s.stmt { ... }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you might be able to write this as:

if s, ok := sc.statements[s.statement][s.placeholderNum]; ok && s == s.stmt { ... }

Thanks, fixed.

errStmtCounter monitoring.Counter
)

// PlaceholderSQL SQL statement placeholder
Copy link
Member

Choose a reason for hiding this comment

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

nit: missing trailing full-stops on a few of the comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@px3303
Copy link
Contributor Author

px3303 commented Mar 21, 2023

How good is the test coverage for this? Not sure if it's worth having dedicated tests but worth considering perhaps.

I have added some test cases, but some situations cannot be simulated. For example, the prepared statement disappeared in the database for unknown reasons. Currently, we have encountered this problem in the production environment. A failure in the AWS RDS database caused the prepared statement to fail, and an error was reported: Unknown prepared statement handler (17) gave to mysql_ stmt_ precheck。 When this happens, the program does not automatically resume.

@Martin2112 @AlCutter

@px3303
Copy link
Contributor Author

px3303 commented Mar 21, 2023

The behaviour of this PR is slightly different to #2936 in that I think here we'll only remove the statement which errored from the cache (the others will remain until they too error out) - is that intentional?

Because in high concurrency situations, clearing all caches may cause a loop problem.

The other thing which I think is worth discussing is the relative size of the two PRs - this one feels like it's going for a "cleaner" way of approaching it, but the trade-off is that it's 10x the LoC. #2936 is very small in comparison, and very explicit, but could miss a call site (although since the entire cache is flushed on any error that would likely mitigate the effect missed call sites). Any thoughts, @px3303 @Martin2112 @mhutchinson @google/trillian-team?

This PR wants to handle it more cleanly, and also wants to monitor SQL errors. Currently, these SQL errors only print logs, and we cannot detect them in a timely manner.

@px3303 px3303 requested review from Martin2112 and AlCutter and removed request for jiggoha, Martin2112 and AlCutter March 21, 2023 14:39
@Martin2112
Copy link
Contributor

I wonder if we should be looking at the other option of removing the statement cache? It's not clear to me what the performance impact would be and whether it would be acceptable.

The Golang driver does not have the same option as Java for retaining prepared statements: https://docs.oracle.com/cd/B19306_01/java.102/b14355/stmtcach.htm#i1071993.

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