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

CASSGO-39 Add query attempt interceptor #1820

Open
wants to merge 5 commits into
base: trunk
Choose a base branch
from

Conversation

BenEddy
Copy link

@BenEddy BenEddy commented Sep 12, 2024

As suggested in #1786, this PR introduces a QueryAttemptInterceptor interface for intercepting query attempts, which allows clients to inject logic that should apply to all queries e.g. context manipulation, rate limiting, query modification, logging, fault injection, metrics, etc.

Alternative to #1810.

@BenEddy BenEddy mentioned this pull request Sep 12, 2024
Removes return statement that bypassed query attempt tracking.

// Optionally bypass the handler and return an error to prevent query execution.
// For example, to simulate query timeouts.
if q.injectFault && query.Attempts() == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the attempt number be a parameter to Intercept? It seems that calling Attempts() could be racy, especially when speculative execution is enabled:

  1. Attempt 0 started
  2. Attempt 1 started
  3. Interceptor 0 called - gets attempt 0
  4. Interceptor 1 called - gets attempt 0
  5. attempts incremented
  6. attempts incremented

Perhaps the signature should be Intercept(ctx context.Context, attempt gocql.QueryAttempt, handler gocql.QueryAttemptHandler)? That would enable adding more information about the attempt in the future.

type QueryAttempt struct {
	// query to execute
	query gocql.ExecutableQuery
	// conn to use to execute the query
	conn *gocql.Conn
	// number of the attempt. 0 is the initial attempt, 1 is first retry, etc.
	number int
}
type QueryAttemptHandler = func(context.Context, QueryAttempt) *Iter

Copy link
Author

Choose a reason for hiding this comment

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

@martin-sucha passing a QueryAttempt type seems like a great way to keep the interceptor API stable 👍


// The interceptor is responsible for calling the `handler` function and returning the handler result. Failure to
// call the handler will panic. If the interceptor wants to halt query execution and prevent retries, it should
// return an error.
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: The function does not have an error return value. Should we add an error return value or should we reference NewIterWithErr instead?

question: How does returning an error prevent a retry?

Copy link
Author

Choose a reason for hiding this comment

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

Should we add an error return value or should we reference NewIterWithErr instead?

Updated Intercept to optionally return an error and removed NewIterWithErr.

How does returning an error prevent a retry?

Returning a non-retriable error like context.Canceled would prevent retries, but admittedly that's not clear from the function docs. I removed the reference to preventing retries and will assume that users can probably figure out how to configure retry policies with whatever error handling and retry prevention makes sense for them.

Remove gocql.NewIterWithErr
Comment on lines +59 to +62
// The connection used to execute the query.
Conn *Conn
// The host that will receive the query.
Host *HostInfo
Copy link

Choose a reason for hiding this comment

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

Do we need to add and expose these fields in QueryAttempt?

If there’s no compelling need for direct connection manipulation, then maybe we could expose only a connection metadata that’s relevant for logging or monitoring purposes?

Comment on lines +81 to +85
err = session.Query("select now() from system.local").
WithContext(ctx).
RetryPolicy(&gocql.SimpleRetryPolicy{NumRetries: 2}).
Scan(&stringValue)
if err != nil {
Copy link

Choose a reason for hiding this comment

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

How about adding an option to apply the interceptor selectively to specific queries, like .WithInterceptor() method to the Query struct?

@ribaraka
Copy link

@joao-r-reis
Copy link
Contributor

I don't know if I like the idea of having an interceptor that can modify the query/batch after it has already been submitted to the driver. Having something like the equivalent of a request tracker in the java driver would be nice but the goal there would be to inspect the queries, not modify them. Modifying query/batch also goes against #1447 which is being worked on atm.

@joao-r-reis
Copy link
Contributor

Rate limiting should probably also be handled in a different component with its own interface dedicated to rate limiting/throttling

@joao-r-reis
Copy link
Contributor

Hmm taking another look at #1786 it seems that the focus is indeed to be an interface generic enough to serve as a way to implement stuff like request limiting or query modification while the inspection stuff could be handled by the observers that we already have.

In that case we need to make sure the changes here are consistent with changes in #1447

@joao-r-reis joao-r-reis changed the title Add query attempt interceptor CASSGO-39 Add query attempt interceptor Nov 22, 2024
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.

4 participants