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

inmem startup improvments #9164

Merged
merged 5 commits into from
Dec 29, 2017
Merged

inmem startup improvments #9164

merged 5 commits into from
Dec 29, 2017

Conversation

stuartcarnie
Copy link
Contributor

@stuartcarnie stuartcarnie commented Nov 27, 2017

  • only call ParseTags when necessary
    • due to introduction of SeriesFile, this optimization is no longer available
  • remove dependency on inmem.Series in tsdb test package
  • Measurement and Series are no longer exported. Their use is restricted
    to the inmem package
  • improve Measurement and Series types by exporting immutable
    fields and removing unnecessary APIs and locks

Reduced startup time from 28s to 17s. Overall improvement including #9162 reduces startup from 46s to 17s for 1MM series across 14 shards.

Required for all non-trivial PRs
  • Rebased/mergable
  • Tests pass
  • CHANGELOG.md updated

@ghost ghost assigned stuartcarnie Nov 27, 2017
@ghost ghost added the review label Nov 27, 2017
walkTags(buf, func(key, value []byte) bool {
tags = append(tags, NewTag(key, value))
tags[p].Key = key
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jwilder this change removes a runtime.typedmemmove

@@ -281,8 +281,8 @@ func ParseKeyBytes(buf []byte) ([]byte, Tags) {
return buf[:i], tags
}

func ParseTags(buf []byte) (Tags, error) {
return parseTags(buf), nil
func ParseTags(buf []byte) Tags {
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 error return value was always nil, so this removes a stack push / pop for every ParseTags call

Copy link
Contributor

@jwilder jwilder left a comment

Choose a reason for hiding this comment

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

LGTM. @benbjohnson @e-dard might want to take a look. Not sure if this will affect their current dev branch.

type measurement struct {
Database string
Name string `json:"name,omitempty"`
NameBytes []byte // cached version as []byte
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need both a string and a byte version of name? Wonder if we can just have Name []byte.

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'll take a look and adjust if Name is no longer needed

Copy link
Contributor Author

@stuartcarnie stuartcarnie Nov 27, 2017

Choose a reason for hiding this comment

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

Name is still used in a number of places, however I pushed an additional commit that replaces []byte(Measurement.Name) with Measurement.NameBytes to prevent additional allocations

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool. Can remove the other places some other time.

Copy link
Contributor

@e-dard e-dard left a comment

Choose a reason for hiding this comment

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

LGTM 👍 I just have a couple of small suggestions/questions.

Can we hold off merging this until Ben and I get the dev branch er-tsi-index-part merged in?

)

func BlockTypeToInfluxQLDataType(typ byte) influxql.DataType {
if int(typ) < len(blockToFieldType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As we're on an über optimisation drive here, you could also have:

fieldTypesN byte = len(blockToFieldType)
...
...
...
func BlockTypeToInfluxQLDataType(typ byte) influxql.DataType {
    if typ < fieldTypesN {

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 tried that, but alas the bounds check elimination (BCE) optimization can't be used. We can only declare FieldTypesN as a var and the compiler cannot prove this value doesn't change outside the BlockTypeToInfluxQLDataType function.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. 👍

t := makeBlockTypeSlice(100)
for i := 0; i < b.N; i++ {
for j := 0; j < len(t); j++ {
tsm1.BlockTypeToInfluxQLDataType(t[j])
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure this can't get optimised away by the compiler? That used to be a problem so I always got into the habit of defining a var result influxql.Unknown somewhere outside the benchmark.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch – if the function was not inlined, it would have made no difference. We can deduce the inner loop overhead is thus about 45ns :)

Original benchmark was about 212ns, less loop overhead is 167ns. I added an assignment and it jumped from 45ns to 70ns. So about 30ns vs 167ns or about a 5.5x improvement.

Copy link
Contributor

Choose a reason for hiding this comment

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

Still a good improvement.


mu sync.RWMutex
shardIDs map[uint64]struct{} // shards that have this series defined
deleted bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move deleted down to the bottom? There will be 7 bytes of padding after deleted, and if it gets left here then we'll end up with a larger than necessary struct size if we add anything that's less than 8 bytes underneath.

t.mu.Unlock()
}

// StoreByte stores ids under the value key.
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me why we need this new method.

Copy link
Contributor Author

@stuartcarnie stuartcarnie Nov 28, 2017

Choose a reason for hiding this comment

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

To complement the LoadByte as there is a drive to move to []byte keys vs string keys. This change also removes a call to runtime.slicebytetostring at the call site in measurement#AddSeries. The cast in StoreByte is for the map key, so we benefit from the compiler optimization that it skips the alloc.

Copy link
Contributor

Choose a reason for hiding this comment

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

I get the push to []byte; it's something that's been slowly chipped away at over time but seems never ending 😄

The cast in StoreByte is for the map key, so we benefit from the compiler optimization that it skips the alloc.

That was kind of the underlying point of my confusion I guess.. We can't benefit from that particular compiler optimisation here because we need to alloc in order to guarantee that the map key is stable don't we? I'm pretty sure that particular optimisation would only work on the read-side wouldn't it?

With Store(value string, ids seriesIDs) I would assume there would be a runtime.slicebytetostring on the string(value) at the call site but then there would be no further allocations would there? Or maybe there would be another allocation when we did t.valueIDs[value] = ids in Store?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed you are correct. Whilst map's won't allocate if a space exists to store a new key in a bucket, the assignment does indeed cause an allocation every time. It seems the compiler is not smart enough to delay the allocation if the key does not exist.

However, that leads to another issue with this logic anyhow. The *measurement#AddSeries method calls tagKeyValue#LoadByte to fetch the seriesIDs slice and later overwrites with the StoreByte method. This is a race condition, as if two concurrent processes mutated the slice, one would overwrite the other on the StoreByte call. We know this won't happen, as it is only mutated via *measurement#AddSeries that takes an exclusive lock on the measurement. We should just remove all this complicated locking logic :)

Copy link
Contributor

Choose a reason for hiding this comment

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

If you're referring to the lock on TagKeyValue, it was added specifically to fix an existing race (I forget where).

@stuartcarnie
Copy link
Contributor Author

Will hold off until @e-dard merges the er-tsi-index-part

@stuartcarnie stuartcarnie force-pushed the sgc-inmem-startup branch 2 times, most recently from 440c8b6 to be616a8 Compare November 28, 2017 18:25
* only call ParseTags when necessary
* remove dependency on inmem.Series in tsdb test package
* Measurement and Series are no longer exported. Their use is restricted
  to the inmem package
* improve Measurement and Series types by exporting immutable
  fields and removing unnecessary APIs and locks

Reduced startup time from 28s to 17s. Overall improvement including
#9162 reduces startup from 46s to 17s for 1MM series across 14 shards.
@stuartcarnie stuartcarnie merged commit 80f1120 into master Dec 29, 2017
@ghost ghost removed the review label Dec 29, 2017
@stuartcarnie stuartcarnie deleted the sgc-inmem-startup branch December 29, 2017 20:33
@stuartcarnie
Copy link
Contributor Author

This PR was merged as it still had a number of useful improvements, including cleaning up some inmem APIs

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.

None yet

3 participants