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

[query] Update Graphite find calls to complete unterminated queries with all possible path values for ** #3593

Merged
merged 10 commits into from
Jul 12, 2021
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[
{
"id": "a.**.daz",
"text": "daz",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a.**.cake",
"text": "cake",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a.**.caw",
"text": "caw",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[
{
"id": "a**.caw",
"text": "caw",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a**.daz",
"text": "daz",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a**.bag",
"text": "bag",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a**.bar",
"text": "bar",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a**.biz",
"text": "biz",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "a**.cake",
"text": "cake",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"id": "qux.**.pos2-1",
"text": "pos2-1",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "qux.**.pos2-0",
"text": "pos2-0",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"id": "qux**.pos2-1",
"text": "pos2-1",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "qux**.pos1-a",
"text": "pos1-a",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "qux**.pos1-b",
"text": "pos1-b",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "qux**.pos1-c",
"text": "pos1-c",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
},
{
"id": "qux**.pos2-0",
"text": "pos2-0",
"leaf": 0,
"expandable": 1,
"allowChildren": 1
}
]
4 changes: 4 additions & 0 deletions scripts/docker-integration-tests/carbon/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'f.bar.*' fbaz.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'g.bar.*' gbaz.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'h.bar*' hbarbaz.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'i.bar*' ibarbaz.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'a**.*' a_starstar_dot_star.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'a.**.*' a_dot_starstar_dot_star.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'qux**.*' qux_starstar_dot_star.json"
ATTEMPTS=2 TIMEOUT=1 retry_with_backoff "find_carbon 'qux.**.*' qux_dot_starstar_dot_star.json"

# Test find limits from config of matching max docs of 200 with:
# carbon:
Expand Down
74 changes: 49 additions & 25 deletions src/query/api/v1/handler/graphite/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,31 +73,45 @@ func mergeTags(
terminatedResult *consolidators.CompleteTagsResult,
childResult *consolidators.CompleteTagsResult,
) (map[string]nodeDescriptor, error) {
// sanity check the case.
if terminatedResult.CompleteNameOnly {
return nil, errors.New("terminated result is completing name only")
}
var (
terminatedResultTags = 0
childResultTags = 0
)

if childResult.CompleteNameOnly {
return nil, errors.New("child result is completing name only")
// Sanity check the queries aren't complete name only queries.
if terminatedResult != nil {
terminatedResultTags = len(terminatedResult.CompletedTags)
if terminatedResult.CompleteNameOnly {
return nil, errors.New("terminated result is completing name only")
}
}

mapLength := len(terminatedResult.CompletedTags) + len(childResult.CompletedTags)
tagMap := make(map[string]nodeDescriptor, mapLength)
if childResult != nil {
childResultTags = len(childResult.CompletedTags)
robskillington marked this conversation as resolved.
Show resolved Hide resolved
if childResult.CompleteNameOnly {
return nil, errors.New("child result is completing name only")
}
}

for _, tag := range terminatedResult.CompletedTags {
for _, value := range tag.Values {
descriptor := tagMap[string(value)]
descriptor.isLeaf = true
tagMap[string(value)] = descriptor
size := terminatedResultTags + childResultTags
robskillington marked this conversation as resolved.
Show resolved Hide resolved
tagMap := make(map[string]nodeDescriptor, size)
if terminatedResult != nil {
for _, tag := range terminatedResult.CompletedTags {
for _, value := range tag.Values {
descriptor := tagMap[string(value)]
descriptor.isLeaf = true
tagMap[string(value)] = descriptor
}
}
}

for _, tag := range childResult.CompletedTags {
for _, value := range tag.Values {
descriptor := tagMap[string(value)]
descriptor.hasChildren = true
tagMap[string(value)] = descriptor
if childResult != nil {
for _, tag := range childResult.CompletedTags {
for _, value := range tag.Values {
descriptor := tagMap[string(value)]
descriptor.hasChildren = true
tagMap[string(value)] = descriptor
}
}
}

Expand All @@ -120,7 +134,7 @@ func (h *grahiteFindHandler) ServeHTTP(
// NB: need to run two separate queries, one of which will match only the
// provided matchers, and one which will match the provided matchers with at
// least one more child node. For further information, refer to the comment
// for parseFindParamsToQueries
// for parseFindParamsToQueries.
terminatedQuery, childQuery, raw, err := parseFindParamsToQueries(r)
if err != nil {
xhttp.WriteError(w, err)
Expand All @@ -134,11 +148,17 @@ func (h *grahiteFindHandler) ServeHTTP(
cErr error
wg sync.WaitGroup
)
wg.Add(2)
go func() {
terminatedResult, tErr = h.storage.CompleteTags(ctx, terminatedQuery, opts)
wg.Done()
}()
if terminatedQuery != nil {
// Sometimes we only perform the child query, so only perform
// terminated query if not nil.
wg.Add(1)
go func() {
terminatedResult, tErr = h.storage.CompleteTags(ctx, terminatedQuery, opts)
wg.Done()
}()
}
// Always perform child query.
wg.Add(1)
go func() {
childResult, cErr = h.storage.CompleteTags(ctx, childQuery, opts)
wg.Done()
Expand All @@ -152,7 +172,11 @@ func (h *grahiteFindHandler) ServeHTTP(
return
}

meta := terminatedResult.Metadata.CombineMetadata(childResult.Metadata)
meta := childResult.Metadata
if terminatedResult != nil {
meta = terminatedResult.Metadata.CombineMetadata(childResult.Metadata)
}

// NB: merge results from both queries to specify which series have children
seenMap, err := mergeTags(terminatedResult, childResult)
if err != nil {
Expand Down
37 changes: 36 additions & 1 deletion src/query/api/v1/handler/graphite/find_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"

"github.com/m3db/m3/src/query/errors"
Expand Down Expand Up @@ -96,12 +97,46 @@ func parseFindParamsToQueries(r *http.Request) (
xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'until': %s", untilString))
}

matchers, err := graphitestorage.TranslateQueryToMatchersWithTerminator(query)
matchers, queryType, err := graphitestorage.TranslateQueryToMatchersWithTerminator(query)
if err != nil {
return nil, nil, "",
xerrors.NewInvalidParamsError(fmt.Errorf("invalid 'query': %s", query))
}

switch queryType {
case graphitestorage.StarStarUnterminatedTranslatedQuery:
// Translated query for "**" has unterminated search for children
// terms, we are only going to do a single search and assume all
// results that come back have also children in the graphite path
// tree (since it's very expensive to check if each result that comes
// back is a child or leaf node and "**" in a find query is typically
// only used for template variables rather than searching for metric
// results, which is the only use case isLeaf/hasChildren is useful).
// Note: Filter to all graphite tags that appears at the last node
// or greater than that (we use 100 as an arbitrary upper bound).
maxPathIndexes := 100
filter := make([][]byte, 0, maxPathIndexes)
parts := 1 + strings.Count(query, ".")
firstPathIndex := parts - 1
for i := firstPathIndex; i < firstPathIndex+maxPathIndexes; i++ {
filter = append(filter, graphite.TagName(i))
}
childQuery := &storage.CompleteTagsQuery{
CompleteNameOnly: false,
FilterNameTags: filter,
TagMatchers: matchers,
Start: xtime.ToUnixNano(from),
End: xtime.ToUnixNano(until),
}
return nil, childQuery, query, nil
case graphitestorage.TerminatedTranslatedQuery:
// Default type of translated query, explicitly craft queries for
// a terminated part of the query and a child part of the query.
break
default:
return nil, nil, "", fmt.Errorf("unknown query type: %v", queryType)
}

// NB: Filter will always be the second last term in the matchers, and the
// matchers should always have a length of at least 2 (term + terminator)
// so this is a sanity check and unexpected in actual execution.
Expand Down
Loading