Remote path expression bulk fetch cache #2
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This is a proof of concept. It is working, but in a hacky state, so do not merge yet :-)
Note that sadly, both the backend and the frontend need to run the new code to test this, I had to add a field to the backend response to be able to map the series it returns to the initial pathExpression (or target:
*.*.*.metricName
)The goal of this is to reduce the amount of queries between the frontend and the backends, as this is believed to be the primary source of slowdown/latency in our setups.
Background
Here's an example of that latency in action.
When serving a query containing a few wildcards and a couple metrics, for example:
target=*.*.*.cpuUsage&target=movingAverage(*.*.*.testMetrics)
, older version of graphite (0.9.x before November 2014) would query each backends with the following queries:/metric/find
: to discover what metrics are of which backends. That's 2 queries in our example.Then, for each matching metric, it would send a query like this:
/render?format=pickle&target=...
In our example, let say it matches 5 metrics.
So that's a total of 7 http queries per backend for a simple graph request. If there's some latency between the frontend and the backends, let's say 80ms (east-west coast), that amounts to 560ms of minimum latency/service time. We then need to add the actual request processing time on the backend and the graph generation time.
In this fork, we added a thread pool which is used to query the backends in parallel so that the latency induced by the above process would be somewhat limited. (we wait in parallel, woohoo!)
This helps a bit, but the latency is still very much noticeable.
Then, earlier this month, there was a patch committed upstream which brought 'bulk querying' between the frontend and the backends. With that patch, the frontend skips the 'metric discovery' step mentioned above and does one request to the backends per target.
In the above example, instead of doing 7 queries, it would only do 2 queries. So we'd have a latency of ~160ms
Patch description
The current pull request builds on that, but the approach is a bit more radical.
The idea is to fetch all metrics in a single query, reducing the latency to the minimum, 80ms in our example.
It sounds simple and obvious, but it is not exactly straightforward because of the way graphite does its parsing / querying. Basically, graphite parses the multiple targets recursively and does the appropriate action for each target it encounters (fetch the data, fetch data for a math function then apply the math function, etc...)
I didn't want to get too deep into this and modify the request parsing, so I implemented the following:
pathExpressions
(metric name/patterns) from the targetspathExpressions
from all backends and store the result in a hash table keyed per backend and per metric name + timerange for easy lookups:cache[remoteBackendName][originalTargetWithWildcards-startTime-endTime] = [ series_from_remote_backend]
requestContext
(which has the targets list, startTime, endTime, etc...)fetchData
method (which, surprise!, fetches data from local and remote sources) do a hashtable lookup for the requested data.If the data is there, or if there is no data but a prefetch call was made for that pathExpr, skip the remote fetch and use the data from the cache.
If there is a cache miss, do a regular remote fetch. This case is pretty rare, it happens when functions needs data out of the timerange of the original query. For example:
timeshift
,movingAverage
, etc.So, here it is. It is a bit hacky in the sense that I hammered the bulk fetching into the
RemoteNode.fetch
function and it doesn't quite feel right, but other than that it seems to work.I haven't done stress testing yet so I do not know if it is leaking or anything, but I did get some good result.
Here's some numbers. 2 backends <1ms latency, 5 different metrics with no wildcards.
Without prefetch:
With prefetch:
I did not really expect an improvement in that case since this was on a lan, but hey, I won't complain ;-)
Here's some numbers for a more real world case:
Request containing 6 metrics and a few math functions:
Result without patch:
With patch:
The improvement is pretty massive, more than 2x.
Can I get a review of this? I'd appreciate idea/feedback on the hacky parts so we could get this in a better shape and send it upstream.
Thx
@nicolaskruchten @marccardinal