PageRenderTime 58ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/Raven.Database/Indexing/IndexStorage.cs

http://github.com/ayende/ravendb
C# | 1754 lines | 1407 code | 292 blank | 55 comment | 249 complexity | 877dd8ac0a2d3580c9b897b6375b621f MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Apache-2.0, BSD-3-Clause, CC-BY-SA-3.0
  1. //-----------------------------------------------------------------------
  2. // <copyright file="IndexStorage.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Concurrent;
  8. using System.Collections.Generic;
  9. using System.ComponentModel.Composition;
  10. using System.Diagnostics;
  11. using System.Globalization;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Runtime.ConstrainedExecution;
  15. using System.Text;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. using Lucene.Net.Analysis;
  19. using Lucene.Net.Index;
  20. using Lucene.Net.Search;
  21. using Lucene.Net.Store;
  22. using Lucene.Net.Util;
  23. using Raven.Abstractions;
  24. using Raven.Abstractions.Data;
  25. using Raven.Abstractions.Exceptions;
  26. using Raven.Abstractions.Extensions;
  27. using Raven.Abstractions.Indexing;
  28. using Raven.Abstractions.Logging;
  29. using Raven.Abstractions.MEF;
  30. using Raven.Abstractions.Util;
  31. using Raven.Database.Actions;
  32. using Raven.Database.Config;
  33. using Raven.Database.Data;
  34. using Raven.Database.Extensions;
  35. using Raven.Database.Impl;
  36. using Raven.Database.Linq;
  37. using Raven.Database.Plugins;
  38. using Raven.Database.Queries;
  39. using Raven.Database.Storage;
  40. using Raven.Database.Util;
  41. using Raven.Imports.Newtonsoft.Json;
  42. using Raven.Imports.Newtonsoft.Json.Linq;
  43. using Raven.Json.Linq;
  44. using Constants = Raven.Abstractions.Data.Constants;
  45. using Directory = Lucene.Net.Store.Directory;
  46. namespace Raven.Database.Indexing
  47. {
  48. /// <summary>
  49. /// Thread safe, single instance for the entire application
  50. /// </summary>
  51. public class IndexStorage : CriticalFinalizerObject, IDisposable
  52. {
  53. private readonly DocumentDatabase documentDatabase;
  54. private const string IndexVersion = "2.0.0.1";
  55. private const string MapReduceIndexVersion = "2.5.0.1";
  56. private readonly IndexDefinitionStorage indexDefinitionStorage;
  57. private readonly InMemoryRavenConfiguration configuration;
  58. private readonly string path;
  59. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  60. private static readonly ILog startupLog = LogManager.GetLogger(typeof(IndexStorage).FullName + ".Startup");
  61. private readonly Analyzer dummyAnalyzer = new SimpleAnalyzer();
  62. private DateTime latestPersistedQueryTime;
  63. private readonly FileStream crashMarker;
  64. private ConcurrentDictionary<int, Index> indexes =
  65. new ConcurrentDictionary<int, Index>();
  66. public class RegisterLowMemoryHandler : ILowMemoryHandler
  67. {
  68. static RegisterLowMemoryHandler _instance;
  69. public static void Setup()
  70. {
  71. if (_instance != null)
  72. return;
  73. lock (typeof(RegisterLowMemoryHandler))
  74. {
  75. if (_instance != null)
  76. return;
  77. _instance = new RegisterLowMemoryHandler();
  78. MemoryStatistics.RegisterLowMemoryHandler(_instance);
  79. }
  80. }
  81. public LowMemoryHandlerStatistics HandleLowMemory()
  82. {
  83. FieldCache_Fields.DEFAULT.PurgeAllCaches();
  84. var stat = GetStats();
  85. return new LowMemoryHandlerStatistics
  86. {
  87. Name = stat.Name,
  88. DatabaseName = stat.DatabaseName,
  89. Summary = "Field Cache forcibly expunges all entries from the underlying caches"
  90. };
  91. }
  92. public LowMemoryHandlerStatistics GetStats()
  93. {
  94. var cacheEntries = FieldCache_Fields.DEFAULT.GetCacheEntries();
  95. var memorySum = cacheEntries.Sum(x =>
  96. {
  97. var curEstimator = new RamUsageEstimator(false);
  98. return curEstimator.EstimateRamUsage(x);
  99. });
  100. return new LowMemoryHandlerStatistics
  101. {
  102. Name = "LuceneLowMemoryHandler",
  103. EstimatedUsedMemory = memorySum,
  104. Metadata = new
  105. {
  106. CachedEntriesAmount = cacheEntries.Length
  107. }
  108. };
  109. }
  110. }
  111. public IndexStorage(IndexDefinitionStorage indexDefinitionStorage, InMemoryRavenConfiguration configuration, DocumentDatabase documentDatabase)
  112. {
  113. try
  114. {
  115. RegisterLowMemoryHandler.Setup();
  116. this.indexDefinitionStorage = indexDefinitionStorage;
  117. this.configuration = configuration;
  118. this.documentDatabase = documentDatabase;
  119. path = configuration.IndexStoragePath;
  120. if (System.IO.Directory.Exists(path) == false && configuration.RunInMemory == false)
  121. System.IO.Directory.CreateDirectory(path);
  122. if (configuration.RunInMemory == false)
  123. {
  124. var crashMarkerPath = Path.Combine(path, "indexing.crash-marker");
  125. if (File.Exists(crashMarkerPath))
  126. {
  127. // the only way this can happen is if we crashed because of a power outage
  128. // in this case, we consider all open indexes to be corrupt and force them
  129. // to be reset. This is because to get better perf, we don't flush the files to disk,
  130. // so in the case of a power outage, we can't be sure that there wasn't still stuff in
  131. // the OS buffer that wasn't written yet.
  132. configuration.ResetIndexOnUncleanShutdown = true;
  133. }
  134. // The delete on close ensures that the only way this file will exists is if there was
  135. // a power outage while the server was running.
  136. crashMarker = File.Create(crashMarkerPath, 16, FileOptions.DeleteOnClose);
  137. }
  138. if (log.IsDebugEnabled)
  139. log.Debug("Start opening indexes. There are {0} indexes that need to be loaded", indexDefinitionStorage.IndexNames.Length);
  140. BackgroundTaskExecuter.Instance.ExecuteAllInterleaved(documentDatabase.WorkContext, indexDefinitionStorage.IndexNames,
  141. name =>
  142. {
  143. var index = OpenIndex(name, onStartup: true, forceFullIndexCheck: false);
  144. if (index != null)
  145. indexes.TryAdd(index.IndexId, index);
  146. if (startupLog.IsDebugEnabled)
  147. startupLog.Debug("{0}/{1} indexes loaded", indexes.Count, indexDefinitionStorage.IndexNames.Length);
  148. });
  149. if (log.IsDebugEnabled)
  150. log.Debug("Index storage initialized. All indexes have been opened.");
  151. }
  152. catch (Exception e)
  153. {
  154. log.WarnException("Could not create index storage", e);
  155. try
  156. {
  157. Dispose();
  158. }
  159. catch (Exception ex)
  160. {
  161. log.FatalException("Failed to dispose when already getting an error during ctor", ex);
  162. }
  163. throw;
  164. }
  165. }
  166. private Index OpenIndex(string indexName, bool onStartup, bool forceFullIndexCheck)
  167. {
  168. if (indexName == null)
  169. throw new ArgumentNullException("indexName");
  170. if (startupLog.IsDebugEnabled)
  171. startupLog.Debug("Loading saved index {0}", indexName);
  172. var indexDefinition = indexDefinitionStorage.GetIndexDefinition(indexName);
  173. if (indexDefinition == null)
  174. return null;
  175. Index indexImplementation = null;
  176. bool resetTried = false;
  177. bool recoveryTried = false;
  178. string[] keysToDeleteAfterRecovery = null;
  179. while (true)
  180. {
  181. Directory luceneDirectory = null;
  182. try
  183. {
  184. luceneDirectory = OpenOrCreateLuceneDirectory(indexDefinition, createIfMissing: resetTried, forceFullExistingIndexCheck: forceFullIndexCheck);
  185. indexImplementation = CreateIndexImplementation(indexDefinition, luceneDirectory);
  186. CheckIndexState(luceneDirectory, indexDefinition, indexImplementation, resetTried);
  187. if (forceFullIndexCheck)
  188. {
  189. // the above index check might pass however an index writer creation can still throw an exception
  190. // so we need to check it here to avoid crashing in runtime
  191. new IndexWriter(luceneDirectory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose();
  192. }
  193. var simpleIndex = indexImplementation as SimpleIndex; // no need to do this on m/r indexes, since we rebuild them from saved data anyway
  194. if (simpleIndex != null && keysToDeleteAfterRecovery != null)
  195. {
  196. // remove keys from index that were deleted after creating commit point
  197. simpleIndex.RemoveDirectlyFromIndex(keysToDeleteAfterRecovery, GetLastEtagForIndex(simpleIndex));
  198. }
  199. LoadExistingSuggestionsExtentions(indexDefinition.Name, indexImplementation);
  200. documentDatabase.TransactionalStorage.Batch(accessor =>
  201. {
  202. IndexStats indexStats = accessor.Indexing.GetIndexStats(indexDefinition.IndexId);
  203. if (indexStats != null)
  204. indexImplementation.Priority = indexStats.Priority;
  205. var read = accessor.Lists.Read("Raven/Indexes/QueryTime", indexName);
  206. if (read == null)
  207. {
  208. if (IsIdleAutoIndex(indexImplementation))
  209. indexImplementation.MarkQueried(); // prevent index abandoning right after startup
  210. return;
  211. }
  212. var dateTime = read.Data.Value<DateTime>("LastQueryTime");
  213. if (IsIdleAutoIndex(indexImplementation) && SystemTime.UtcNow - dateTime > configuration.TimeToWaitBeforeRunningAbandonedIndexes)
  214. indexImplementation.MarkQueried(); // prevent index abandoning right after startup
  215. else
  216. indexImplementation.MarkQueried(dateTime);
  217. if (dateTime > latestPersistedQueryTime)
  218. latestPersistedQueryTime = dateTime;
  219. });
  220. break;
  221. }
  222. catch (Exception e)
  223. {
  224. if (resetTried)
  225. throw new InvalidOperationException("Could not open / create index" + indexName + ", reset already tried", e);
  226. if (indexImplementation != null)
  227. indexImplementation.Dispose();
  228. if (recoveryTried == false && luceneDirectory != null && configuration.Indexing.SkipRecoveryOnStartup == false)
  229. {
  230. recoveryTried = true;
  231. startupLog.WarnException("Could not open index " + indexName + ". Trying to recover index", e);
  232. keysToDeleteAfterRecovery = TryRecoveringIndex(indexDefinition, luceneDirectory);
  233. }
  234. else
  235. {
  236. resetTried = true;
  237. startupLog.WarnException("Could not open index " + indexName + ". Recovery operation failed, forcibly resetting index", e);
  238. TryResettingIndex(indexName, indexDefinition, onStartup);
  239. }
  240. }
  241. }
  242. return indexImplementation;
  243. }
  244. private void CheckIndexState(Directory directory, IndexDefinition indexDefinition, Index index, bool resetTried)
  245. {
  246. //if (configuration.ResetIndexOnUncleanShutdown == false)
  247. // return;
  248. // 1. If commitData is null it means that there were no commits, so just in case we are resetting to Etag.Empty
  249. // 2. If no 'LastEtag' in commitData then we consider it an invalid index
  250. // 3. If 'LastEtag' is present (and valid), then resetting to it (if it is lower than lastStoredEtag)
  251. var commitData = IndexReader.GetCommitUserData(directory);
  252. if (index.IsMapReduce)
  253. CheckMapReduceIndexState(commitData, resetTried);
  254. else
  255. CheckMapIndexState(commitData, indexDefinition, index);
  256. }
  257. private void CheckMapIndexState(IDictionary<string, string> commitData, IndexDefinition indexDefinition, Index index)
  258. {
  259. string value;
  260. Etag lastEtag = null;
  261. if (commitData != null && commitData.TryGetValue("LastEtag", out value))
  262. Etag.TryParse(value, out lastEtag); // etag will be null if parsing will fail
  263. var lastStoredEtag = GetLastEtagForIndex(index) ?? Etag.Empty;
  264. lastEtag = lastEtag ?? Etag.Empty;
  265. if (EtagUtil.IsGreaterThanOrEqual(lastEtag, lastStoredEtag))
  266. return;
  267. log.Info(string.Format("Resetting index '{0} ({1})'. Last stored etag: {2}. Last commit etag: {3}.", indexDefinition.Name, index.indexId, lastStoredEtag, lastEtag));
  268. var now = SystemTime.UtcNow;
  269. ResetLastIndexedEtag(indexDefinition, lastEtag, now);
  270. }
  271. private static void CheckMapReduceIndexState(IDictionary<string, string> commitData, bool resetTried)
  272. {
  273. if (resetTried)
  274. return;
  275. string marker;
  276. long commitMarker;
  277. var valid = commitData != null
  278. && commitData.TryGetValue("Marker", out marker)
  279. && long.TryParse(marker, out commitMarker)
  280. && commitMarker == RavenIndexWriter.CommitMarker;
  281. if (valid == false)
  282. throw new InvalidOperationException("Map-Reduce index corruption detected.");
  283. }
  284. private static bool IsIdleAutoIndex(Index index)
  285. {
  286. return index.PublicName.StartsWith("Auto/") && index.Priority == IndexingPriority.Idle;
  287. }
  288. private void TryResettingIndex(string indexName, IndexDefinition indexDefinition, bool onStartup)
  289. {
  290. try
  291. {
  292. Action reset = () =>
  293. {
  294. try
  295. {
  296. documentDatabase.Indexes.DeleteIndex(indexDefinition, removeIndexReplaceDocument: false);
  297. documentDatabase.Indexes.PutNewIndexIntoStorage(indexName, indexDefinition);
  298. var indexReplaceDocumentKey = Constants.IndexReplacePrefix + indexName;
  299. var indexReplaceDocument = documentDatabase.Documents.Get(indexReplaceDocumentKey, null);
  300. if (indexReplaceDocument == null)
  301. return;
  302. documentDatabase.Documents.Put(indexReplaceDocumentKey, null, indexReplaceDocument.DataAsJson, indexReplaceDocument.Metadata, null);
  303. }
  304. catch (Exception e)
  305. {
  306. throw new InvalidOperationException("Could not finalize reseting of index: " + indexName, e);
  307. }
  308. };
  309. if (onStartup)
  310. {
  311. // we have to defer the work here until the database is actually ready for work
  312. documentDatabase.OnIndexingWiringComplete += reset;
  313. }
  314. else
  315. {
  316. reset();
  317. }
  318. var indexFullPath = Path.Combine(path, indexDefinition.IndexId.ToString(CultureInfo.InvariantCulture));
  319. IOExtensions.DeleteDirectory(indexFullPath);
  320. var suggestionsForIndex = Path.Combine(configuration.IndexStoragePath, "Raven-Suggestions", indexName);
  321. IOExtensions.DeleteDirectory(suggestionsForIndex);
  322. }
  323. catch (Exception exception)
  324. {
  325. throw new InvalidOperationException("Could not reset index " + indexName, exception);
  326. }
  327. }
  328. private string[] TryRecoveringIndex(IndexDefinition indexDefinition,
  329. Directory luceneDirectory)
  330. {
  331. string[] keysToDeleteAfterRecovery = null;
  332. if (indexDefinition.IsMapReduce == false)
  333. {
  334. IndexCommitPoint commitUsedToRestore;
  335. if (TryReusePreviousCommitPointsToRecoverIndex(luceneDirectory,
  336. indexDefinition, path,
  337. out commitUsedToRestore,
  338. out keysToDeleteAfterRecovery))
  339. {
  340. ResetLastIndexedEtag(indexDefinition, commitUsedToRestore.HighestCommitedETag, commitUsedToRestore.TimeStamp);
  341. }
  342. }
  343. else
  344. {
  345. RegenerateMapReduceIndex(luceneDirectory, indexDefinition);
  346. }
  347. return keysToDeleteAfterRecovery;
  348. }
  349. private void LoadExistingSuggestionsExtentions(string indexName, Index indexImplementation)
  350. {
  351. var suggestionsForIndex = Path.Combine(configuration.IndexStoragePath, "Raven-Suggestions", indexName);
  352. if (!System.IO.Directory.Exists(suggestionsForIndex))
  353. return;
  354. try
  355. {
  356. var directories = System.IO.Directory.GetDirectories(suggestionsForIndex);
  357. if (directories.Any(dir => dir.Contains("-")))
  358. {
  359. // Legacy handling:
  360. // Previously we had separate folder with suggestions for each triple: (field, distanceType, accuracy)
  361. // Now we have field only.
  362. // Legacy naming convention was: field-{distanceType}-{accuracy}
  363. // since when we have - (dash) in SOME folder name it seems to be legacy
  364. HandleLegacySuggestions(directories);
  365. // Refresh directories list as handling legacy might rename or delete some of them.
  366. directories = System.IO.Directory.GetDirectories(suggestionsForIndex);
  367. }
  368. foreach (var directory in directories)
  369. {
  370. IndexSearcher searcher;
  371. using (indexImplementation.GetSearcher(out searcher))
  372. {
  373. var key = Path.GetFileName(directory);
  374. var field = MonoHttpUtility.UrlDecode(key);
  375. var extension = new SuggestionQueryIndexExtension(
  376. indexImplementation,
  377. documentDatabase.WorkContext,
  378. Path.Combine(configuration.IndexStoragePath, "Raven-Suggestions", indexName, key),
  379. searcher.IndexReader.Directory() is RAMDirectory,
  380. field);
  381. indexImplementation.SetExtension(key, extension);
  382. }
  383. }
  384. }
  385. catch (Exception e)
  386. {
  387. log.WarnException("Could not open suggestions for index " + indexName + ", resetting the index", e);
  388. try
  389. {
  390. IOExtensions.DeleteDirectory(suggestionsForIndex);
  391. }
  392. catch (Exception)
  393. {
  394. // ignore the failure
  395. }
  396. throw;
  397. }
  398. }
  399. internal static void HandleLegacySuggestions(string[] directories)
  400. {
  401. var alreadySeenFields = new HashSet<string>();
  402. foreach (var directory in directories)
  403. {
  404. var key = Path.GetFileName(directory);
  405. var parentDir = System.IO.Directory.GetParent(directory).FullName;
  406. if (key.Contains("-"))
  407. {
  408. var tokens = key.Split('-');
  409. var field = tokens[0];
  410. if (alreadySeenFields.Contains(field))
  411. {
  412. log.Info("Removing legacy suggestions: {0}", directory);
  413. IOExtensions.DeleteDirectory(directory);
  414. }
  415. else
  416. {
  417. alreadySeenFields.Add(field);
  418. var newLocation = Path.Combine(parentDir, field);
  419. log.Info("Moving suggestions from: {0} to {1}", directory, newLocation);
  420. System.IO.Directory.Move(directory, newLocation);
  421. }
  422. }
  423. else
  424. {
  425. alreadySeenFields.Add(key);
  426. }
  427. }
  428. }
  429. protected Lucene.Net.Store.Directory OpenOrCreateLuceneDirectory(IndexDefinition indexDefinition, bool createIfMissing = true, bool forceFullExistingIndexCheck = false)
  430. {
  431. Lucene.Net.Store.Directory directory;
  432. if (configuration.RunInMemory ||
  433. (indexDefinition.IsMapReduce == false && // there is no point in creating map/reduce indexes in memory, we write the intermediate results to disk anyway
  434. indexDefinitionStorage.IsNewThisSession(indexDefinition) &&
  435. indexDefinition.DisableInMemoryIndexing == false &&
  436. configuration.DisableInMemoryIndexing == false &&
  437. forceFullExistingIndexCheck == false))
  438. {
  439. directory = new RAMDirectory();
  440. new IndexWriter(directory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose(); // creating index structure
  441. }
  442. else
  443. {
  444. var indexDirectory = indexDefinition.IndexId.ToString();
  445. var indexFullPath = Path.Combine(path, indexDirectory);
  446. directory = new LuceneCodecDirectory(indexFullPath, documentDatabase.IndexCodecs.OfType<AbstractIndexCodec>());
  447. if (!IndexReader.IndexExists(directory))
  448. {
  449. if (createIfMissing == false)
  450. throw new InvalidOperationException(string.Format("Index directory '{0}' does not exists for '{1}' index.", indexFullPath, indexDefinition.Name));
  451. WriteIndexVersion(directory, indexDefinition);
  452. //creating index structure if we need to
  453. new IndexWriter(directory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose();
  454. }
  455. else
  456. {
  457. EnsureIndexVersionMatches(directory, indexDefinition);
  458. if (forceFullExistingIndexCheck == false)
  459. {
  460. if (directory.FileExists("write.lock")) // force lock release, because it was still open when we shut down
  461. {
  462. IndexWriter.Unlock(directory);
  463. // for some reason, just calling unlock doesn't remove this file
  464. directory.DeleteFile("write.lock");
  465. }
  466. if (directory.FileExists("writing-to-index.lock")) // we had an unclean shutdown
  467. {
  468. if (configuration.ResetIndexOnUncleanShutdown)
  469. throw new InvalidOperationException(string.Format("Rude shutdown detected on '{0}' index in '{1}' directory.", indexDefinition.Name, indexFullPath));
  470. CheckIndexAndTryToFix(directory, indexDefinition);
  471. directory.DeleteFile("writing-to-index.lock");
  472. }
  473. }
  474. else
  475. {
  476. IndexWriter.Unlock(directory);
  477. if (directory.FileExists("write.lock"))
  478. directory.DeleteFile("write.lock");
  479. CheckIndexAndTryToFix(directory, indexDefinition);
  480. if (directory.FileExists("writing-to-index.lock"))
  481. directory.DeleteFile("writing-to-index.lock");
  482. }
  483. }
  484. }
  485. return directory;
  486. }
  487. private void RegenerateMapReduceIndex(Directory directory, IndexDefinition indexDefinition)
  488. {
  489. // remove old index data
  490. var dirOnDisk = Path.Combine(path, indexDefinition.IndexId.ToString());
  491. IOExtensions.DeleteDirectory(dirOnDisk);
  492. // initialize by new index
  493. System.IO.Directory.CreateDirectory(dirOnDisk);
  494. WriteIndexVersion(directory, indexDefinition);
  495. new IndexWriter(directory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose();
  496. var start = 0;
  497. const int take = 100;
  498. documentDatabase.TransactionalStorage.Batch(actions =>
  499. {
  500. IList<ReduceTypePerKey> reduceKeysAndTypes;
  501. do
  502. {
  503. reduceKeysAndTypes = actions.MapReduce.GetReduceKeysAndTypes(indexDefinition.IndexId, start, take).ToList();
  504. start += take;
  505. var keysToScheduleOnLevel2 =
  506. reduceKeysAndTypes.Where(x => x.OperationTypeToPerform == ReduceType.MultiStep).ToList();
  507. var keysToScheduleOnLevel0 =
  508. reduceKeysAndTypes.Where(x => x.OperationTypeToPerform == ReduceType.SingleStep).ToList();
  509. var itemsToScheduleOnLevel2 = keysToScheduleOnLevel2.Select(x => new ReduceKeyAndBucket(0, x.ReduceKey)).ToList();
  510. var itemsToScheduleOnLevel0 = new List<ReduceKeyAndBucket>();
  511. foreach (var reduceKey in keysToScheduleOnLevel0.Select(x => x.ReduceKey))
  512. {
  513. var mappedBuckets = actions.MapReduce.GetMappedBuckets(indexDefinition.IndexId, reduceKey, CancellationToken.None).Distinct();
  514. itemsToScheduleOnLevel0.AddRange(mappedBuckets.Select(x => new ReduceKeyAndBucket(x, reduceKey)));
  515. }
  516. foreach (var itemToReduce in itemsToScheduleOnLevel2)
  517. {
  518. actions.MapReduce.ScheduleReductions(indexDefinition.IndexId, 2, itemToReduce);
  519. actions.General.MaybePulseTransaction();
  520. }
  521. foreach (var itemToReduce in itemsToScheduleOnLevel0)
  522. {
  523. actions.MapReduce.ScheduleReductions(indexDefinition.IndexId, 0, itemToReduce);
  524. actions.General.MaybePulseTransaction();
  525. }
  526. } while (reduceKeysAndTypes.Count > 0);
  527. });
  528. }
  529. private void ResetLastIndexedEtag(IndexDefinition indexDefinition, Etag lastIndexedEtag, DateTime timestamp)
  530. {
  531. documentDatabase.TransactionalStorage.Batch(
  532. accessor =>
  533. accessor.Indexing.UpdateLastIndexed(indexDefinition.IndexId, lastIndexedEtag, timestamp));
  534. }
  535. internal Etag GetLastEtagForIndex(Index index)
  536. {
  537. if (index.IsMapReduce)
  538. return null;
  539. IndexStats stats = null;
  540. documentDatabase.TransactionalStorage.Batch(accessor => stats = accessor.Indexing.GetIndexStats(index.IndexId));
  541. return stats != null ? stats.LastIndexedEtag : Etag.Empty;
  542. }
  543. public static string IndexVersionFileName(IndexDefinition indexDefinition)
  544. {
  545. if (indexDefinition.IsMapReduce)
  546. return "mapReduce.version";
  547. return "index.version";
  548. }
  549. public static void WriteIndexVersion(Directory directory, IndexDefinition indexDefinition)
  550. {
  551. var version = IndexVersion;
  552. if (indexDefinition.IsMapReduce)
  553. {
  554. version = MapReduceIndexVersion;
  555. }
  556. using (var indexOutput = directory.CreateOutput(IndexVersionFileName(indexDefinition)))
  557. {
  558. indexOutput.WriteString(version);
  559. indexOutput.Flush();
  560. }
  561. }
  562. private static void EnsureIndexVersionMatches(Directory directory, IndexDefinition indexDefinition)
  563. {
  564. var versionToCheck = IndexVersion;
  565. if (indexDefinition.IsMapReduce)
  566. {
  567. versionToCheck = MapReduceIndexVersion;
  568. }
  569. var indexVersion = IndexVersionFileName(indexDefinition);
  570. if (directory.FileExists(indexVersion) == false)
  571. {
  572. throw new InvalidOperationException("Could not find " + indexVersion + " " + indexDefinition.IndexId + ", resetting index");
  573. }
  574. using (var indexInput = directory.OpenInput(indexVersion))
  575. {
  576. var versionFromDisk = indexInput.ReadString();
  577. if (versionFromDisk != versionToCheck)
  578. throw new InvalidOperationException("Index " + indexDefinition.IndexId + " is of version " + versionFromDisk +
  579. " which is not compatible with " + versionToCheck + ", resetting index");
  580. }
  581. }
  582. private static void CheckIndexAndTryToFix(Directory directory, IndexDefinition indexDefinition)
  583. {
  584. startupLog.Warn("Unclean shutdown detected on {0}, checking the index for errors. This may take a while.", indexDefinition.Name);
  585. var memoryStream = new MemoryStream();
  586. var stringWriter = new StreamWriter(memoryStream);
  587. var checkIndex = new CheckIndex(directory);
  588. if (startupLog.IsWarnEnabled)
  589. checkIndex.SetInfoStream(stringWriter);
  590. var sp = Stopwatch.StartNew();
  591. var status = checkIndex.CheckIndex_Renamed_Method();
  592. sp.Stop();
  593. if (startupLog.IsWarnEnabled)
  594. {
  595. startupLog.Warn("Checking index {0} took: {1}, clean: {2}", indexDefinition.Name, sp.Elapsed, status.clean);
  596. memoryStream.Position = 0;
  597. log.Warn(new StreamReader(memoryStream).ReadToEnd());
  598. }
  599. if (status.clean)
  600. return;
  601. startupLog.Warn("Attempting to fix index: {0}", indexDefinition.Name);
  602. sp.Restart();
  603. checkIndex.FixIndex(status);
  604. startupLog.Warn("Fixed index {0} in {1}", indexDefinition.Name, sp.Elapsed);
  605. }
  606. public void StoreCommitPoint(string indexName, IndexCommitPoint indexCommit)
  607. {
  608. if (indexCommit.SegmentsInfo == null || indexCommit.SegmentsInfo.IsIndexCorrupted)
  609. return;
  610. var directoryName = indexCommit.SegmentsInfo.Generation.ToString("0000000000000000000", CultureInfo.InvariantCulture);
  611. var commitPointDirectory = new IndexCommitPointDirectory(path, indexName, directoryName);
  612. if (System.IO.Directory.Exists(commitPointDirectory.AllCommitPointsFullPath) == false)
  613. {
  614. System.IO.Directory.CreateDirectory(commitPointDirectory.AllCommitPointsFullPath);
  615. }
  616. System.IO.Directory.CreateDirectory(commitPointDirectory.FullPath);
  617. using (var commitPointFile = File.Create(commitPointDirectory.FileFullPath))
  618. using (var sw = new StreamWriter(commitPointFile))
  619. {
  620. var jsonSerializer = JsonExtensions.CreateDefaultJsonSerializer();
  621. var textWriter = new JsonTextWriter(sw);
  622. jsonSerializer.Serialize(textWriter, indexCommit);
  623. sw.Flush();
  624. }
  625. var currentSegmentsFileName = indexCommit.SegmentsInfo.SegmentsFileName;
  626. File.Copy(Path.Combine(commitPointDirectory.IndexFullPath, currentSegmentsFileName),
  627. Path.Combine(commitPointDirectory.FullPath, currentSegmentsFileName),
  628. overwrite: true);
  629. var storedCommitPoints = System.IO.Directory.GetDirectories(commitPointDirectory.AllCommitPointsFullPath);
  630. if (storedCommitPoints.Length > configuration.MaxNumberOfStoredCommitPoints)
  631. {
  632. foreach (var toDelete in storedCommitPoints.Take(storedCommitPoints.Length - configuration.MaxNumberOfStoredCommitPoints))
  633. {
  634. IOExtensions.DeleteDirectory(toDelete);
  635. }
  636. }
  637. }
  638. public void AddDeletedKeysToCommitPoints(IndexDefinition indexDefinition, string[] deletedKeys)
  639. {
  640. var indexFullPath = Path.Combine(path, indexDefinition.IndexId.ToString());
  641. var existingCommitPoints = IndexCommitPointDirectory.ScanAllCommitPointsDirectory(indexFullPath);
  642. foreach (var commitPointDirectory in existingCommitPoints.Select(commitPoint => new IndexCommitPointDirectory(path, indexDefinition.IndexId.ToString(), commitPoint)))
  643. {
  644. using (var stream = File.Open(commitPointDirectory.DeletedKeysFile, FileMode.OpenOrCreate))
  645. {
  646. stream.Seek(0, SeekOrigin.End);
  647. using (var writer = new StreamWriter(stream))
  648. {
  649. foreach (var deletedKey in deletedKeys)
  650. {
  651. writer.WriteLine(deletedKey);
  652. }
  653. }
  654. }
  655. }
  656. }
  657. private bool TryReusePreviousCommitPointsToRecoverIndex(Directory directory, IndexDefinition indexDefinition, string indexStoragePath, out IndexCommitPoint indexCommit, out string[] keysToDelete)
  658. {
  659. indexCommit = null;
  660. keysToDelete = null;
  661. if (indexDefinition.IsMapReduce)
  662. return false;
  663. var indexFullPath = Path.Combine(indexStoragePath, indexDefinition.IndexId.ToString());
  664. var allCommitPointsFullPath = IndexCommitPointDirectory.GetAllCommitPointsFullPath(indexFullPath);
  665. if (System.IO.Directory.Exists(allCommitPointsFullPath) == false)
  666. return false;
  667. var filesInIndexDirectory = System.IO.Directory.GetFiles(indexFullPath).Select(Path.GetFileName);
  668. var existingCommitPoints =
  669. IndexCommitPointDirectory.ScanAllCommitPointsDirectory(indexFullPath);
  670. Array.Reverse(existingCommitPoints); // start from the highest generation
  671. foreach (var commitPointDirectoryName in existingCommitPoints)
  672. {
  673. try
  674. {
  675. var commitPointDirectory = new IndexCommitPointDirectory(indexStoragePath, indexDefinition.IndexId.ToString(),
  676. commitPointDirectoryName);
  677. if (TryGetCommitPoint(commitPointDirectory, out indexCommit) == false)
  678. {
  679. IOExtensions.DeleteDirectory(commitPointDirectory.FullPath);
  680. continue; // checksum is invalid, try another commit point
  681. }
  682. var missingFile =
  683. indexCommit.SegmentsInfo.ReferencedFiles.Any(
  684. referencedFile => filesInIndexDirectory.Contains(referencedFile) == false);
  685. if (missingFile)
  686. {
  687. IOExtensions.DeleteDirectory(commitPointDirectory.FullPath);
  688. continue; // there are some missing files, try another commit point
  689. }
  690. var storedSegmentsFile = indexCommit.SegmentsInfo.SegmentsFileName;
  691. // here there should be only one segments_N file, however remove all if there is more
  692. foreach (var currentSegmentsFile in System.IO.Directory.GetFiles(commitPointDirectory.IndexFullPath, "segments_*"))
  693. {
  694. File.Delete(currentSegmentsFile);
  695. }
  696. // copy old segments_N file
  697. File.Copy(Path.Combine(commitPointDirectory.FullPath, storedSegmentsFile),
  698. Path.Combine(commitPointDirectory.IndexFullPath, storedSegmentsFile), true);
  699. try
  700. {
  701. // update segments.gen file
  702. using (var genOutput = directory.CreateOutput(IndexFileNames.SEGMENTS_GEN))
  703. {
  704. genOutput.WriteInt(SegmentInfos.FORMAT_LOCKLESS);
  705. genOutput.WriteLong(indexCommit.SegmentsInfo.Generation);
  706. genOutput.WriteLong(indexCommit.SegmentsInfo.Generation);
  707. }
  708. }
  709. catch (Exception)
  710. {
  711. // here we can ignore, segments.gen is used only as fallback
  712. }
  713. if (File.Exists(commitPointDirectory.DeletedKeysFile))
  714. keysToDelete = File.ReadLines(commitPointDirectory.DeletedKeysFile).ToArray();
  715. return true;
  716. }
  717. catch (Exception ex)
  718. {
  719. startupLog.WarnException("Could not recover an index named '" + indexDefinition.IndexId +
  720. "'from segments of the following generation " + commitPointDirectoryName, ex);
  721. }
  722. }
  723. return false;
  724. }
  725. public static IndexSegmentsInfo GetCurrentSegmentsInfo(string indexName, Directory directory)
  726. {
  727. var segmentInfos = new SegmentInfos();
  728. var result = new IndexSegmentsInfo();
  729. try
  730. {
  731. segmentInfos.Read(directory);
  732. result.Generation = segmentInfos.Generation;
  733. result.SegmentsFileName = segmentInfos.GetCurrentSegmentFileName();
  734. result.ReferencedFiles = segmentInfos.Files(directory, false);
  735. }
  736. catch (CorruptIndexException ex)
  737. {
  738. log.WarnException(string.Format("Could not read segment information for an index '{0}'", indexName), ex);
  739. result.IsIndexCorrupted = true;
  740. }
  741. return result;
  742. }
  743. public static bool TryGetCommitPoint(IndexCommitPointDirectory commitPointDirectory, out IndexCommitPoint indexCommit)
  744. {
  745. using (var commitPointFile = File.OpenRead(commitPointDirectory.FileFullPath))
  746. {
  747. try
  748. {
  749. var textReader = new JsonTextReader(new StreamReader(commitPointFile));
  750. var jsonCommitPoint = RavenJObject.Load(textReader);
  751. var jsonEtag = jsonCommitPoint.Value<RavenJToken>("HighestCommitedETag");
  752. Etag recoveredEtag = null;
  753. if (jsonEtag.Type == JTokenType.Object) // backward compatibility - HighestCommitedETag is written as {"Restarts":123,"Changes":1}
  754. {
  755. jsonCommitPoint.Remove("HighestCommitedETag");
  756. recoveredEtag = new Etag(UuidType.Documents, jsonEtag.Value<long>("Restarts"), jsonEtag.Value<long>("Changes"));
  757. }
  758. indexCommit = jsonCommitPoint.JsonDeserialization<IndexCommitPoint>();
  759. if (indexCommit == null)
  760. return false;
  761. if (recoveredEtag != null)
  762. indexCommit.HighestCommitedETag = recoveredEtag;
  763. if (indexCommit.HighestCommitedETag == null || indexCommit.HighestCommitedETag.CompareTo(Etag.Empty) == 0)
  764. return false;
  765. return true;
  766. }
  767. catch (Exception e)
  768. {
  769. log.Warn("Could not get commit point from the following location {0}. Exception {1}", commitPointDirectory.FileFullPath, e);
  770. indexCommit = null;
  771. return false;
  772. }
  773. }
  774. }
  775. internal Directory MakeRAMDirectoryPhysical(RAMDirectory ramDir, IndexDefinition indexDefinition)
  776. {
  777. var newDir = new LuceneCodecDirectory(Path.Combine(path, indexDefinition.IndexId.ToString()), documentDatabase.IndexCodecs.OfType<AbstractIndexCodec>());
  778. Directory.Copy(ramDir, newDir, false);
  779. return newDir;
  780. }
  781. private Index CreateIndexImplementation(IndexDefinition indexDefinition, Directory directory)
  782. {
  783. var viewGenerator = indexDefinitionStorage.GetViewGenerator(indexDefinition.IndexId);
  784. var indexImplementation = indexDefinition.IsMapReduce
  785. ? (Index)new MapReduceIndex(directory, indexDefinition.IndexId, indexDefinition, viewGenerator, documentDatabase.WorkContext)
  786. : new SimpleIndex(directory, indexDefinition.IndexId, indexDefinition, viewGenerator, documentDatabase.WorkContext);
  787. configuration.Container.SatisfyImportsOnce(indexImplementation);
  788. indexImplementation.AnalyzerGenerators.Apply(initialization => initialization.Initialize(documentDatabase));
  789. return indexImplementation;
  790. }
  791. public int[] Indexes
  792. {
  793. get { return indexes.Keys.ToArray(); }
  794. }
  795. public string[] IndexNames
  796. {
  797. get { return indexes.Values.Select(x => x.PublicName).ToArray(); }
  798. }
  799. public bool HasIndex(string index)
  800. {
  801. if (index == null)
  802. return false;
  803. return indexes.Any(x => String.Compare(index, x.Value.PublicName, StringComparison.OrdinalIgnoreCase) == 0);
  804. }
  805. public void Dispose()
  806. {
  807. var exceptionAggregator = new ExceptionAggregator(log, "Could not properly close index storage");
  808. exceptionAggregator.Execute(FlushMapIndexes);
  809. exceptionAggregator.Execute(FlushReduceIndexes);
  810. exceptionAggregator.Execute(() => Parallel.ForEach(indexes.Values, index => exceptionAggregator.Execute(index.Dispose)));
  811. exceptionAggregator.Execute(() => dummyAnalyzer.Close());
  812. exceptionAggregator.Execute(() =>
  813. {
  814. if (crashMarker != null)
  815. crashMarker.Dispose();
  816. });
  817. exceptionAggregator.ThrowIfNeeded();
  818. }
  819. public void DeleteIndex(string name)
  820. {
  821. var value = TryIndexByName(name);
  822. if (value == null)
  823. return;
  824. DeleteIndex(value.indexId);
  825. }
  826. public void DeleteIndex(int id)
  827. {
  828. var value = GetIndexInstance(id);
  829. if (value == null)
  830. {
  831. if (log.IsDebugEnabled)
  832. log.Debug("Ignoring delete for non existing index {0}", id);
  833. return;
  834. }
  835. documentDatabase.TransactionalStorage.Batch(accessor =>
  836. accessor.Lists.Remove("Raven/Indexes/QueryTime", value.PublicName));
  837. if (log.IsDebugEnabled)
  838. log.Debug("Deleting index {0}", value.PublicName);
  839. value.Dispose();
  840. Index ignored;
  841. var dirOnDisk = Path.Combine(path, id.ToString());
  842. if (!indexes.TryRemove(id, out ignored) || !System.IO.Directory.Exists(dirOnDisk))
  843. return;
  844. UpdateIndexMappingFile();
  845. }
  846. public void RenameIndex(IndexDefinition existingIndex, string newIndexName)
  847. {
  848. var indexId = existingIndex.IndexId;
  849. var value = GetIndexInstance(indexId);
  850. if (log.IsDebugEnabled)
  851. log.Debug("Renaming index {0} -> {1}", value.PublicName, newIndexName);
  852. // since all we have to do in storage layer is to update mapping file
  853. // we simply call UpdateIndexMappingFile
  854. // mapping was already updated in IndexDefinitionStorage.RenameIndex method.
  855. UpdateIndexMappingFile();
  856. }
  857. public void DeleteIndexData(int id)
  858. {
  859. var dirOnDisk = Path.Combine(path, id.ToString(CultureInfo.InvariantCulture));
  860. IOExtensions.DeleteDirectory(dirOnDisk);
  861. }
  862. public Index ReopenCorruptedIndex(Index index)
  863. {
  864. if (index.Priority != IndexingPriority.Error)
  865. throw new InvalidOperationException(string.Format("Index {0} isn't errored", index.PublicName));
  866. index.Dispose();
  867. var reopened = OpenIndex(index.PublicName, onStartup: false, forceFullIndexCheck: true);
  868. if (reopened == null)
  869. throw new InvalidOperationException("Reopened index cannot be null instance. Index name:" + index.PublicName);
  870. return indexes.AddOrUpdate(reopened.IndexId, n => reopened, (s, existigIndex) => reopened);
  871. }
  872. public void CreateIndexImplementation(IndexDefinition indexDefinition)
  873. {
  874. if (log.IsDebugEnabled)
  875. log.Debug("Creating index {0} with id {1}", indexDefinition.IndexId, indexDefinition.Name);
  876. IndexDefinitionStorage.ResolveAnalyzers(indexDefinition);
  877. if (TryIndexByName(indexDefinition.Name) != null)
  878. {
  879. throw new InvalidOperationException("Index " + indexDefinition.Name + " already exists");
  880. }
  881. var addedIndex = indexes.AddOrUpdate(indexDefinition.IndexId, n =>
  882. {
  883. var directory = OpenOrCreateLuceneDirectory(indexDefinition);
  884. return CreateIndexImplementation(indexDefinition, directory);
  885. }, (s, index) => index);
  886. //prevent corrupted index when creating a map-reduce index
  887. //need to do this for every map reduce index, even when indexing is enabled,
  888. if (addedIndex.IsMapReduce)
  889. {
  890. addedIndex.EnsureIndexWriter();
  891. addedIndex.Flush(Etag.Empty);
  892. }
  893. UpdateIndexMappingFile();
  894. }
  895. public Query GetDocumentQuery(string index, IndexQuery query, OrderedPartCollection<AbstractIndexQueryTrigger> indexQueryTriggers)
  896. {
  897. var value = TryIndexByName(index);
  898. if (value == null)
  899. {
  900. if (log.IsDebugEnabled)
  901. log.Debug("Query on non existing index {0}", index);
  902. throw new InvalidOperationException("Index '" + index + "' does not exists");
  903. }
  904. var fieldsToFetch = new FieldsToFetch(new string[0], false, null);
  905. return new Index.IndexQueryOperation(value, query, _ => false, fieldsToFetch, indexQueryTriggers).GetDocumentQuery();
  906. }
  907. private Index TryIndexByName(string name)
  908. {
  909. return indexes.Where(index => String.Compare(index.Value.PublicName, name, StringComparison.OrdinalIgnoreCase) == 0)
  910. .Select(x => x.Value)
  911. .FirstOrDefault();
  912. }
  913. public IEnumerable<IndexQueryResult> Query(string index,
  914. IndexQuery query,
  915. Func<IndexQueryResult, bool> shouldIncludeInResults,
  916. FieldsToFetch fieldsToFetch,
  917. OrderedPartCollection<AbstractIndexQueryTrigger> indexQueryTriggers,
  918. CancellationToken token,
  919. Action<double> parseTiming = null
  920. )
  921. {
  922. Index value = TryIndexByName(index);
  923. if (value == null)
  924. {
  925. if (log.IsDebugEnabled)
  926. log.Debug("Query on non existing index '{0}'", index);
  927. throw new InvalidOperationException("Index '" + index + "' does not exists");
  928. }
  929. if ((value.Priority.HasFlag(IndexingPriority.Idle) || value.Priority.HasFlag(IndexingPriority.Abandoned)) &&
  930. value.Priority.HasFlag(IndexingPriority.Forced) == false)
  931. {
  932. value.Priority = IndexingPriority.Normal;
  933. try
  934. {
  935. documentDatabase.TransactionalStorage.Batch(accessor =>
  936. {
  937. accessor.Indexing.SetIndexPriority(value.indexId, IndexingPriority.Normal);
  938. });
  939. }
  940. catch (ConcurrencyException)
  941. {
  942. //we explicitly ignore write conflicts here,
  943. //it is okay if we got set twice (two concurrent queries, or setting while indexing).
  944. }
  945. documentDatabase.WorkContext.ShouldNotifyAboutWork(() => "Idle index queried");
  946. documentDatabase.Notifications.RaiseNotifications(new IndexChangeNotification
  947. {
  948. Name = value.PublicName,
  949. Type = IndexChangeTypes.IndexPromotedFromIdle
  950. });
  951. }
  952. var indexQueryOperation = new Index.IndexQueryOperation(value, query, shouldIncludeInResults, fieldsToFetch, indexQueryTriggers);
  953. if (parseTiming != null)
  954. parseTiming(indexQueryOperation.QueryParseDuration.TotalMilliseconds);
  955. if (query.Query != null && query.Query.Contains(Constants.IntersectSeparator))
  956. return indexQueryOperation.IntersectionQuery(token);
  957. return indexQueryOperation.Query(token);
  958. }
  959. public IEnumerable<RavenJObject> IndexEntires(
  960. string indexName,
  961. IndexQuery query,
  962. List<string> reduceKeys,
  963. OrderedPartCollection<AbstractIndexQueryTrigger> indexQueryTriggers,
  964. Reference<int> totalResults)
  965. {
  966. Index value = TryIndexByName(indexName);
  967. if (value == null)
  968. {
  969. if (log.IsDebugEnabled)
  970. log.Debug("Query on non existing index '{0}'", indexName);
  971. throw new InvalidOperationException("Index '" + indexName + "' does not exists");
  972. }
  973. var indexQueryOperation = new Index.IndexQueryOperation(value, query, null, new FieldsToFetch(null, false, null), indexQueryTriggers, reduceKeys);
  974. return indexQueryOperation.IndexEntries(totalResults);
  975. }
  976. public void RemoveFromIndex(int index, string[] keys, WorkContext context)
  977. {
  978. Index value;
  979. if (indexes.TryGetValue(index, out value) == false)
  980. {
  981. if (log.IsDebugEnabled)
  982. log.Debug("Removing from non existing index '{0}', ignoring", index);
  983. return;
  984. }
  985. value.Remove(keys, context);
  986. context.RaiseIndexChangeNotification(new IndexChangeNotification
  987. {
  988. Name = value.PublicName,
  989. Type = IndexChangeTypes.RemoveFromIndex
  990. });
  991. }
  992. [CLSCompliant(false)]
  993. public IndexingPerformanceStats Index(int index, AbstractViewGenerator viewGenerator, IndexingBatch batch, WorkContext context, IStorageActionsAccessor actions, DateTime minimumTimestamp, CancellationToken token)
  994. {
  995. Index value;
  996. if (indexes.TryGetValue(index, out value) == false)
  997. {
  998. if (log.IsDebugEnabled)
  999. log.Debug("Tried to index on a non existent index {0}, ignoring", index);
  1000. return null;
  1001. }
  1002. using (CultureHelper.EnsureInvariantCulture())
  1003. using (DocumentCacher.SkipSetAndGetDocumentsInDocumentCache())
  1004. {
  1005. var performance = value.IndexDocuments(viewGenerator, batch, actions, minimumTimestamp, token);
  1006. context.RaiseIndexChangeNotification(new IndexChangeNotification
  1007. {
  1008. Name = value.PublicName,
  1009. Type = IndexChangeTypes.MapCompleted,
  1010. Collections = batch.Collections
  1011. });
  1012. return performance;
  1013. }
  1014. }
  1015. [CLSCompliant(false)]
  1016. public IndexingPerformanceStats Reduce(
  1017. int index,
  1018. AbstractViewGenerator viewGenerator,
  1019. IEnumerable<IGrouping<int, object>> mappedResults,
  1020. int level,
  1021. WorkContext context,
  1022. IStorageActionsAccessor actions,
  1023. HashSet<string> reduceKeys,
  1024. int inputCount)
  1025. {
  1026. Index value;
  1027. if (indexes.TryGetValue(index, out value) == false)
  1028. {
  1029. if (log.IsDebugEnabled)
  1030. log.Debug("Tried to index on a non existent index {0}, ignoring", index);
  1031. return null;
  1032. }
  1033. var mapReduceIndex = value as MapReduceIndex;
  1034. if (mapReduceIndex == null)
  1035. {
  1036. log.Warn("Tried to reduce on an index that is not a map/reduce index: {0}, ignoring", index);
  1037. return null;
  1038. }
  1039. using (CultureHelper.EnsureInvariantCulture())
  1040. {
  1041. var reduceDocuments = new MapReduceIndex.ReduceDocuments(mapReduceIndex, viewGenerator, mappedResults, level, context, actions, reduceKeys, inputCount);
  1042. var performance = reduceDocuments.ExecuteReduction();
  1043. context.RaiseIndexChangeNotification(new IndexChangeNotification
  1044. {
  1045. Name = value.PublicName,
  1046. Type = IndexChangeTypes.ReduceCompleted
  1047. });
  1048. return performance;
  1049. }
  1050. }
  1051. internal IndexSearcherHolder.IndexSearcherHoldingState GetCurrentStateHolder(string indexName)
  1052. {
  1053. return GetIndexByName(indexName).GetCurrentStateHolder();
  1054. }
  1055. public IDisposable GetCurrentIndexSearcher(int indexId, out IndexSearcher searcher)
  1056. {
  1057. return GetIndexInstance(indexId).GetSearcher(out searcher);
  1058. }
  1059. public IDisposable GetCurrentIndexSearcherAndTermDocs(string indexName, out IndexSearcher searcher, out RavenJObject[] termsDocs)
  1060. {
  1061. return GetIndexByName(indexName).GetSearcherAndTermsDocs(out searcher, out termsDocs);
  1062. }
  1063. private Index GetIndexByName(string indexName)
  1064. {
  1065. var result = TryIndexByName(indexName);
  1066. if (result == null)
  1067. throw new InvalidOperationException(string.Format("Index '{0}' does not exist", indexName));
  1068. return result;
  1069. }
  1070. public void RunIdleOperations()
  1071. {
  1072. foreach (var value in indexes.Values)
  1073. {
  1074. if ((SystemTime.UtcNow - value.LastIndexTime).TotalMinutes < 1)
  1075. continue;
  1076. value.Flush(value.GetLastEtagFromStats());
  1077. }
  1078. SetUnusedIndexesToIdle();
  1079. UpdateLatestPersistedQueryTime();
  1080. DeleteSurpassedAutoIndexes();
  1081. }
  1082. public bool IsIndexStale(string indexName, LastCollectionEtags lastCollectionEtags)
  1083. {
  1084. var index = TryIndexByName(indexName);
  1085. if (index == null)
  1086. throw new InvalidOperationException("Could not find index " + indexName);
  1087. return IsIndexStale(index.IndexId, lastCollectionEtags);
  1088. }
  1089. public bool IsIndexStale(int indexId, LastCollectionEtags lastCollectionEtags)
  1090. {
  1091. bool isStale = false;
  1092. documentDatabase.TransactionalStorage.Batch(actions =>
  1093. {
  1094. Index indexInstance = GetIndexInstance(indexId);
  1095. isStale = (indexInstance != null && indexInstance.IsMapIndexingInProgress) || actions.Staleness.IsIndexStale(indexId, null, null);
  1096. if (indexInstance != null && indexInstance.IsTestIndex)
  1097. isStale = false;
  1098. if (isStale && actions.Staleness.IsIndexStaleByTask(indexId, null) == false && actions.Staleness.IsReduceStale(indexId) == false)
  1099. {
  1100. var viewGenerator = indexDefinitionStorage.GetViewGenerator(indexId);
  1101. if (viewGenerator == null)
  1102. return;
  1103. var indexStats = actions.Indexing.GetIndexStats(indexId);
  1104. if (indexStats == null)
  1105. return;
  1106. var lastIndexedEtag = indexStats.LastIndexedEtag;
  1107. var collectionNames = viewGenerator.ForEntityNames.ToList();
  1108. if (lastCollectionEtags.HasEtagGreaterThan(collectionNames, lastIndexedEtag) == false)
  1109. isStale = false;
  1110. }
  1111. });
  1112. return isStale;
  1113. }
  1114. private void DeleteSurpassedAutoIndexes()
  1115. {
  1116. if (indexes.Any(x => x.Value.PublicName.StartsWith("Auto/", StringComparison.InvariantCultureIgnoreCase)) == false)
  1117. return;
  1118. var mergeSuggestions = indexDefinitionStorage.ProposeIndexMergeSuggestions();
  1119. foreach (var mergeSuggestion in mergeSuggestions.Suggestions)
  1120. {
  1121. if (string.IsNullOrEmpty(mergeSuggestion.SurpassingIndex))
  1122. continue;
  1123. if (mergeSuggestion.CanDelete.Any(x => x.StartsWith("Auto/", StringComparison.InvariantCultureIgnoreCase)) == false)
  1124. continue;
  1125. if (IsIndexStale(mergeSuggestion.SurpassingIndex, documentDatabase.LastCollectionEtags))
  1126. continue;
  1127. foreach (var indexToDelete in mergeSuggestion.CanDelete.Where(x => x.StartsWith("Auto/", StringComparison.InvariantCultureIgnoreCase)))
  1128. {
  1129. documentDatabase.Indexes.DeleteIndex(indexToDelete);
  1130. }
  1131. }
  1132. }
  1133. private void UpdateLatestPersistedQueryTime()
  1134. {
  1135. documentDatabase.TransactionalStorage.Batch(accessor =>
  1136. {
  1137. var maxDate = latestPersistedQueryTime;
  1138. foreach (var index in indexes)
  1139. {
  1140. var lastQueryTime = index.Value.LastQueryTime ?? DateTime.MinValue;
  1141. if (lastQueryTime <= latestPersistedQueryTime)
  1142. continue;
  1143. accessor.Lists.Set("Raven/Indexes/QueryTime", index.Value.PublicName, new RavenJObject
  1144. {
  1145. {"LastQueryTime", lastQueryTime}
  1146. }, UuidType.Indexing);
  1147. if (lastQueryTime > maxDate)
  1148. maxDate = lastQueryTime;
  1149. }
  1150. latestPersistedQueryTime = maxDate;
  1151. });
  1152. }
  1153. public class UnusedIndexState
  1154. {
  1155. public DateTime LastQueryTime { get; set; }
  1156. public Index Index { get; set; }
  1157. public string Name { get; set; }
  1158. public IndexingPriority Priority { get; set; }
  1159. public DateTime CreationDate { get; set; }
  1160. }
  1161. private void SetUnusedIndexesToIdle()
  1162. {
  1163. documentDatabase.TransactionalStorage.Batch(accessor =>
  1164. {
  1165. var autoIndexesSortedByLastQueryTime =
  1166. (from index in indexes
  1167. let stats = GetIndexStats(accessor, index.Key)
  1168. where stats != null
  1169. let lastQueryTime = stats.LastQueryTimestamp ?? DateTime.MinValue
  1170. where index.Value.PublicName.StartsWith("Auto/", StringComparison.InvariantCultureIgnoreCase)
  1171. orderby lastQueryTime
  1172. select new UnusedIndexState
  1173. {
  1174. LastQueryTime = lastQueryTime,
  1175. Index = index.Value,
  1176. Name = index.Value.PublicName,
  1177. Priority = stats.Priority,
  1178. CreationDate = stats.CreatedTimestamp
  1179. }).ToArray();
  1180. var timeToWaitBeforeMarkingAutoIndexAsIdle = documentDatabase.Configuration.TimeToWaitBeforeMarkingAutoIndexAsIdle;
  1181. var timeToWaitForIdleMinutes = timeToWaitBeforeMarkingAutoIndexAsIdle.TotalMinutes * 10;
  1182. for (var i = 0; i < autoIndexesSortedByLastQueryTime.Length; i++)
  1183. {
  1184. var thisItem = autoIndexesSortedByLastQueryTime[i];
  1185. if (thisItem.Priority.HasFlag(IndexingPriority.Disabled) || // we don't really have much to say about those in here
  1186. thisItem.Priority.HasFlag(IndexingPriority.Error) || // no need to touch erroring indexes
  1187. thisItem.Priority.HasFlag(IndexingPriority.Forced))// if it is forced, we can't change it
  1188. continue;
  1189. var age = (SystemTime.UtcNow - thisItem.CreationDate).TotalMinutes;
  1190. var lastQuery = (SystemTime.UtcNow - thisItem.LastQueryTime).TotalMinutes;
  1191. if (thisItem.Priority.HasFlag(IndexingPriority.Normal))
  1192. {
  1193. if (age < timeToWaitForIdleMinutes)
  1194. {
  1195. HandleActiveIndex(thisItem, age, lastQuery, accessor, timeToWaitForIdleMinutes);
  1196. }
  1197. else
  1198. {
  1199. // If it's a fairly established query then we need to determine whether there is any activity currently
  1200. // If there is activity and this has not been queried against 'recently' it needs idling
  1201. if (i < autoIndexesSortedByLastQueryTime.Length - 1)
  1202. {
  1203. var nextItem = autoIndexesSortedByLastQueryTime[i + 1];
  1204. if ((nextItem.LastQueryTime - thisItem.LastQueryTime).TotalMinutes > timeToWaitForIdleMinutes)
  1205. {
  1206. accessor.Indexing.SetIndexPriority(thisItem.Index.indexId, IndexingPriority.Idle);
  1207. thisItem.Index.Priority = IndexingPriority.Idle;
  1208. documentDatabase.Notifications.RaiseNotifications(new IndexChangeNotification()
  1209. {
  1210. Name = thisItem.Name,
  1211. Type = IndexChangeTypes.IndexDemotedToIdle
  1212. });
  1213. }
  1214. }
  1215. }
  1216. continue;
  1217. }
  1218. if (thisItem.Priority.HasFlag(IndexingPriority.Idle))
  1219. {
  1220. HandleIdleIndex(age, lastQuery, thisItem, accessor);
  1221. continue;
  1222. }
  1223. }
  1224. });
  1225. }
  1226. private IndexStats GetIndexStats(IStorageActionsAccessor accessor, int indexId)
  1227. {
  1228. var indexStats = accessor.Indexing.GetIndexStats(indexId);
  1229. if (indexStats == null)
  1230. return null;
  1231. indexStats.LastQueryTimestamp = GetLastQueryTime(indexId);
  1232. return indexStats;
  1233. }
  1234. private void HandleIdleIndex(double age, double lastQuery, UnusedIndexState thisItem,
  1235. IStorageActionsAccessor accessor)
  1236. {
  1237. // relatively young index, haven't been queried for a while already
  1238. // can be safely removed, probably
  1239. if (age < 90 && lastQuery > 30)
  1240. {
  1241. accessor.Indexing.DeleteIndex(thisItem.Index.indexId, documentDatabase.WorkContext.CancellationToken);
  1242. return;
  1243. }
  1244. if (lastQuery < configuration.TimeToWaitBeforeMarkingIdleIndexAsAbandoned.TotalMinutes)
  1245. return;
  1246. // old enough, and haven't been queried for a while, mark it as abandoned
  1247. accessor.Indexing.SetIndexPriority(thisItem.Index.indexId, IndexingPriority.Abandoned);
  1248. thisItem.Index.Priority = IndexingPriority.Abandoned;
  1249. documentDatabase.Notifications.RaiseNotifications(new IndexChangeNotification()
  1250. {
  1251. Name = thisItem.Name,
  1252. Type = IndexChangeTypes.IndexDemotedToAbandoned
  1253. });
  1254. }
  1255. private void HandleActiveIndex(UnusedIndexState thisItem, double age, double lastQuery, IStorageActionsAccessor accessor, double timeToWaitForIdle)
  1256. {
  1257. if (age < (timeToWaitForIdle * 2.5) && lastQuery < (1.5 * timeToWaitForIdle))
  1258. return;
  1259. if (age < (timeToWaitForIdle * 6) && lastQuery < (2.5 * timeToWaitForIdle))
  1260. return;
  1261. accessor.Indexing.SetIndexPriority(thisItem.Index.indexId, IndexingPriority.Idle);
  1262. thisItem.Index.Priority = IndexingPriority.Idle;
  1263. documentDatabase.Notifications.RaiseNotifications(new IndexChangeNotification()
  1264. {
  1265. Name = thisItem.Name,
  1266. Type = IndexChangeTypes.IndexDemotedToIdle
  1267. });
  1268. }
  1269. private void UpdateIndexMappingFile()
  1270. {
  1271. if (configuration.RunInMemory)
  1272. return;
  1273. var sb = new StringBuilder();
  1274. foreach (var index in indexes)
  1275. {
  1276. sb.Append(string.Format("{0} - {1}{2}", index.Value.IndexId, index.Value.PublicName, Environment.NewLine));
  1277. }
  1278. File.WriteAllText(Path.Combine(path, "indexes.txt"), sb.ToString());
  1279. }
  1280. public void FlushMapIndexes()
  1281. {
  1282. if (indexes == null)
  1283. return;
  1284. foreach (var index in indexes)
  1285. {
  1286. if (index.Value.IsMapReduce == false)
  1287. FlushIndex(index.Value);
  1288. }
  1289. }
  1290. public void FlushReduceIndexes()
  1291. {
  1292. if (indexes == null)
  1293. return;
  1294. foreach (var index in indexes)
  1295. {
  1296. if (index.Value.IsMapReduce)
  1297. FlushIndex(index.Value);
  1298. }
  1299. }
  1300. public void FlushIndexes(HashSet<int> indexIds, bool onlyAddIndexError)
  1301. {
  1302. if (indexes == null || indexIds.Count == 0)
  1303. return;
  1304. foreach (var indexId in indexIds)
  1305. {
  1306. FlushIndex(indexId, onlyAddIndexError);
  1307. }
  1308. }
  1309. public void FlushIndex(int indexId, bool onlyAddIndexError)
  1310. {
  1311. Index value;
  1312. if (indexes.TryGetValue(indexId, out value))
  1313. FlushIndex(value, onlyAddIndexError);
  1314. }
  1315. private static void FlushIndex(Index value, bool onlyAddIndexError = false)
  1316. {
  1317. var sp = Stopwatch.StartNew();
  1318. try
  1319. {
  1320. value.Flush(value.GetLastEtagFromStats());
  1321. }
  1322. catch (Exception e)
  1323. {
  1324. value.HandleWriteError(e);
  1325. log.WarnException(string.Format("Failed to flush {0} index: {1} (id: {2})",
  1326. GetIndexType(value.IsMapReduce), value.PublicName, value.IndexId), e);
  1327. if (onlyAddIndexError)
  1328. {
  1329. value.AddIndexFailedFlushError(e);
  1330. return;
  1331. }
  1332. throw;
  1333. }
  1334. if (log.IsDebugEnabled)
  1335. {
  1336. log.Debug("Flushed {0} index: {1} (id: {2}), took {3}ms",
  1337. GetIndexType(value.IsMapReduce), value.PublicName, value.IndexId, sp.ElapsedMilliseconds);
  1338. }
  1339. }
  1340. private static string GetIndexType(bool isMapReduce)
  1341. {
  1342. return isMapReduce ? "map-reduce" : "simple map";
  1343. }
  1344. public List<int> GetDisabledIndexIds()
  1345. {
  1346. var indexIds = new List<int>();
  1347. foreach (var index in indexes)
  1348. {
  1349. if (index.Value.Priority.HasFlag(IndexingPriority.Disabled))
  1350. indexIds.Add(index.Key);
  1351. }
  1352. return indexIds;
  1353. }
  1354. public IIndexExtension GetIndexExtension(string index, string indexExtensionKey)
  1355. {
  1356. return GetIndexByName(index).GetExtension(indexExtensionKey);
  1357. }
  1358. public IIndexExtension GetIndexExtensionByPrefix(string index, string indexExtensionKeyPrefix)
  1359. {
  1360. return GetIndexByName(index).GetExtensionByPrefix(indexExtensionKeyPrefix);
  1361. }
  1362. public void SetIndexExtension(string indexName, string indexExtensionKey, IIndexExtension suggestionQueryIndexExtension)
  1363. {
  1364. GetIndexByName(indexName).SetExtension(indexExtensionKey, suggestionQueryIndexExtension);
  1365. }
  1366. public Index GetIndexInstance(string indexName)
  1367. {
  1368. return TryIndexByName(indexName);
  1369. }
  1370. public IEnumerable<Index> GetAllIndexes()
  1371. {
  1372. return indexes.Values;
  1373. }
  1374. public Index GetIndexInstance(int indexId)
  1375. {
  1376. Index value;
  1377. indexes.TryGetValue(indexId, out value);
  1378. return value;
  1379. }
  1380. public void MarkCachedQuery(string indexName)
  1381. {
  1382. GetIndexByName(indexName).MarkQueried();
  1383. }
  1384. internal void SetLastQueryTime(string indexName, DateTime lastQueryTime)
  1385. {
  1386. GetIndexByName(indexName).MarkQueried(lastQueryTime);
  1387. }
  1388. public DateTime? GetLastQueryTime(int index)
  1389. {
  1390. return GetIndexInstance(index).LastQueryTime;
  1391. }
  1392. public DateTime? GetLastQueryTime(string index)
  1393. {
  1394. return GetIndexInstance(index).LastQueryTime;
  1395. }
  1396. public IndexingPerformanceStats[] GetIndexingPerformance(int index)
  1397. {
  1398. return GetIndexInstance(index).GetIndexingPerformance();
  1399. }
  1400. public void Backup(string directory, string incrementalTag = null, Action<string, Exception, BackupStatus.BackupMessageSeverity> notifyCallback = null, CancellationToken token = default(CancellationToken))
  1401. {
  1402. Parallel.ForEach(indexes.Values, index =>
  1403. index.Backup(directory, path, incrementalTag, notifyCallback, token));
  1404. }
  1405. public void MergeAllIndexes()
  1406. {
  1407. Parallel.ForEach(indexes.Values, index =>
  1408. index.MergeSegments());
  1409. }
  1410. public string IndexOnRam(int id)
  1411. {
  1412. return GetIndexInstance(id).IsOnRam;
  1413. }
  1414. public void ForceWriteToDiskAndWriteInMemoryIndexToDiskIfNecessary(string indexName)
  1415. {
  1416. var index = GetIndexByName(indexName);
  1417. index.ForceWriteToDisk();
  1418. index.WriteInMemoryIndexToDiskIfNecessary(Etag.Empty);
  1419. }
  1420. internal bool TryReplaceIndex(string indexName, string indexToReplaceName)
  1421. {
  1422. var indexDefinition = indexDefinitionStorage.GetIndexDefinition(indexName);
  1423. if (indexDefinition == null)
  1424. {
  1425. //the replacing index doesn't exist
  1426. return true;
  1427. }
  1428. var indexToReplace = indexDefinitionStorage.GetIndexDefinition(indexToReplaceName);
  1429. if (indexToReplace != null)
  1430. {
  1431. switch (indexToReplace.LockMode)
  1432. {
  1433. case IndexLockMode.SideBySide:
  1434. //keep the SideBySide lock mode from the replaced index
  1435. indexDefinition.LockMode = IndexLockMode.SideBySide;
  1436. break;
  1437. case IndexLockMode.LockedIgnore:
  1438. //we ignore this and need to delete the replacing index
  1439. documentDatabase.IndexStorage.DeleteIndex(indexName);
  1440. log.Info("An attempt to replace an index with lock mode: LockedIgnore by a side by side index was detected");
  1441. return true;
  1442. case IndexLockMode.LockedError:
  1443. log.Info("An attempt to replace an index with lock mode: LockedError by a side by side index was detected");
  1444. throw new InvalidOperationException("An attempt to replace an index, locked with LockedError, by a side by side index was detected.");
  1445. }
  1446. }
  1447. var success = indexDefinitionStorage.ReplaceIndex(indexName, indexToReplaceName, () =>
  1448. {
  1449. //replace the index errors with the new index errors
  1450. int? indexToReplaceId = null;
  1451. if (indexToReplace != null)
  1452. indexToReplaceId = indexToReplace.IndexId;
  1453. documentDatabase.WorkContext.ReplaceIndexingErrors(indexToReplaceName,
  1454. indexToReplaceId, indexName, indexDefinition.IndexId);
  1455. });
  1456. if (success == false)
  1457. return false;
  1458. documentDatabase.Indexes.DeleteIndex(indexToReplace, removeByNameMapping: false, clearErrors: false, isSideBySideReplacement: true);
  1459. return true;
  1460. }
  1461. }
  1462. }