private void FillCollectionEtags(Transaction tx, Dictionary <string, IndexTransactionCache.CollectionEtags> map) { foreach (string collection in _index.Collections) { using (Slice.From(tx.LowLevelTransaction.Allocator, collection, out Slice collectionSlice)) { var etags = new IndexTransactionCache.CollectionEtags { LastIndexedEtag = IndexStorage.ReadLastEtag(tx, IndexStorage.IndexSchema.EtagsTree, collectionSlice ), LastProcessedTombstoneEtag = IndexStorage.ReadLastEtag(tx, IndexStorage.IndexSchema.EtagsTombstoneTree, collectionSlice ) }; map[collection] = etags; } } var referencedCollections = _index.GetReferencedCollections(); if (referencedCollections == null || referencedCollections.Count == 0) { return; } foreach (var(src, collections) in referencedCollections) { var collectionEtags = map[src]; collectionEtags.LastReferencedEtags ??= new Dictionary <string, IndexTransactionCache.ReferenceCollectionEtags>(StringComparer.OrdinalIgnoreCase); foreach (var collectionName in collections) { collectionEtags.LastReferencedEtags[collectionName.Name] = new IndexTransactionCache.ReferenceCollectionEtags { LastEtag = IndexStorage.ReadLastProcessedReferenceEtag(tx, src, collectionName), LastProcessedTombstoneEtag = IndexStorage.ReadLastProcessedReferenceTombstoneEtag(tx, src, collectionName), }; } } }
private bool HandleDocuments(ActionType actionType, DocumentsOperationContext databaseContext, TransactionOperationContext indexContext, Lazy <IndexWriteOperation> writeOperation, IndexingStatsScope stats, int pageSize, TimeSpan maxTimeForDocumentTransactionToRemainOpen, CancellationToken token) { var moreWorkFound = false; Dictionary <string, long> lastIndexedEtagsByCollection = null; foreach (var collection in _index.Collections) { if (_referencedCollections.TryGetValue(collection, out HashSet <CollectionName> referencedCollections) == false) { continue; } if (lastIndexedEtagsByCollection == null) { lastIndexedEtagsByCollection = new Dictionary <string, long>(StringComparer.OrdinalIgnoreCase); } if (lastIndexedEtagsByCollection.TryGetValue(collection, out long lastIndexedEtag) == false) { lastIndexedEtagsByCollection[collection] = lastIndexedEtag = _indexStorage.ReadLastIndexedEtag(indexContext.Transaction, collection); } if (lastIndexedEtag == 0) // we haven't indexed yet, so we are skipping references for now { continue; } var totalProcessedCount = 0; foreach (var referencedCollection in referencedCollections) { var inMemoryStats = _index.GetReferencesStats(referencedCollection.Name); using (var collectionStats = stats.For("Collection_" + referencedCollection.Name)) { long lastReferenceEtag; switch (actionType) { case ActionType.Document: lastReferenceEtag = IndexStorage.ReadLastProcessedReferenceEtag(indexContext.Transaction.InnerTransaction, collection, referencedCollection); break; case ActionType.Tombstone: lastReferenceEtag = IndexStorage.ReadLastProcessedReferenceTombstoneEtag(indexContext.Transaction.InnerTransaction, collection, referencedCollection); break; default: throw new NotSupportedException(); } var lastEtag = lastReferenceEtag; var resultsCount = 0; var sw = new Stopwatch(); var keepRunning = true; var lastCollectionEtag = -1L; while (keepRunning) { var hasChanges = false; using (databaseContext.OpenReadTransaction()) { sw.Restart(); IEnumerable <Reference> references; switch (actionType) { case ActionType.Document: if (lastCollectionEtag == -1) { lastCollectionEtag = _index.GetLastDocumentEtagInCollection(databaseContext, collection); } references = _documentsStorage .GetDocumentsFrom(databaseContext, referencedCollection.Name, lastEtag + 1, 0, pageSize, DocumentFields.Id | DocumentFields.Etag) .Select(document => { _reference.Key = document.Id; _reference.Etag = document.Etag; return(_reference); }); break; case ActionType.Tombstone: if (lastCollectionEtag == -1) { lastCollectionEtag = _index.GetLastTombstoneEtagInCollection(databaseContext, collection); } references = _documentsStorage .GetTombstonesFrom(databaseContext, referencedCollection.Name, lastEtag + 1, 0, pageSize) .Select(tombstone => { _reference.Key = tombstone.LowerId; _reference.Etag = tombstone.Etag; return(_reference); }); break; default: throw new NotSupportedException(); } var isTombstone = actionType == ActionType.Tombstone; foreach (var referencedDocument in references) { lastEtag = referencedDocument.Etag; hasChanges = true; inMemoryStats.UpdateLastEtag(lastEtag, isTombstone); var documents = GetDocumentFromCollectionThatReference(databaseContext, indexContext, collection, referencedDocument, lastIndexedEtag); using (var docsEnumerator = _index.GetMapEnumerator(documents, collection, indexContext, collectionStats, _index.Type)) { while (docsEnumerator.MoveNext(out IEnumerable mapResults)) { token.ThrowIfCancellationRequested(); totalProcessedCount++; collectionStats.RecordMapReferenceAttempt(); var current = docsEnumerator.Current; stats.RecordDocumentSize(current.Data.Size); try { var numberOfResults = _index.HandleMap(current.LowerId, current.Id, mapResults, writeOperation, indexContext, collectionStats); resultsCount += numberOfResults; collectionStats.RecordMapReferenceSuccess(); _index.MapsPerSec.MarkSingleThreaded(numberOfResults); } catch (Exception e) when(e.IsIndexError()) { docsEnumerator.OnError(); _index.ErrorIndexIfCriticalException(e); collectionStats.RecordMapReferenceError(); if (_logger.IsInfoEnabled) { _logger.Info($"Failed to execute mapping function on '{current.Id}' for '{_index.Name}'.", e); } collectionStats.AddMapReferenceError(current.Id, $"Failed to execute mapping function on {current.Id}. Exception: {e}"); } _index.UpdateThreadAllocations(indexContext, writeOperation, stats, updateReduceStats: false); } } if (CanContinueBatch(databaseContext, indexContext, collectionStats, writeOperation, lastEtag, lastCollectionEtag, totalProcessedCount) == false) { keepRunning = false; break; } if (totalProcessedCount >= pageSize) { keepRunning = false; break; } if (MapDocuments.MaybeRenewTransaction(databaseContext, sw, _configuration, ref maxTimeForDocumentTransactionToRemainOpen)) { break; } } if (hasChanges == false) { break; } } } if (lastReferenceEtag == lastEtag) { // the last referenced etag hasn't changed continue; } moreWorkFound = true; if (_logger.IsInfoEnabled) { _logger.Info($"Executed handle references for '{_index.Name}' index and '{referencedCollection.Name}' collection. " + $"Got {resultsCount:#,#;;0} map results in {collectionStats.Duration.TotalMilliseconds:#,#;;0} ms."); } switch (actionType) { case ActionType.Document: _indexStorage.WriteLastReferenceEtag(indexContext.Transaction, collection, referencedCollection, lastEtag); break; case ActionType.Tombstone: _indexStorage.WriteLastReferenceTombstoneEtag(indexContext.Transaction, collection, referencedCollection, lastEtag); break; default: throw new NotSupportedException(); } } } } return(moreWorkFound); }