PageRenderTime 45ms CodeModel.GetById 9ms 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

Large files files are truncated, but you can click here to view the full file

  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("Indeā€¦

Large files files are truncated, but you can click here to view the full file