-
Notifications
You must be signed in to change notification settings - Fork 453
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] Fanout options to select namespaces #1328
Changes from 5 commits
1d4d844
ed332f6
148e9ce
8fa62a3
fbe2219
fa79ff0
343898f
a7133a1
9567ae0
a2d0839
077d2c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,8 +130,14 @@ func (s *m3WrappedStore) FetchByQuery( | |
m3ctx, cancel := context.WithTimeout(ctx.RequestContext(), opts.Timeout) | ||
defer cancel() | ||
|
||
m3result, err := s.m3.Fetch(m3ctx, m3query, | ||
storage.NewFetchOptions()) | ||
fetchOptions := storage.NewFetchOptions() | ||
fetchOptions.FanoutOptions = &storage.FanoutOptions{ | ||
FanoutUnaggregated: storage.FanoutForceDisable, | ||
FanoutAggregated: storage.FanoutForceEnable, | ||
FanoutAggregatedOptimized: storage.FanoutForceEnable, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want this to be |
||
} | ||
|
||
m3result, err := s.m3.Fetch(m3ctx, m3query, fetchOptions) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -207,7 +207,12 @@ func (s *m3storage) fetchCompressed( | |
// cluster that can completely fulfill this range and then prefer the | ||
// highest resolution (most fine grained) results. | ||
// This needs to be optimized, however this is a start. | ||
fanout, namespaces, err := s.resolveClusterNamespacesForQuery(query.Start, query.End) | ||
fanout, namespaces, err := s.resolveClusterNamespacesForQuery( | ||
query.Start, | ||
query.End, | ||
options.FanoutOptions, | ||
) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -507,27 +512,46 @@ func (s *m3storage) writeSingle( | |
func (s *m3storage) resolveClusterNamespacesForQuery( | ||
start time.Time, | ||
end time.Time, | ||
opts *storage.FanoutOptions, | ||
) (queryFanoutType, ClusterNamespaces, error) { | ||
now := s.nowFn() | ||
|
||
unaggregated := s.clusters.UnaggregatedClusterNamespace() | ||
unaggregatedRetention := unaggregated.Options().Attributes().Retention | ||
unaggregatedStart := now.Add(-1 * unaggregatedRetention) | ||
if unaggregatedStart.Before(start) || unaggregatedStart.Equal(start) { | ||
// Highest resolution is unaggregated, return if it can fulfill it | ||
return namespaceCoversAllQueryRange, ClusterNamespaces{unaggregated}, nil | ||
type unaggregatedNamespaceDetails struct { | ||
unaggregated ClusterNamespace | ||
retention time.Duration | ||
enabled bool | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe move this type to top of this file? |
||
|
||
var ( | ||
unaggregatedNamespace unaggregatedNamespaceDetails | ||
) | ||
|
||
if opts.FanoutUnaggregated != storage.FanoutForceDisable { | ||
unaggregated := s.clusters.UnaggregatedClusterNamespace() | ||
unaggregatedRetention := unaggregated.Options().Attributes().Retention | ||
unaggregatedStart := now.Add(-1 * unaggregatedRetention) | ||
if unaggregatedStart.Before(start) || unaggregatedStart.Equal(start) { | ||
// Highest resolution is unaggregated, return if it can fulfill it | ||
return namespaceCoversAllQueryRange, ClusterNamespaces{unaggregated}, nil | ||
} | ||
|
||
unaggregatedNamespace.unaggregated = unaggregated | ||
unaggregatedNamespace.retention = unaggregatedRetention | ||
unaggregatedNamespace.enabled = true | ||
} | ||
|
||
// First determine if any aggregated clusters span the whole query range, if | ||
// so that's the most optimal strategy, choose the most granular resolution | ||
// that can and fan out to any partial aggregated namespaces that may holder | ||
// even more granular resolutions | ||
var r reusedAggregatedNamespaceSlices | ||
r = s.aggregatedNamespaces(r, func(namespace ClusterNamespace) bool { | ||
// Include only if can fulfill the entire time range of the query | ||
clusterStart := now.Add(-1 * namespace.Options().Attributes().Retention) | ||
return clusterStart.Before(start) || clusterStart.Equal(start) | ||
}) | ||
r = s.aggregatedNamespaces(r, | ||
func(namespace ClusterNamespace) bool { | ||
// Include only if can fulfill the entire time range of the query | ||
clusterStart := now.Add(-1 * namespace.Options().Attributes().Retention) | ||
return clusterStart.Before(start) || clusterStart.Equal(start) | ||
}, | ||
opts) | ||
|
||
if len(r.completeAggregated) > 0 { | ||
// Return the most granular completed aggregated namespace and | ||
|
@@ -556,45 +580,69 @@ func (s *m3storage) resolveClusterNamespacesForQuery( | |
// as much data as possible, along with any partially aggregated namespaces | ||
// that have either same retention and lower resolution or longer retention | ||
// than the complete aggregated namespace | ||
r = s.aggregatedNamespaces(r, nil) | ||
r = s.aggregatedNamespaces(r, nil, opts) | ||
|
||
if len(r.completeAggregated) == 0 { | ||
// Absolutely no complete aggregated namespaces, need to fanout to all | ||
// partial aggregated namespaces as well as the unaggregated cluster | ||
// as we have no idea who has the longest retention | ||
result := append(r.partialAggregated, unaggregated) | ||
// as we have no idea who has the longest retention. | ||
result := r.partialAggregated | ||
if unaggregatedNamespace.enabled { | ||
result = append(result, unaggregatedNamespace.unaggregated) | ||
} | ||
|
||
// Need to change namespaceCoversAllQueryRange if all these namespaces | ||
// cover the entire query range, since if that is the case we just | ||
// want to choose the namespace with the best resolution. | ||
allFulfillsRange := true | ||
for _, n := range result { | ||
clusterStart := now.Add(-1 * n.Options().Attributes().Retention) | ||
fulfillsRange := clusterStart.Before(start) || clusterStart.Equal(start) | ||
if !fulfillsRange { | ||
allFulfillsRange = false | ||
break | ||
} | ||
} | ||
|
||
if allFulfillsRange { | ||
return namespaceCoversAllQueryRange, result, nil | ||
} | ||
|
||
return namespaceCoversPartialQueryRange, result, nil | ||
} | ||
|
||
// Return the longest retention aggregated namespace and | ||
// any potentially more granular or longer retention partial | ||
// aggregated namespaces | ||
// aggregated namespaces. | ||
sort.Stable(sort.Reverse(ClusterNamespacesByRetentionAsc(r.completeAggregated))) | ||
|
||
// Take longest retention complete aggregated namespace or the unaggregated | ||
// cluster if that is longer than the longest aggregated namespace | ||
// cluster if that is longer than the longest aggregated namespace. | ||
result := r.completeAggregated[:1] | ||
completedAttrs := result[0].Options().Attributes() | ||
if completedAttrs.Retention <= unaggregatedRetention { | ||
// If the longest aggregated cluster for some reason has lower retention | ||
// than the unaggregated cluster then we prefer the unaggregated cluster | ||
// as it has a complete data set and is always the most granular | ||
result[0] = unaggregated | ||
completedAttrs = unaggregated.Options().Attributes() | ||
if unaggregatedNamespace.enabled { | ||
if completedAttrs.Retention <= unaggregatedNamespace.retention { | ||
// If the longest aggregated cluster for some reason has lower retention | ||
// than the unaggregated cluster then we prefer the unaggregated cluster | ||
// as it has a complete data set and is always the most granular. | ||
unaggregated := unaggregatedNamespace.unaggregated | ||
result[0] = unaggregated | ||
completedAttrs = unaggregated.Options().Attributes() | ||
} | ||
} | ||
|
||
// Take any partially aggregated namespaces with longer retention or | ||
// same retention with more granular resolution that may contain | ||
// a matching metric | ||
// a matching metric. | ||
for _, n := range r.partialAggregated { | ||
if n.Options().Attributes().Retention > completedAttrs.Retention { | ||
// Higher retention | ||
// Higher retention. | ||
result = append(result, n) | ||
continue | ||
} | ||
if n.Options().Attributes().Retention == completedAttrs.Retention && | ||
n.Options().Attributes().Resolution < completedAttrs.Resolution { | ||
// Same retention but more granular resolution | ||
// Same retention but more granular resolution. | ||
result = append(result, n) | ||
continue | ||
} | ||
|
@@ -611,22 +659,32 @@ type reusedAggregatedNamespaceSlices struct { | |
func (s *m3storage) aggregatedNamespaces( | ||
slices reusedAggregatedNamespaceSlices, | ||
filter func(ClusterNamespace) bool, | ||
opts *storage.FanoutOptions, | ||
) reusedAggregatedNamespaceSlices { | ||
all := s.clusters.ClusterNamespaces() | ||
|
||
// Reset reused slices as necessary | ||
if slices.completeAggregated == nil { | ||
slices.completeAggregated = make([]ClusterNamespace, 0, len(all)) | ||
} | ||
|
||
slices.completeAggregated = slices.completeAggregated[:0] | ||
if slices.partialAggregated == nil { | ||
slices.partialAggregated = make([]ClusterNamespace, 0, len(all)) | ||
} | ||
|
||
slices.partialAggregated = slices.partialAggregated[:0] | ||
if opts.FanoutAggregated == storage.FanoutForceDisable { | ||
// Force disable fanning out to any aggregated namespaces. | ||
return slices | ||
} | ||
|
||
// Otherwise the default and force enable is to fanout and treat | ||
// the aggregated namespaces differently (depending on whether they | ||
// have all the data). | ||
for _, namespace := range all { | ||
opts := namespace.Options() | ||
if opts.Attributes().MetricsType != storage.AggregatedMetricsType { | ||
nsOpts := namespace.Options() | ||
if nsOpts.Attributes().MetricsType != storage.AggregatedMetricsType { | ||
// Not an aggregated cluster | ||
continue | ||
} | ||
|
@@ -635,8 +693,20 @@ func (s *m3storage) aggregatedNamespaces( | |
continue | ||
} | ||
|
||
downsampleOpts, err := opts.DownsampleOptions() | ||
if err != nil || !downsampleOpts.All { | ||
downsampleOpts, err := nsOpts.DownsampleOptions() | ||
if err != nil { | ||
continue | ||
} | ||
|
||
if opts.FanoutAggregatedOptimized == storage.FanoutForceDisable { | ||
// If we disable the optimization of sometimes knowing we can eclipse | ||
// the partially aggregated namespaces, then we treat all namespaces | ||
// as potentially not having the complete set of data. | ||
slices.partialAggregated = append(slices.partialAggregated, namespace) | ||
continue | ||
} | ||
|
||
if !downsampleOpts.All { | ||
// Cluster does not contain all data, include as part of fan out | ||
// but separate from | ||
slices.partialAggregated = append(slices.partialAggregated, namespace) | ||
|
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.
Hm, should we specifying Default perhaps here?