public async Task <TTValues[]> GetAsyncInternal <TTValues>(DateTime?from = null, DateTime?to = null, int start = 0, int pageSize = int.MaxValue, CancellationToken token = default) where TTValues : TimeSeriesEntry { TimeSeriesRangeResult <TTValues> rangeResult; from = from?.EnsureUtc(); to = to?.EnsureUtc(); if (pageSize == 0) { return(Array.Empty <TTValues>()); } if (Session.TimeSeriesByDocId.TryGetValue(DocId, out var cache) && cache.TryGetValue(Name, out var ranges) && ranges.Count > 0) { if (ranges[0].From > to || ranges[ranges.Count - 1].To < from) { // the entire range [from, to] is out of cache bounds // e.g. if cache is : [[2,3], [4,6], [8,9]] // and requested range is : [12, 15] // then ranges[ranges.Count - 1].To < from // so we need to get [12,15] from server and place it // at the end of the cache list Session.IncrementRequestCount(); rangeResult = await Session.Operations.SendAsync( new GetTimeSeriesOperation <TTValues>(DocId, Name, from, to, start, pageSize), Session._sessionInfo, token : token) .ConfigureAwait(false); if (rangeResult == null) { return(null); } if (Session.NoTracking == false) { var index = ranges[0].From > to ? 0 : ranges.Count; ranges.Insert(index, rangeResult); } return(rangeResult.Entries); } var(servedFromCache, resultToUser, mergedValues, fromRangeIndex, toRangeIndex) = await ServeFromCacheOrGetMissingPartsFromServerAndMerge(from ?? DateTime.MinValue, to ?? DateTime.MaxValue, ranges, start, pageSize, token) .ConfigureAwait(false); if (servedFromCache == false && Session.NoTracking == false) { InMemoryDocumentSessionOperations.AddToCache(Name, from ?? DateTime.MinValue, to ?? DateTime.MaxValue, fromRangeIndex, toRangeIndex, ranges, cache, mergedValues); } return(resultToUser?.Take(pageSize).Cast <TTValues>().ToArray()); } if (Session.DocumentsById.TryGetValue(DocId, out var document) && document.Metadata.TryGet(Constants.Documents.Metadata.TimeSeries, out BlittableJsonReaderArray metadataTimeSeries) && metadataTimeSeries.BinarySearch(Name, StringComparison.OrdinalIgnoreCase) < 0) { // the document is loaded in the session, but the metadata says that there is no such timeseries return(Array.Empty <TTValues>()); } Session.IncrementRequestCount(); rangeResult = await Session.Operations.SendAsync( new GetTimeSeriesOperation <TTValues>(DocId, Name, from, to, start, pageSize), Session._sessionInfo, token : token) .ConfigureAwait(false); if (rangeResult == null) { return(null); } if (Session.NoTracking == false) { if (Session.TimeSeriesByDocId.TryGetValue(DocId, out cache) == false) { Session.TimeSeriesByDocId[DocId] = cache = new Dictionary <string, List <TimeSeriesRangeResult> >(StringComparer.OrdinalIgnoreCase); } cache[Name] = new List <TimeSeriesRangeResult> { rangeResult }; } return(rangeResult.Entries); }
ServeFromCache( DateTime from, DateTime to, int start, int pageSize, Action <ITimeSeriesIncludeBuilder> includes, CancellationToken token) { var cache = Session.TimeSeriesByDocId[DocId]; var ranges = cache[Name]; // try to find a range in cache that contains [from, to] // if found, chop just the relevant part from it and return to the user. // otherwise, try to find two ranges (fromRange, toRange), // such that 'fromRange' is the last occurence for which range.From <= from // and 'toRange' is the first occurence for which range.To >= to. // At the same time, figure out the missing partial ranges that we need to get from the server. int toRangeIndex; var fromRangeIndex = -1; List <TimeSeriesRange> rangesToGetFromServer = default; for (toRangeIndex = 0; toRangeIndex < ranges.Count; toRangeIndex++) { if (ranges[toRangeIndex].From <= from) { if ((ranges[toRangeIndex].To >= to) || (ranges[toRangeIndex].Entries.Length - start >= pageSize)) { // we have the entire range in cache // we have all the range we need // or that we have all the results we need in smaller range return(ChopRelevantRange(ranges[toRangeIndex], from, to, start, pageSize)); } fromRangeIndex = toRangeIndex; continue; } // can't get the entire range from cache rangesToGetFromServer ??= new List <TimeSeriesRange>(); // add the missing part [f, t] between current range start (or 'from') // and previous range end (or 'to') to the list of ranges we need to get from server rangesToGetFromServer.Add(new TimeSeriesRange { Name = Name, From = toRangeIndex == 0 || ranges[toRangeIndex - 1].To < from ? from : ranges[toRangeIndex - 1].To, To = ranges[toRangeIndex].From <= to ? ranges[toRangeIndex].From : to }); if (ranges[toRangeIndex].To >= to) { break; } } if (toRangeIndex == ranges.Count) { // requested range [from, to] ends after all ranges in cache // add the missing part between the last range end and 'to' // to the list of ranges we need to get from server rangesToGetFromServer ??= new List <TimeSeriesRange>(); rangesToGetFromServer.Add(new TimeSeriesRange { Name = Name, From = ranges[ranges.Count - 1].To, To = to }); } // get all the missing parts from server Session.IncrementRequestCount(); var details = await Session.Operations.SendAsync( new GetMultipleTimeSeriesOperation(DocId, rangesToGetFromServer, start, pageSize, includes), Session._sessionInfo, token : token) .ConfigureAwait(false); if (includes != null) { RegisterIncludes(details); } // merge all the missing parts we got from server // with all the ranges in cache that are between 'fromRange' and 'toRange' var mergedValues = MergeRangesWithResults(from, to, ranges, fromRangeIndex, toRangeIndex, resultFromServer: details.Values[Name], out var resultToUser); if (Session.NoTracking == false) { from = details.Values[Name].Min(ts => ts.From); to = details.Values[Name].Max(ts => ts.To); InMemoryDocumentSessionOperations.AddToCache(Name, from, to, fromRangeIndex, toRangeIndex, ranges, cache, mergedValues); } return(resultToUser); }