-
Notifications
You must be signed in to change notification settings - Fork 333
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
Query backfills #1280
Query backfills #1280
Conversation
We need to be caching results here, and not loading only all backfills for every query. So we need a backfill cache like we have a tickets cache. Some context: #1123 We used to use in redis filtering for tickets, and the performance was frankly awful. As the number of queries goes up, the load on redis (which is single threaded!) goes up linearly. By joining multiple query calls into one cache update, the load only scales relative to the number of query service instances. Additionally, it reduces the number of times a given ticket/backfill is loaded from redis (per version) to only once per query instance, instead of once per query. |
We have discussed a need to make an update to the design: include Backfill Imagine there is a version 1 Two options to achieve the aforementioned behaviour:
|
ping @Laremere ☝️ |
if !returnedBackfillByQuery(t, tc) { | ||
require.Fail(t, "Expected to find backfill in pool but didn't.") | ||
} | ||
}) |
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.
Hey @Laremere i could use some help here. I'm using the currently existing test cases for querying backfills. Do you think are useful scenarios which doesn't provide false positives?? Or should we revisit this??
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'm not sure what you're asking? These tests seem good to me.
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 was asking if current test cases are useful for testing this. But if you think the test cases are good for querying backfills, i'll leave them untouched
I vote for splitting this PR into at least 2 parts. First part should include
|
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.
Now tests are fine. Please roll back TicketCache and move this to another PR. So that we don't update the core logic.
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.
Approving the PR. The main functionality is working now. Tests are flaky at the moment, but most of the time passes locally. Let's skip them and unblock @yeukovichd MMF and Backend Changes.
@Laremere Scott can you please review this PR? It is needed to proceed with MMF changes. |
|
||
pf, err := filter.NewPoolFilter(pool) | ||
if err != nil { | ||
return err |
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.
Should it be an exact status code like in the return statements above? If so, please check other returns in this method.
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.
QueryTickets
does not have. But we can make it better.
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 assume nothing to be done. NewPoolFilter
already included those codes
.
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.
Yes, but what should be done with other returns down below?
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 NewPoolFilter returns status.Error(...) if some error happens, so here that error it is only returned
TestBackfillNotFound is failing (hard to reproduce locally): |
toFetch := []string{} | ||
for id := range indexedBackfills { | ||
if _, ok := bc.backfills[id]; !ok { | ||
toFetch = append(toFetch, id) |
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.
Why can't you call bc.store.GetBackfill right here, instead of iterating through toFetch down below? We can avoid 1 extra for loop.
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.
Here could be a TODO or something, in order to keep this similar to runRequest of Tickets. Later on, we can optimise it.
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.
Tests are flaky even without this enhancement. The main difference is hset
with Generation in it vs usual set
.
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.
Here i have some doubts because, if we keep the same behavior from TicketCache, there are some metrics i need to implement and one of them uses the toFetch
variable. So on one hand we could do what you say Alexey, or we keep the same metrics as for tickets.
cc: @Laremere
} | ||
} | ||
|
||
for _, backfillToFetch := range toFetch { |
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'm worried this loop will be noticeably slow, because it could potentially make many separate requests to redis. Especially true because it's expected for backfills to change a lot and need to be refreshed. For tickets, there's a bulk get which is done using a single redis call. I'm not opposed to merging it in this state, and adding that later (as long as it's high priority to add.)
|
||
func newBackfillCache(b *appmain.Bindings, cfg config.View) *backfillCache { | ||
bc := &backfillCache{ | ||
store: statestore.New(cfg), |
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.
Both caches should share the same statestore, move this to be an argument to the newCache, and create it once.
@@ -325,3 +371,146 @@ func (tc *ticketCache) update() { | |||
logger.Debugf("Ticket Cache update: Previous %d, Deleted %d, Fetched %d, Current %d", previousCount, deletedCount, len(toFetch), len(tc.tickets)) | |||
tc.err = nil | |||
} | |||
|
|||
type backfillCache struct { |
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.
Simply copy pasting here created a lot of duplicated code, which I mostly don't like because the common part is tricky concurrency stuff.
I think the best solution here is to refactor the ticketCache into simply cache
. Include in the struct (and newCache
) an update function with type func(store statestore.Service, state interface{}) error
, moving both update functions to static functions which can be passed into the constructor. You'll need a default value for the state as well.
Then change f in request to be f func(interface{})
. For the update and request functions/closures, simply do a cast.
It's not pretty, and generics would be really nice here. However all of the casts will trivially be tested by the e2e tests, so I'm not really worried about a bug there. I think the cost of a few casts is worth de-duplicating the concurrency code.
Thoughts?
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 also saw lots of duplicate code here, just thought it might be united into one set of functions later on at the refactoring stage. In order to leave beta (backfill) and release (ticket) code separate.
Overall there is only one condition which is changed, it is comparing Generation number in a memory (map) and in HSET in Redis.
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.
Some further examples to clarify:
type ticketCache struct {
store statestore.Service
requests chan *cacheRequest
// Single item buffered channel. Holds a value when runQuery can be safely
// started. Basically a channel/select friendly mutex around runQuery
// running.
startRunRequest chan struct{}
wg sync.WaitGroup
update func(statestore.Service, interface{}) error
// Mutlithreaded unsafe fields, only to be written by update, and read when
// request given the ok.
value interface{}
err error
}
func (s *queryService) QueryTicketIds(req *pb.QueryTicketIdsRequest, responseServer pb.QueryService_QueryTicketIdsServer) error {
/// <code removed here for example simplicity>
var results []string
err = s.tc.request(ctx, func(value interface{}) {
tickets := value.(map[string]*pb.Ticket)
for id, ticket := range tickets {
if pf.In(ticket) {
results = append(results, id)
}
}
})
if err != nil {
err = errors.Wrap(err, "QueryTicketIds: failed to run request")
return err
}
/// <code removed here for example simplicity>
}
func updateTicketCache(store statestore.Service, value interface{}) error {
tickets := value.(map[string]*pb.Ticket)
/// etc....
}
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.
Covered by this PR #1300 . We can close this PR I think.
@@ -66,3 +70,59 @@ func TestGetPageSize(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
|||
func TestBackfillCache(t *testing.T) { |
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.
Honestly, I'd skip testing here (as I did with tickets), and just make sure all these cases are covered by e2e.
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.
Specifically, the "call create and see backfill in query, then call update and the backfill in query is updated" test case on e2e is super important. Be good to also check with delete properly making it go away. (Do we have that test case for tickets? If not we should...)
Doing those tests would make these tests redudant.
if !returnedBackfillByQuery(t, tc) { | ||
require.Fail(t, "Expected to find backfill in pool but didn't.") | ||
} | ||
}) |
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'm not sure what you're asking? These tests seem good to me.
}) | ||
if err != nil { | ||
err = errors.Wrap(err, "QueryBackfills: failed to run request") | ||
return err |
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.
@hsorellana I mean should you return here some status codes?
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.
ouh you're right!
What this PR does / Why we need it:
This PR is WIP which adds the ability to query backfill objects. Still needs a lot of work.
Special notes for your reviewer:
Don't pay attention to Frontend and Statestore func additions, since they are unrelated to this PR but i needed some functionality from those services to get QueryService working
This is related to Backfill feature.