diff --git a/README.md b/README.md index c053225102..a9c878e77d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ More information: ``` # to build a local m3dbnode process -make m3dbnode +make m3dbnode (note that we currently require at least Go 1.10 or higher) # run it with the sample configuration ./bin/m3dbnode -f ./src/dbnode/config/m3dbnode-local-etcd.yml diff --git a/src/query/models/tag.go b/src/query/models/tag.go index fde458ac7f..1c6aa3d303 100644 --- a/src/query/models/tag.go +++ b/src/query/models/tag.go @@ -25,6 +25,7 @@ import ( "hash/fnv" "regexp" "sort" + "strings" ) const ( @@ -147,7 +148,25 @@ func (m Matchers) ToTags() (Tags, error) { // ID returns a string representation of the tags func (t Tags) ID() string { - b := make([]byte, 0, len(t)) + var ( + idLen = t.IDLen() + strBuilder = strings.Builder{} + ) + + strBuilder.Grow(idLen) + for _, tag := range t { + strBuilder.WriteString(tag.Name) + strBuilder.WriteByte(eq) + strBuilder.WriteString(tag.Value) + strBuilder.WriteByte(sep) + } + + return strBuilder.String() +} + +// IDMarshalTo writes out the ID representation +// of the tags into the provided buffer. +func (t Tags) IDMarshalTo(b []byte) []byte { for _, tag := range t { b = append(b, tag.Name...) b = append(b, eq) @@ -155,7 +174,18 @@ func (t Tags) ID() string { b = append(b, sep) } - return string(b) + return b +} + +// IDLen returns the length of the ID that would be +// generated from the tags. +func (t Tags) IDLen() int { + idLen := 2 * len(t) // account for eq and sep + for _, tag := range t { + idLen += len(tag.Name) + idLen += len(tag.Value) + } + return idLen } // IDWithExcludes returns a string representation of the tags excluding some tag keys diff --git a/src/query/models/tag_test.go b/src/query/models/tag_test.go index c7dd9f3cf8..ee25f371d5 100644 --- a/src/query/models/tag_test.go +++ b/src/query/models/tag_test.go @@ -115,7 +115,17 @@ func createTags(withName bool) Tags { func TestTagID(t *testing.T) { tags := createTags(false) - assert.Equal(t, tags.ID(), "t1=v1,t2=v2,") + assert.Equal(t, "t1=v1,t2=v2,", tags.ID()) + assert.Equal(t, tags.IDLen(), len(tags.ID())) +} + +func TestTagIDMarshalTo(t *testing.T) { + var ( + tags = createTags(false) + b = tags.IDMarshalTo([]byte{}) + ) + assert.Equal(t, []byte("t1=v1,t2=v2,"), b) + assert.Equal(t, tags.IDLen(), len(b)) } func TestWithoutName(t *testing.T) { diff --git a/src/query/storage/m3/storage.go b/src/query/storage/m3/storage.go index bd7109756b..76b4afb851 100644 --- a/src/query/storage/m3/storage.go +++ b/src/query/storage/m3/storage.go @@ -269,13 +269,25 @@ func (s *m3storage) Write( return errors.ErrNilWriteQuery } - id := query.Tags.ID() - // TODO: Consider caching id -> identID - identID := ident.StringID(id) + var ( + // TODO: Pool this once an ident pool is setup. We will have + // to stop calling NoFinalize() below if we do that. + buf = make([]byte, 0, query.Tags.IDLen()) + idBuf = query.Tags.IDMarshalTo(buf) + id = ident.BytesID(idBuf) + ) // Set id to NoFinalize to avoid cloning it in write operations - identID.NoFinalize() + id.NoFinalize() tagIterator := storage.TagsToIdentTagIterator(query.Tags) + if len(query.Datapoints) == 1 { + // Special case single datapoint because it is common and we + // can avoid the overhead of a waitgroup, goroutine, multierr, + // iterator duplication etc. + return s.writeSingle( + ctx, query, query.Datapoints[0], id, tagIterator) + } + var ( wg sync.WaitGroup multiErr syncMultiErrs @@ -287,7 +299,7 @@ func (s *m3storage) Write( datapoint := datapoint wg.Add(1) s.writeWorkerPool.Go(func() { - if err := s.writeSingle(ctx, query, datapoint, identID, tagIter); err != nil { + if err := s.writeSingle(ctx, query, datapoint, id, tagIter); err != nil { multiErr.add(err) }