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

Add support for concurrent index block queries #1195

Merged
merged 23 commits into from
Nov 29, 2018

Conversation

robskillington
Copy link
Collaborator

@robskillington robskillington commented Nov 22, 2018

No description provided.

@codecov
Copy link

codecov bot commented Nov 22, 2018

Codecov Report

Merging #1195 into master will decrease coverage by <.1%.
The diff coverage is 80.9%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master   #1195     +/-   ##
========================================
- Coverage    71.1%     71%   -0.1%     
========================================
  Files         739     739             
  Lines       62065   62206    +141     
========================================
+ Hits        44132   44197     +65     
- Misses      15083   15143     +60     
- Partials     2850    2866     +16
Flag Coverage Δ
#aggregator 81.6% <ø> (ø) ⬆️
#cluster 85.6% <ø> (-0.1%) ⬇️
#collector 78.1% <ø> (ø) ⬆️
#dbnode 80.7% <80.9%> (-0.2%) ⬇️
#m3em 73.2% <ø> (ø) ⬆️
#m3ninx 75.3% <ø> (ø) ⬆️
#m3nsch 51.1% <ø> (ø) ⬆️
#metrics 18.3% <ø> (ø) ⬆️
#msg 74.9% <ø> (ø) ⬆️
#query 61.4% <ø> (ø) ⬆️
#x 74.4% <ø> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cc6f8fb...308b766. Read the comment docs.

@robskillington robskillington changed the title [WIP] Add support for concurrent index block queries Add support for concurrent index block queries Nov 23, 2018
@robskillington
Copy link
Collaborator Author

Ready to review now

@richardartoul richardartoul self-requested a review November 26, 2018 19:46
@@ -38,6 +38,10 @@ const (
// DefaultBootstrapConsistencyLevel is the default bootstrap consistency level
DefaultBootstrapConsistencyLevel = topology.ReadConsistencyLevelMajority

// DefaultIndexDefaultQueryTimeout is the hard timeout value to use if none is
// specified for a specific query, zero specifies no timeout.
DefaultIndexDefaultQueryTimeout = time.Minute
Copy link
Contributor

Choose a reason for hiding this comment

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

What value are you seeing in prod? I would have thought 15->30 would be a more reasonable default

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, it could be, I just don't want to necessarily break some users existing queries. I could be convinced using 30s instead though for sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you file an issue for this to be settable from config?

Copy link
Contributor

Choose a reason for hiding this comment

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

since we don't have good support for setting runtime values in O.S.S

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yup, I opened here:
#1220

Copy link
Contributor

@richardartoul richardartoul left a comment

Choose a reason for hiding this comment

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

Haven't finished reviewing everything yet but I need to run right now, will finish review later

return index.QueryResults{}, errDbIndexUnableToQueryClosed
}

// override query response limit if needed.
// Track this as an inflight query that needs to finish
Copy link
Contributor

Choose a reason for hiding this comment

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

Super nit: period at the end of all these comments, I've been trying to fix these as we go

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}, nil
}

func (i *nsIndex) timeoutForQueryWithLock(
Copy link
Contributor

Choose a reason for hiding this comment

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

withRLock for consistency with our naming conventions elsewhere

return i.state.runtimeOpts.defaultQueryTimeout
}

func (i *nsIndex) overriddenOptsForQueryWithLock(
Copy link
Contributor

Choose a reason for hiding this comment

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

withRLock for consistency with our naming conventions elsewhere

results.Reset(i.nsMetadata.ID())
ctx.RegisterFinalizer(results)

func (i *nsIndex) blocksForQueryWithLock(queryRange xtime.Ranges) ([]index.Block, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

withRLock for consistency with our naming conventions elsewhere

}

var (
start = i.nowFn()
Copy link
Contributor

Choose a reason for hiding this comment

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

this doesn't depend on the lock right? Can probably move this above the RLock and then set the deadline down here still

}

// ensure the block has data requested by the query
if queryRange.IsEmpty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: This might be a little less confusing if it was the first part of the loop. As is, I was confused why you would need to check this after doing the block lookup, but its not related to that at all

}
}
func (i *nsIndex) newConcurrentResults(ctx context.Context) *index.ConcurrentResults {
results := i.opts.IndexOptions().ResultsPool().Get()
Copy link
Contributor

Choose a reason for hiding this comment

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

want to just throw this pool onto the struct itself like we do with the worker pool so you don't have to do the triple function call?

i.state.closed = true

var multiErr xerrors.MultiError
multiErr = multiErr.Add(i.state.insertQueue.Stop())

// Wait for inflight queries to finish before closing blocks
i.queriesWg.Wait()
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be safer (or less likely to deadlock when changes are made) if you do this wait outside the lock. So:

i.state.Lock()
defer i.state.Unlock()

if !i.isOpenWithRLock() {
    return errDbIndexAlreadyClosed
}

// Signal our intent to close so that we stop accepting reads / writes.
i.state.closed = true

i.state.Unlock()

// Wait for in-flight queries to finish before continuing.
i.queriesWg.Wait()

// Reacquire the lock so we can complete the shutdown.
// Not even sure you need to re-acquire the lock at this point if you're willing
// to remove the defer at the top since technically no one else can call Close()
// after you mark is closed.
i.state.Lock()

var multiErr xerror.MultiError
multiErr = multiErr.Add(i.state.insertQueue.Stop())

...

Copy link
Contributor

Choose a reason for hiding this comment

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

You understand the lifecycle better than I do, so take it or leave it

size = results.Size()
brokeEarly = false
)
size := results.Size()
Copy link
Contributor

