private Task <BoolResult> RegisterLocationAsync( OperationContext context, IReadOnlyList <ShortHashWithSize> contentHashes, MachineId machineId, [CallerMemberName] string caller = null) { const int operationsPerHash = 3; var hashBatchSize = Math.Max(1, Configuration.RedisBatchPageSize / operationsPerHash); return(context.PerformOperationAsync( Tracer, async() => { foreach (var page in contentHashes.GetPages(hashBatchSize)) { var batchResult = await RaidedRedis.ExecuteRedisAsync(context, async(redisDb, token) => { Counters[GlobalStoreCounters.RegisterLocalLocationHashCount].Add(page.Count); int requiresSetBitCount; ConcurrentBitArray requiresSetBit; if (Configuration.UseOptimisticRegisterLocalLocation) { requiresSetBitCount = 0; requiresSetBit = new ConcurrentBitArray(page.Count); var redisBatch = redisDb.CreateBatch(RedisOperation.RegisterLocalSetNonExistentHashEntries); // Perform initial pass to set redis entries in single operation. Fallback to more elaborate // flow where we use SetBit + KeyExpire foreach (var indexedHash in page.WithIndices()) { var hash = indexedHash.value; var key = GetRedisKey(hash.Hash); redisBatch.AddOperationAndTraceIfFailure(context, key, async batch => { bool set = await batch.StringSetAsync(key, ContentLocationEntry.ConvertSizeAndMachineIdToRedisValue(hash.Size, machineId), Configuration.LocationEntryExpiry, When.NotExists); if (!set) { requiresSetBit[indexedHash.index] = true; Interlocked.Increment(ref requiresSetBitCount); } return set; }, operationName: "ConvertSizeAndMachineIdToRedisValue"); } var result = await redisDb.ExecuteBatchOperationAsync(context, redisBatch, token); if (!result || requiresSetBitCount == 0) { return result; } } else { requiresSetBitCount = page.Count; requiresSetBit = null; } // Some keys already exist and require that we set the bit and update the expiry on the existing entry using (Counters[GlobalStoreCounters.RegisterLocalLocationUpdate].Start()) { Counters[GlobalStoreCounters.RegisterLocalLocationUpdateHashCount].Add(requiresSetBitCount); var updateRedisBatch = redisDb.CreateBatch(RedisOperation.RegisterLocalSetHashEntries); foreach (var hash in page.Where((h, index) => requiresSetBit?[index] ?? true)) { var key = GetRedisKey(hash.Hash); updateRedisBatch.AddOperationAndTraceIfFailure( context, key, batch => SetLocationBitAndExpireAsync(context, batch, key, hash, machineId), operationName: "SetLocationBitAndExpireAsync"); } return await redisDb.ExecuteBatchOperationAsync(context, updateRedisBatch, token); } }, Configuration.RetryWindow); if (!batchResult) { return batchResult; } } return BoolResult.Success; }, Counters[GlobalStoreCounters.RegisterLocalLocation], caller: caller, traceErrorsOnly: true)); }
/// <inheritdoc /> public Task <Result <IReadOnlyList <ContentLocationEntry> > > GetBulkAsync(OperationContext context, IReadOnlyList <ShortHash> contentHashes) { return(context.PerformOperationAsync( Tracer, async() => { var results = new ContentLocationEntry[contentHashes.Count]; UnixTime now = _clock.UtcNow; int dualResultCount = 0; foreach (var page in contentHashes.AsIndexed().GetPages(Configuration.RedisBatchPageSize)) { var batchResult = await RaidedRedis.ExecuteRedisAsync(context, async(redisDb, token) => { var redisBatch = redisDb.CreateBatch(RedisOperation.GetBulkGlobal); foreach (var indexedHash in page) { var key = GetRedisKey(indexedHash.Item); redisBatch.AddOperationAndTraceIfFailure(context, key, async batch => { var redisEntry = await batch.StringGetAsync(key); ContentLocationEntry entry; if (redisEntry.IsNullOrEmpty) { entry = ContentLocationEntry.Missing; } else { entry = ContentLocationEntry.FromRedisValue(redisEntry, now, missingSizeHandling: true); } var originalEntry = Interlocked.CompareExchange(ref results[indexedHash.Index], entry, null); if (originalEntry != null) { // Existing entry was there. Merge the entries. entry = ContentLocationEntry.MergeEntries(entry, originalEntry); Interlocked.Exchange(ref results[indexedHash.Index], entry); Interlocked.Increment(ref dualResultCount); } return Unit.Void; }); } // TODO ST: now this operation may fail with TaskCancelledException. But this should be traced differently! return await redisDb.ExecuteBatchOperationAsync(context, redisBatch, token); }, Configuration.RetryWindow); if (!batchResult) { return new Result <IReadOnlyList <ContentLocationEntry> >(batchResult); } } if (RaidedRedis.HasSecondary) { Counters[GlobalStoreCounters.GetBulkEntrySingleResult].Add(contentHashes.Count - dualResultCount); } return Result.Success <IReadOnlyList <ContentLocationEntry> >(results); }, Counters[GlobalStoreCounters.GetBulk], traceErrorsOnly: true)); }