Choose a reason for hiding this comment

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

wanna add the var back? I think it was cleaner. I assume you removed it unintentionally while refactoring

// we only retrieve the results lock when we add a batch of documents
// to the results set.
batch := b.docsPool.Get()
// Use documentArrayPoolCapacity to as max batch to avoid growing outside
Copy link
Contributor

Choose a reason for hiding this comment

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

"Use the maximum capacity of the array pool as the max batch size to avoid growing outside the allowed pool capacity."

}()

// NB(r): This query method only called once per block so is relatively
// cheap to declared as a lambda.
flushBatch := func() error {
Copy link
Contributor

Choose a reason for hiding this comment

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

could you eliminate the allocation entirely if you had a named function / method with the signature:

func flushBatch(batch []Document, results *ConcurrentResults) ([]Document, error)

and then you could use it like:

batch = append(batch, d)
if len(batch) < maxBatch {
    continue
}
batch, err = flushBatch(batch, results)
if err != nil {
    ...

Maybe overkill, but the anonymous func doesn't seem like its closing over many vars

brokeEarly = false
)
size := results.Size()
limitedResults := false
Copy link
Contributor

Choose a reason for hiding this comment

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

can you just call this exhaustive and return it at the end instead of doing the limitedResults -> exhaustive translation

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Reads worse, it was actually like this before.

// documentArrayPool size in general: 256*256*sizeof(doc.Document)
// = 256 * 256 * 16
// = 1mb (but with Go's heap probably 2mb)
// TODO(r): Make this configurable in a followup change.
Copy link
Contributor

Choose a reason for hiding this comment

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

We're probably gonna want this tunable really soon so we don't have to ask people to recompile when they're testing our changes, especially if their read workloads are very different from ours

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is now only going to be used by the compactor, so it should be tiny (we never will have 256 concurrent compactions, and compactor just uses a single pool).

Name: r.copyBytes(f.Name),
Value: r.copyBytes(f.Value),
})
tags.Append(r.idPool.CloneTag(ident.Tag{
Copy link
Contributor

Choose a reason for hiding this comment

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

is this just a cleanup?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yup, this wasn't being pooled before, now it is (it always could have been).

workers = i.opts.QueryIDsWorkerPool()
wg sync.WaitGroup

// results contains all concurrent mutalbe state below
Copy link
Contributor

Choose a reason for hiding this comment

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

mutable

@@ -38,6 +38,10 @@ const (
// DefaultBootstrapConsistencyLevel is the default bootstrap consistency level
DefaultBootstrapConsistencyLevel = topology.ReadConsistencyLevelMajority

// DefaultIndexDefaultQueryTimeout is the hard timeout value to use if none is
// specified for a specific query, zero specifies no timeout.
DefaultIndexDefaultQueryTimeout = time.Minute
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you file an issue for this to be settable from config?

@@ -38,6 +38,10 @@ const (
// DefaultBootstrapConsistencyLevel is the default bootstrap consistency level
DefaultBootstrapConsistencyLevel = topology.ReadConsistencyLevelMajority

// DefaultIndexDefaultQueryTimeout is the hard timeout value to use if none is
// specified for a specific query, zero specifies no timeout.
DefaultIndexDefaultQueryTimeout = time.Minute
Copy link
Contributor

Choose a reason for hiding this comment

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

since we don't have good support for setting runtime values in O.S.S


// queriesWg tracks outstanding queries to ensure
// we wait for all queries to complete before actually closing
// blocks and other cleanup tasks on index close
Copy link
Contributor

Choose a reason for hiding this comment

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

super nit: Period.

i.queriesWg.Add(1)
defer i.queriesWg.Done()

// Enact overrides for query options
Copy link
Contributor

Choose a reason for hiding this comment

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

super nit: period

}

var (
ticker = time.NewTicker(timeLeft)
Copy link
Contributor

Choose a reason for hiding this comment

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

I know Prateek has talked about time tickers not getting scheduled properly in certain situations and preferring to use loops with time.Sleep which apparently has some special hooks into the runtime, although then you'd have to implement polling so ehhhh. Probably fine as is

@@ -905,18 +1138,19 @@ func (i *nsIndex) CleanupExpiredFileSets(t time.Time) error {

func (i *nsIndex) Close() error {
Copy link
Contributor

Choose a reason for hiding this comment

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

is there not a "Tick()" loop? How does the ticking get stopped

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There is a tick loop yeah, see Tick() on nsIndex.

@robskillington robskillington merged commit 8229522 into master Nov 29, 2018
@justinjc justinjc deleted the r/concurrent-index-block-queries branch January 7, 2019 19:30
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.

2 participants