PageRenderTime 52ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Indexing/IndexStorage.cs

http://github.com/ravendb/ravendb
C# | 1602 lines | 1296 code | 267 blank | 39 comment | 217 complexity | 5632cabcab044e4664345d11ae253d55 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause

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.Extensions;
  26. using Raven.Abstractions.Indexing;
  27. using Raven.Abstractions.Logging;
  28. using Raven.Abstractions.MEF;
  29. using Raven.Abstractions.Util;
  30. using Raven.Database.Actions;
  31. using Raven.Database.Config;
  32. using Raven.Database.Data;
  33. using Raven.Database.Extensions;
  34. using Raven.Database.Impl;
  35. using Raven.Database.Linq;
  36. using Raven.Database.Plugins;
  37. using Raven.Database.Queries;
  38. using Raven.Database.Storage;
  39. using Raven.Database.Util;
  40. using Raven.Imports.Newtonsoft.Json;
  41. using Raven.Imports.Newtonsoft.Json.Linq;
  42. using Raven.Json.Linq;
  43. using Constants = Raven.Abstractions.Data.Constants;
  44. using Directory = Lucene.Net.Store.Directory;
  45. namespace Raven.Database.Indexing
  46. {
  47. /// <summary>
  48. /// Thread safe, single instance for the entire application
  49. /// </summary>
  50. public class IndexStorage : CriticalFinalizerObject, IDisposable
  51. {
  52. private readonly DocumentDatabase documentDatabase;
  53. private const string IndexVersion = "2.0.0.1";
  54. private const string MapReduceIndexVersion = "2.5.0.1";
  55. private readonly IndexDefinitionStorage indexDefinitionStorage;
  56. private readonly InMemoryRavenConfiguration configuration;
  57. private readonly string path;
  58. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  59. private static readonly ILog startupLog = LogManager.GetLogger(typeof(IndexStorage).FullName + ".Startup");
  60. private readonly Analyzer dummyAnalyzer = new SimpleAnalyzer();
  61. private DateTime latestPersistedQueryTime;
  62. private readonly FileStream crashMarker;
  63. private ConcurrentDictionary<int, Index> indexes =
  64. new ConcurrentDictionary<int, Index>();
  65. public class RegisterLowMemoryHandler : ILowMemoryHandler
  66. {
  67. static RegisterLowMemoryHandler _instance;
  68. public static void Setup()
  69. {
  70. if (_instance != null)
  71. return;
  72. lock (typeof (RegisterLowMemoryHandler))
  73. {
  74. if (_instance != null)
  75. return;
  76. _instance = new RegisterLowMemoryHandler();
  77. MemoryStatistics.RegisterLowMemoryHandler(_instance);
  78. }
  79. }
  80. public void HandleLowMemory()
  81. {
  82. FieldCache_Fields.DEFAULT.PurgeAllCaches();
  83. }
  84. public void SoftMemoryRelease()
  85. {
  86. }
  87. public LowMemoryHandlerStatistics GetStats()
  88. {
  89. var cacheEntries = FieldCache_Fields.DEFAULT.GetCacheEntries();
  90. var memorySum = cacheEntries.Sum(x =>
  91. {
  92. var curEstimator = new RamUsageEstimator(false);
  93. return curEstimator.EstimateRamUsage(x);
  94. });
  95. return new LowMemoryHandlerStatistics
  96. {
  97. Name = "LuceneLowMemoryHandler",
  98. EstimatedUsedMemory = memorySum,
  99. Metadata = new
  100. {
  101. CachedEntriesAmount = cacheEntries.Length
  102. }
  103. };
  104. }
  105. }
  106. public IndexStorage(IndexDefinitionStorage indexDefinitionStorage, InMemoryRavenConfiguration configuration, DocumentDatabase documentDatabase)
  107. {
  108. try
  109. {
  110. RegisterLowMemoryHandler.Setup();
  111. this.indexDefinitionStorage = indexDefinitionStorage;
  112. this.configuration = configuration;
  113. this.documentDatabase = documentDatabase;
  114. path = configuration.IndexStoragePath;
  115. if (System.IO.Directory.Exists(path) == false && configuration.RunInMemory == false)
  116. System.IO.Directory.CreateDirectory(path);
  117. if (configuration.RunInMemory == false)
  118. {
  119. var crashMarkerPath = Path.Combine(path, "indexing.crash-marker");
  120. if (File.Exists(crashMarkerPath))
  121. {
  122. // the only way this can happen is if we crashed because of a power outage
  123. // in this case, we consider all open indexes to be corrupt and force them
  124. // to be reset. This is because to get better perf, we don't flush the files to disk,
  125. // so in the case of a power outage, we can't be sure that there wasn't still stuff in
  126. // the OS buffer that wasn't written yet.
  127. configuration.ResetIndexOnUncleanShutdown = true;
  128. }
  129. // The delete on close ensures that the only way this file will exists is if there was
  130. // a power outage while the server was running.
  131. crashMarker = File.Create(crashMarkerPath, 16, FileOptions.DeleteOnClose);
  132. }
  133. BackgroundTaskExecuter.Instance.ExecuteAllInterleaved(documentDatabase.WorkContext,
  134. indexDefinitionStorage.IndexNames, OpenIndexOnStartup);
  135. }
  136. catch (Exception e)
  137. {
  138. log.WarnException("Could not create index storage", e);
  139. try
  140. {
  141. Dispose();
  142. }
  143. catch (Exception ex)
  144. {
  145. log.FatalException("Failed to disposed when already getting an error during ctor", ex);
  146. }
  147. throw;
  148. }
  149. }
  150. private void OpenIndexOnStartup(string indexName)
  151. {
  152. if (indexName == null)
  153. throw new ArgumentNullException("indexName");
  154. startupLog.Debug("Loading saved index {0}", indexName);
  155. var indexDefinition = indexDefinitionStorage.GetIndexDefinition(indexName);
  156. if (indexDefinition == null)
  157. return;
  158. Index indexImplementation;
  159. bool resetTried = false;
  160. bool recoveryTried = false;
  161. string[] keysToDeleteAfterRecovery = null;
  162. while (true)
  163. {
  164. Directory luceneDirectory = null;
  165. try
  166. {
  167. luceneDirectory = OpenOrCreateLuceneDirectory(indexDefinition, createIfMissing: resetTried);
  168. indexImplementation = CreateIndexImplementation(indexDefinition, luceneDirectory);
  169. CheckIndexState(luceneDirectory, indexDefinition, indexImplementation, resetTried);
  170. var simpleIndex = indexImplementation as SimpleIndex; // no need to do this on m/r indexes, since we rebuild them from saved data anyway
  171. if (simpleIndex != null && keysToDeleteAfterRecovery != null)
  172. {
  173. // remove keys from index that were deleted after creating commit point
  174. simpleIndex.RemoveDirectlyFromIndex(keysToDeleteAfterRecovery, GetLastEtagForIndex(simpleIndex));
  175. }
  176. LoadExistingSuggestionsExtentions(indexDefinition.Name, indexImplementation);
  177. documentDatabase.TransactionalStorage.Batch(accessor =>
  178. {
  179. IndexStats indexStats = accessor.Indexing.GetIndexStats(indexDefinition.IndexId);
  180. if (indexStats != null)
  181. indexImplementation.Priority = indexStats.Priority;
  182. var read = accessor.Lists.Read("Raven/Indexes/QueryTime", indexName);
  183. if (read == null)
  184. {
  185. if (IsIdleAutoIndex(indexImplementation))
  186. indexImplementation.MarkQueried(); // prevent index abandoning right after startup
  187. return;
  188. }
  189. var dateTime = read.Data.Value<DateTime>("LastQueryTime");
  190. if (IsIdleAutoIndex(indexImplementation) && SystemTime.UtcNow - dateTime > configuration.TimeToWaitBeforeRunningAbandonedIndexes)
  191. indexImplementation.MarkQueried(); // prevent index abandoning right after startup
  192. else
  193. indexImplementation.MarkQueried(dateTime);
  194. if (dateTime > latestPersistedQueryTime)
  195. latestPersistedQueryTime = dateTime;
  196. });
  197. break;
  198. }
  199. catch (Exception e)
  200. {
  201. if (resetTried)
  202. throw new InvalidOperationException("Could not open / create index" + indexName + ", reset already tried", e);
  203. if (recoveryTried == false && luceneDirectory != null && configuration.Indexing.SkipRecoveryOnStartup == false)
  204. {
  205. recoveryTried = true;
  206. startupLog.WarnException("Could not open index " + indexName + ". Trying to recover index", e);
  207. keysToDeleteAfterRecovery = TryRecoveringIndex(indexDefinition, luceneDirectory);
  208. }
  209. else
  210. {
  211. resetTried = true;
  212. startupLog.WarnException("Could not open index " + indexName + ". Recovery operation failed, forcibly resetting index", e);
  213. TryResettingIndex(indexName, indexDefinition);
  214. }
  215. }
  216. }
  217. indexes.TryAdd(indexDefinition.IndexId, indexImplementation);
  218. }
  219. private void CheckIndexState(Directory directory, IndexDefinition indexDefinition, Index index, bool resetTried)
  220. {
  221. //if (configuration.ResetIndexOnUncleanShutdown == false)
  222. // return;
  223. // 1. If commitData is null it means that there were no commits, so just in case we are resetting to Etag.Empty
  224. // 2. If no 'LastEtag' in commitData then we consider it an invalid index
  225. // 3. If 'LastEtag' is present (and valid), then resetting to it (if it is lower than lastStoredEtag)
  226. var commitData = IndexReader.GetCommitUserData(directory);
  227. if (index.IsMapReduce)
  228. CheckMapReduceIndexState(commitData, resetTried);
  229. else
  230. CheckMapIndexState(commitData, indexDefinition, index);
  231. }
  232. private void CheckMapIndexState(IDictionary<string, string> commitData, IndexDefinition indexDefinition, Index index)
  233. {
  234. string value;
  235. Etag lastEtag = null;
  236. if (commitData != null && commitData.TryGetValue("LastEtag", out value))
  237. Etag.TryParse(value, out lastEtag); // etag will be null if parsing will fail
  238. var lastStoredEtag = GetLastEtagForIndex(index) ?? Etag.Empty;
  239. lastEtag = lastEtag ?? Etag.Empty;
  240. if (EtagUtil.IsGreaterThanOrEqual(lastEtag, lastStoredEtag))
  241. return;
  242. log.Info(string.Format("Resetting index '{0} ({1})'. Last stored etag: {2}. Last commit etag: {3}.", indexDefinition.Name, index.indexId, lastStoredEtag, lastEtag));
  243. var now = SystemTime.UtcNow;
  244. ResetLastIndexedEtag(indexDefinition, lastEtag, now);
  245. }
  246. private static void CheckMapReduceIndexState(IDictionary<string, string> commitData, bool resetTried)
  247. {
  248. if (resetTried)
  249. return;
  250. string marker;
  251. long commitMarker;
  252. var valid = commitData != null
  253. && commitData.TryGetValue("Marker", out marker)
  254. && long.TryParse(marker, out commitMarker)
  255. && commitMarker == RavenIndexWriter.CommitMarker;
  256. if (valid == false)
  257. throw new InvalidOperationException("Map-Reduce index corruption detected.");
  258. }
  259. private static bool IsIdleAutoIndex(Index index)
  260. {
  261. return index.PublicName.StartsWith("Auto/") && index.Priority == IndexingPriority.Idle;
  262. }
  263. private void TryResettingIndex(string indexName, IndexDefinition indexDefinition)
  264. {
  265. try
  266. {
  267. // we have to defer the work here until the database is actually ready for work
  268. documentDatabase.OnIndexingWiringComplete += () =>
  269. {
  270. try
  271. {
  272. documentDatabase.Indexes.DeleteIndex(indexDefinition, removeIndexReplaceDocument: false);
  273. documentDatabase.Indexes.PutNewIndexIntoStorage(indexName, indexDefinition);
  274. var indexReplaceDocumentKey = Constants.IndexReplacePrefix + indexName;
  275. var indexReplaceDocument = documentDatabase.Documents.Get(indexReplaceDocumentKey, null);
  276. if (indexReplaceDocument == null)
  277. return;
  278. documentDatabase.Documents.Put(indexReplaceDocumentKey, null, indexReplaceDocument.DataAsJson, indexReplaceDocument.Metadata, null);
  279. }
  280. catch (Exception e)
  281. {
  282. throw new InvalidOperationException("Could not finalize reseting of index: " + indexName, e);
  283. }
  284. };
  285. var indexFullPath = Path.Combine(path, indexDefinition.IndexId.ToString(CultureInfo.InvariantCulture));
  286. IOExtensions.DeleteDirectory(indexFullPath);
  287. var suggestionsForIndex = Path.Combine(configuration.IndexStoragePath, "Raven-Suggestions", indexName);
  288. IOExtensions.DeleteDirectory(suggestionsForIndex);
  289. }
  290. catch (Exception exception)
  291. {
  292. throw new InvalidOperationException("Could not reset index " + indexName, exception);
  293. }
  294. }
  295. private string[] TryRecoveringIndex(IndexDefinition indexDefinition,
  296. Directory luceneDirectory)
  297. {
  298. string[] keysToDeleteAfterRecovery = null;
  299. if (indexDefinition.IsMapReduce == false)
  300. {
  301. IndexCommitPoint commitUsedToRestore;
  302. if (TryReusePreviousCommitPointsToRecoverIndex(luceneDirectory,
  303. indexDefinition, path,
  304. out commitUsedToRestore,
  305. out keysToDeleteAfterRecovery))
  306. {
  307. ResetLastIndexedEtag(indexDefinition, commitUsedToRestore.HighestCommitedETag, commitUsedToRestore.TimeStamp);
  308. }
  309. }
  310. else
  311. {
  312. RegenerateMapReduceIndex(luceneDirectory, indexDefinition);
  313. }
  314. return keysToDeleteAfterRecovery;
  315. }
  316. private void LoadExistingSuggestionsExtentions(string indexName, Index indexImplementation)
  317. {
  318. var suggestionsForIndex = Path.Combine(configuration.IndexStoragePath, "Raven-Suggestions", indexName);
  319. if (!System.IO.Directory.Exists(suggestionsForIndex))
  320. return;
  321. try
  322. {
  323. foreach (var directory in System.IO.Directory.GetDirectories(suggestionsForIndex))
  324. {
  325. IndexSearcher searcher;
  326. using (indexImplementation.GetSearcher(out searcher))
  327. {
  328. var key = Path.GetFileName(directory);
  329. var decodedKey = MonoHttpUtility.UrlDecode(key);
  330. var lastIndexOfDash = decodedKey.LastIndexOf('-');
  331. var accuracy = float.Parse(decodedKey.Substring(lastIndexOfDash + 1), CultureInfo.InvariantCulture);
  332. var lastIndexOfDistance = decodedKey.LastIndexOf('-', lastIndexOfDash - 1);
  333. StringDistanceTypes distanceType;
  334. Enum.TryParse(decodedKey.Substring(lastIndexOfDistance + 1, lastIndexOfDash - lastIndexOfDistance - 1),
  335. true, out distanceType);
  336. var field = decodedKey.Substring(0, lastIndexOfDistance);
  337. var extension = new SuggestionQueryIndexExtension(
  338. indexImplementation,
  339. documentDatabase.WorkContext,
  340. Path.Combine(configuration.IndexStoragePath, "Raven-Suggestions", indexName, key),
  341. SuggestionQueryRunner.GetStringDistance(distanceType),
  342. searcher.IndexReader.Directory() is RAMDirectory,
  343. field,
  344. accuracy);
  345. indexImplementation.SetExtension(key, extension);
  346. }
  347. }
  348. }
  349. catch (Exception e)
  350. {
  351. log.WarnException("Could not open suggestions for index " + indexName + ", resetting the index", e);
  352. try
  353. {
  354. IOExtensions.DeleteDirectory(suggestionsForIndex);
  355. }
  356. catch (Exception)
  357. {
  358. // ignore the failure
  359. }
  360. throw;
  361. }
  362. }
  363. protected Directory OpenOrCreateLuceneDirectory(IndexDefinition indexDefinition, bool createIfMissing = true)
  364. {
  365. Directory directory;
  366. if (configuration.RunInMemory ||
  367. (indexDefinition.IsMapReduce == false && // there is no point in creating map/reduce indexes in memory, we write the intermediate results to disk anyway
  368. indexDefinitionStorage.IsNewThisSession(indexDefinition) &&
  369. indexDefinition.DisableInMemoryIndexing == false &&
  370. configuration.DisableInMemoryIndexing == false))
  371. {
  372. directory = new RAMDirectory();
  373. new IndexWriter(directory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose(); // creating index structure
  374. }
  375. else
  376. {
  377. var indexDirectory = indexDefinition.IndexId.ToString();
  378. var indexFullPath = Path.Combine(path, indexDirectory);
  379. directory = new LuceneCodecDirectory(indexFullPath, documentDatabase.IndexCodecs.OfType<AbstractIndexCodec>());
  380. if (!IndexReader.IndexExists(directory))
  381. {
  382. if (createIfMissing == false)
  383. throw new InvalidOperationException(string.Format("Index directory '{0}' does not exists for '{1}' index.", indexFullPath, indexDefinition.Name));
  384. WriteIndexVersion(directory, indexDefinition);
  385. //creating index structure if we need to
  386. new IndexWriter(directory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose();
  387. }
  388. else
  389. {
  390. EnsureIndexVersionMatches(directory, indexDefinition);
  391. if (directory.FileExists("write.lock"))// force lock release, because it was still open when we shut down
  392. {
  393. IndexWriter.Unlock(directory);
  394. // for some reason, just calling unlock doesn't remove this file
  395. directory.DeleteFile("write.lock");
  396. }
  397. if (directory.FileExists("writing-to-index.lock")) // we had an unclean shutdown
  398. {
  399. if (configuration.ResetIndexOnUncleanShutdown)
  400. throw new InvalidOperationException(string.Format("Rude shutdown detected on '{0}' index in '{1}' directory.", indexDefinition.Name, indexFullPath));
  401. CheckIndexAndTryToFix(directory, indexDefinition);
  402. directory.DeleteFile("writing-to-index.lock");
  403. }
  404. }
  405. }
  406. return directory;
  407. }
  408. private void RegenerateMapReduceIndex(Directory directory, IndexDefinition indexDefinition)
  409. {
  410. // remove old index data
  411. var dirOnDisk = Path.Combine(path, indexDefinition.IndexId.ToString());
  412. IOExtensions.DeleteDirectory(dirOnDisk);
  413. // initialize by new index
  414. System.IO.Directory.CreateDirectory(dirOnDisk);
  415. WriteIndexVersion(directory, indexDefinition);
  416. new IndexWriter(directory, dummyAnalyzer, IndexWriter.MaxFieldLength.UNLIMITED).Dispose();
  417. var start = 0;
  418. const int take = 100;
  419. documentDatabase.TransactionalStorage.Batch(actions =>
  420. {
  421. IList<ReduceTypePerKey> reduceKeysAndTypes;
  422. do
  423. {
  424. reduceKeysAndTypes = actions.MapReduce.GetReduceKeysAndTypes(indexDefinition.IndexId, start, take).ToList();
  425. start += take;
  426. var keysToScheduleOnLevel2 =
  427. reduceKeysAndTypes.Where(x => x.OperationTypeToPerform == ReduceType.MultiStep).ToList();
  428. var keysToScheduleOnLevel0 =
  429. reduceKeysAndTypes.Where(x => x.OperationTypeToPerform == ReduceType.SingleStep).ToList();
  430. var itemsToScheduleOnLevel2 = keysToScheduleOnLevel2.Select(x => new ReduceKeyAndBucket(0, x.ReduceKey)).ToList();
  431. var itemsToScheduleOnLevel0 = new List<ReduceKeyAndBucket>();
  432. foreach (var reduceKey in keysToScheduleOnLevel0.Select(x => x.ReduceKey))
  433. {
  434. var mappedBuckets = actions.MapReduce.GetMappedBuckets(indexDefinition.IndexId, reduceKey, CancellationToken.None).Distinct();
  435. itemsToScheduleOnLevel0.AddRange(mappedBuckets.Select(x => new ReduceKeyAndBucket(x, reduceKey)));
  436. }
  437. foreach (var itemToReduce in itemsToScheduleOnLevel2)
  438. {
  439. actions.MapReduce.ScheduleReductions(indexDefinition.IndexId, 2, itemToReduce);
  440. actions.General.MaybePulseTransaction();
  441. }
  442. foreach (var itemToReduce in itemsToScheduleOnLevel0)
  443. {
  444. actions.MapReduce.ScheduleReductions(indexDefinition.IndexId, 0, itemToReduce);
  445. actions.General.MaybePulseTransaction();
  446. }
  447. } while (reduceKeysAndTypes.Count > 0);
  448. });
  449. }
  450. private void ResetLastIndexedEtag(IndexDefinition indexDefinition, Etag lastIndexedEtag, DateTime timestamp)
  451. {
  452. documentDatabase.TransactionalStorage.Batch(
  453. accessor =>
  454. accessor.Indexing.UpdateLastIndexed(indexDefinition.IndexId, lastIndexedEtag, timestamp));
  455. }
  456. internal Etag GetLastEtagForIndex(Index index)
  457. {
  458. if (index.IsMapReduce)
  459. return null;
  460. IndexStats stats = null;
  461. documentDatabase.TransactionalStorage.Batch(accessor => stats = accessor.Indexing.GetIndexStats(index.IndexId));
  462. return stats != null ? stats.LastIndexedEtag : Etag.Empty;
  463. }
  464. public static string IndexVersionFileName(IndexDefinition indexDefinition)
  465. {
  466. if (indexDefinition.IsMapReduce)
  467. return "mapReduce.version";
  468. return "index.version";
  469. }
  470. public static void WriteIndexVersion(Directory directory, IndexDefinition indexDefinition)
  471. {
  472. var version = IndexVersion;
  473. if (indexDefinition.IsMapReduce)
  474. {
  475. version = MapReduceIndexVersion;
  476. }
  477. using (var indexOutput = directory.CreateOutput(IndexVersionFileName(indexDefinition)))
  478. {
  479. indexOutput.WriteString(version);
  480. indexOutput.Flush();
  481. }
  482. }
  483. private static void EnsureIndexVersionMatches(Directory directory, IndexDefinition indexDefinition)
  484. {
  485. var versionToCheck = IndexVersion;
  486. if (indexDefinition.IsMapReduce)
  487. {
  488. versionToCheck = MapReduceIndexVersion;
  489. }
  490. var indexVersion = IndexVersionFileName(indexDefinition);
  491. if (directory.FileExists(indexVersion) == false)
  492. {
  493. throw new InvalidOperationException("Could not find " + indexVersion + " " + indexDefinition.IndexId + ", resetting index");
  494. }
  495. using (var indexInput = directory.OpenInput(indexVersion))
  496. {
  497. var versionFromDisk = indexInput.ReadString();
  498. if (versionFromDisk != versionToCheck)
  499. throw new InvalidOperationException("Index " + indexDefinition.IndexId + " is of version " + versionFromDisk +
  500. " which is not compatible with " + versionToCheck + ", resetting index");
  501. }
  502. }
  503. private static void CheckIndexAndTryToFix(Directory directory, IndexDefinition indexDefinition)
  504. {
  505. startupLog.Warn("Unclean shutdown detected on {0}, checking the index for errors. This may take a while.", indexDefinition.Name);
  506. var memoryStream = new MemoryStream();
  507. var stringWriter = new StreamWriter(memoryStream);
  508. var checkIndex = new CheckIndex(directory);
  509. if (startupLog.IsWarnEnabled)
  510. checkIndex.SetInfoStream(stringWriter);
  511. var sp = Stopwatch.StartNew();
  512. var status = checkIndex.CheckIndex_Renamed_Method();
  513. sp.Stop();
  514. if (startupLog.IsWarnEnabled)
  515. {
  516. startupLog.Warn("Checking index {0} took: {1}, clean: {2}", indexDefinition.Name, sp.Elapsed, status.clean);
  517. memoryStream.Position = 0;
  518. log.Warn(new StreamReader(memoryStream).ReadToEnd());
  519. }
  520. if (status.clean)
  521. return;
  522. startupLog.Warn("Attempting to fix index: {0}", indexDefinition.Name);
  523. sp.Restart();
  524. checkIndex.FixIndex(status);
  525. startupLog.Warn("Fixed index {0} in {1}", indexDefinition.Name, sp.Elapsed);
  526. }
  527. public void StoreCommitPoint(string indexName, IndexCommitPoint indexCommit)
  528. {
  529. if (indexCommit.SegmentsInfo == null || indexCommit.SegmentsInfo.IsIndexCorrupted)
  530. return;
  531. var directoryName = indexCommit.SegmentsInfo.Generation.ToString("0000000000000000000", CultureInfo.InvariantCulture);
  532. var commitPointDirectory = new IndexCommitPointDirectory(path, indexName, directoryName);
  533. if (System.IO.Directory.Exists(commitPointDirectory.AllCommitPointsFullPath) == false)
  534. {
  535. System.IO.Directory.CreateDirectory(commitPointDirectory.AllCommitPointsFullPath);
  536. }
  537. System.IO.Directory.CreateDirectory(commitPointDirectory.FullPath);
  538. using (var commitPointFile = File.Create(commitPointDirectory.FileFullPath))
  539. using (var sw = new StreamWriter(commitPointFile))
  540. {
  541. var jsonSerializer = JsonExtensions.CreateDefaultJsonSerializer();
  542. var textWriter = new JsonTextWriter(sw);
  543. jsonSerializer.Serialize(textWriter, indexCommit);
  544. sw.Flush();
  545. }
  546. var currentSegmentsFileName = indexCommit.SegmentsInfo.SegmentsFileName;
  547. File.Copy(Path.Combine(commitPointDirectory.IndexFullPath, currentSegmentsFileName),
  548. Path.Combine(commitPointDirectory.FullPath, currentSegmentsFileName),
  549. overwrite: true);
  550. var storedCommitPoints = System.IO.Directory.GetDirectories(commitPointDirectory.AllCommitPointsFullPath);
  551. if (storedCommitPoints.Length > configuration.MaxNumberOfStoredCommitPoints)
  552. {
  553. foreach (var toDelete in storedCommitPoints.Take(storedCommitPoints.Length - configuration.MaxNumberOfStoredCommitPoints))
  554. {
  555. IOExtensions.DeleteDirectory(toDelete);
  556. }
  557. }
  558. }
  559. public void AddDeletedKeysToCommitPoints(IndexDefinition indexDefinition, string[] deletedKeys)
  560. {
  561. var indexFullPath = Path.Combine(path, indexDefinition.IndexId.ToString());
  562. var existingCommitPoints = IndexCommitPointDirectory.ScanAllCommitPointsDirectory(indexFullPath);
  563. foreach (var commitPointDirectory in existingCommitPoints.Select(commitPoint => new IndexCommitPointDirectory(path, indexDefinition.IndexId.ToString(), commitPoint)))
  564. {
  565. using (var stream = File.Open(commitPointDirectory.DeletedKeysFile, FileMode.OpenOrCreate))
  566. {
  567. stream.Seek(0, SeekOrigin.End);
  568. using (var writer = new StreamWriter(stream))
  569. {
  570. foreach (var deletedKey in deletedKeys)
  571. {
  572. writer.WriteLine(deletedKey);
  573. }
  574. }
  575. }
  576. }
  577. }
  578. private bool TryReusePreviousCommitPointsToRecoverIndex(Directory directory, IndexDefinition indexDefinition, string indexStoragePath, out IndexCommitPoint indexCommit, out string[] keysToDelete)
  579. {
  580. indexCommit = null;
  581. keysToDelete = null;
  582. if (indexDefinition.IsMapReduce)
  583. return false;
  584. var indexFullPath = Path.Combine(indexStoragePath, indexDefinition.IndexId.ToString());
  585. var allCommitPointsFullPath = IndexCommitPointDirectory.GetAllCommitPointsFullPath(indexFullPath);
  586. if (System.IO.Directory.Exists(allCommitPointsFullPath) == false)
  587. return false;
  588. var filesInIndexDirectory = System.IO.Directory.GetFiles(indexFullPath).Select(Path.GetFileName);
  589. var existingCommitPoints =
  590. IndexCommitPointDirectory.ScanAllCommitPointsDirectory(indexFullPath);
  591. Array.Reverse(existingCommitPoints); // start from the highest generation
  592. foreach (var commitPointDirectoryName in existingCommitPoints)
  593. {
  594. try
  595. {
  596. var commitPointDirectory = new IndexCommitPointDirectory(indexStoragePath, indexDefinition.IndexId.ToString(),
  597. commitPointDirectoryName);
  598. if (TryGetCommitPoint(commitPointDirectory, out indexCommit) == false)
  599. {
  600. IOExtensions.DeleteDirectory(commitPointDirectory.FullPath);
  601. continue; // checksum is invalid, try another commit point
  602. }
  603. var missingFile =
  604. indexCommit.SegmentsInfo.ReferencedFiles.Any(
  605. referencedFile => filesInIndexDirectory.Contains(referencedFile) == false);
  606. if (missingFile)
  607. {
  608. IOExtensions.DeleteDirectory(commitPointDirectory.FullPath);
  609. continue; // there are some missing files, try another commit point
  610. }
  611. var storedSegmentsFile = indexCommit.SegmentsInfo.SegmentsFileName;
  612. // here there should be only one segments_N file, however remove all if there is more
  613. foreach (var currentSegmentsFile in System.IO.Directory.GetFiles(commitPointDirectory.IndexFullPath, "segments_*"))
  614. {
  615. File.Delete(currentSegmentsFile);
  616. }
  617. // copy old segments_N file
  618. File.Copy(Path.Combine(commitPointDirectory.FullPath, storedSegmentsFile),
  619. Path.Combine(commitPointDirectory.IndexFullPath, storedSegmentsFile), true);
  620. try
  621. {
  622. // update segments.gen file
  623. using (var genOutput = directory.CreateOutput(IndexFileNames.SEGMENTS_GEN))
  624. {
  625. genOutput.WriteInt(SegmentInfos.FORMAT_LOCKLESS);
  626. genOutput.WriteLong(indexCommit.SegmentsInfo.Generation);
  627. genOutput.WriteLong(indexCommit.SegmentsInfo.Generation);
  628. }
  629. }
  630. catch (Exception)
  631. {
  632. // here we can ignore, segments.gen is used only as fallback
  633. }
  634. if (File.Exists(commitPointDirectory.DeletedKeysFile))
  635. keysToDelete = File.ReadLines(commitPointDirectory.DeletedKeysFile).ToArray();
  636. return true;
  637. }
  638. catch (Exception ex)
  639. {
  640. startupLog.WarnException("Could not recover an index named '" + indexDefinition.IndexId +
  641. "'from segments of the following generation " + commitPointDirectoryName, ex);
  642. }
  643. }
  644. return false;
  645. }
  646. public static IndexSegmentsInfo GetCurrentSegmentsInfo(string indexName, Directory directory)
  647. {
  648. var segmentInfos = new SegmentInfos();
  649. var result = new IndexSegmentsInfo();
  650. try
  651. {
  652. segmentInfos.Read(directory);
  653. result.Generation = segmentInfos.Generation;
  654. result.SegmentsFileName = segmentInfos.GetCurrentSegmentFileName();
  655. result.ReferencedFiles = segmentInfos.Files(directory, false);
  656. }
  657. catch (CorruptIndexException ex)
  658. {
  659. log.WarnException(string.Format("Could not read segment information for an index '{0}'", indexName), ex);
  660. result.IsIndexCorrupted = true;
  661. }
  662. return result;
  663. }
  664. public static bool TryGetCommitPoint(IndexCommitPointDirectory commitPointDirectory, out IndexCommitPoint indexCommit)
  665. {
  666. using (var commitPointFile = File.OpenRead(commitPointDirectory.FileFullPath))
  667. {
  668. try
  669. {
  670. var textReader = new JsonTextReader(new StreamReader(commitPointFile));
  671. var jsonCommitPoint = RavenJObject.Load(textReader);
  672. var jsonEtag = jsonCommitPoint.Value<RavenJToken>("HighestCommitedETag");
  673. Etag recoveredEtag = null;
  674. if (jsonEtag.Type == JTokenType.Object) // backward compatibility - HighestCommitedETag is written as {"Restarts":123,"Changes":1}
  675. {
  676. jsonCommitPoint.Remove("HighestCommitedETag");
  677. recoveredEtag = new Etag(UuidType.Documents, jsonEtag.Value<long>("Restarts"), jsonEtag.Value<long>("Changes"));
  678. }
  679. indexCommit = jsonCommitPoint.JsonDeserialization<IndexCommitPoint>();
  680. if (indexCommit == null)
  681. return false;
  682. if (recoveredEtag != null)
  683. indexCommit.HighestCommitedETag = recoveredEtag;
  684. if (indexCommit.HighestCommitedETag == null || indexCommit.HighestCommitedETag.CompareTo(Etag.Empty) == 0)
  685. return false;
  686. return true;
  687. }
  688. catch (Exception e)
  689. {
  690. log.Warn("Could not get commit point from the following location {0}. Exception {1}", commitPointDirectory.FileFullPath, e);
  691. indexCommit = null;
  692. return false;
  693. }
  694. }
  695. }
  696. internal Directory MakeRAMDirectoryPhysical(RAMDirectory ramDir, IndexDefinition indexDefinition)
  697. {
  698. var newDir = new LuceneCodecDirectory(Path.Combine(path, indexDefinition.IndexId.ToString()), documentDatabase.IndexCodecs.OfType<AbstractIndexCodec>());
  699. Directory.Copy(ramDir, newDir, false);
  700. return newDir;
  701. }
  702. private Index CreateIndexImplementation(IndexDefinition indexDefinition, Directory directory)
  703. {
  704. var viewGenerator = indexDefinitionStorage.GetViewGenerator(indexDefinition.IndexId);
  705. var indexImplementation = indexDefinition.IsMapReduce
  706. ? (Index)new MapReduceIndex(directory, indexDefinition.IndexId, indexDefinition, viewGenerator, documentDatabase.WorkContext)
  707. : new SimpleIndex(directory, indexDefinition.IndexId, indexDefinition, viewGenerator, documentDatabase.WorkContext);
  708. configuration.Container.SatisfyImportsOnce(indexImplementation);
  709. return indexImplementation;
  710. }
  711. public int[] Indexes
  712. {
  713. get { return indexes.Keys.ToArray(); }
  714. }
  715. public string[] IndexNames
  716. {
  717. get { return indexes.Values.Select(x => x.PublicName).ToArray(); }
  718. }
  719. public bool HasIndex(string index)
  720. {
  721. if (index == null)
  722. return false;
  723. return indexes.Any(x => String.Compare(index, x.Value.PublicName, StringComparison.OrdinalIgnoreCase) == 0);
  724. }
  725. public void Dispose()
  726. {
  727. var exceptionAggregator = new ExceptionAggregator(log, "Could not properly close index storage");
  728. exceptionAggregator.Execute(FlushMapIndexes);
  729. exceptionAggregator.Execute(FlushReduceIndexes);
  730. exceptionAggregator.Execute(() => Parallel.ForEach(indexes.Values, index => exceptionAggregator.Execute(index.Dispose)));
  731. exceptionAggregator.Execute(() => dummyAnalyzer.Close());
  732. exceptionAggregator.Execute(() =>
  733. {
  734. if (crashMarker != null)
  735. crashMarker.Dispose();
  736. });
  737. exceptionAggregator.ThrowIfNeeded();
  738. }
  739. public void DeleteIndex(string name)
  740. {
  741. var value = TryIndexByName(name);
  742. if (value == null)
  743. return;
  744. DeleteIndex(value.indexId);
  745. }
  746. public void DeleteIndex(int id)
  747. {
  748. var value = GetIndexInstance(id);
  749. if (value == null)
  750. {
  751. log.Debug("Ignoring delete for non existing index {0}", id);
  752. return;
  753. }
  754. documentDatabase.TransactionalStorage.Batch(accessor =>
  755. accessor.Lists.Remove("Raven/Indexes/QueryTime", value.PublicName));
  756. log.Debug("Deleting index {0}", value.PublicName);
  757. value.Dispose();
  758. Index ignored;
  759. var dirOnDisk = Path.Combine(path, id.ToString());
  760. if (!indexes.TryRemove(id, out ignored) || !System.IO.Directory.Exists(dirOnDisk))
  761. return;
  762. UpdateIndexMappingFile();
  763. }
  764. public void DeleteIndexData(int id)
  765. {
  766. var dirOnDisk = Path.Combine(path, id.ToString(CultureInfo.InvariantCulture));
  767. IOExtensions.DeleteDirectory(dirOnDisk);
  768. }
  769. public void CreateIndexImplementation(IndexDefinition indexDefinition)
  770. {
  771. log.Debug("Creating index {0} with id {1}", indexDefinition.IndexId, indexDefinition.Name);
  772. IndexDefinitionStorage.ResolveAnalyzers(indexDefinition);
  773. if (TryIndexByName(indexDefinition.Name) != null)
  774. {
  775. throw new InvalidOperationException("Index " + indexDefinition.Name + " already exists");
  776. }
  777. var addedIndex = indexes.AddOrUpdate(indexDefinition.IndexId, n =>
  778. {
  779. var directory = OpenOrCreateLuceneDirectory(indexDefinition);
  780. return CreateIndexImplementation(indexDefinition, directory);
  781. }, (s, index) => index);
  782. //prevent corrupted index when creating a map-reduce index
  783. //need to do this for every map reduce index, even when indexing is enabled,
  784. if (addedIndex.IsMapReduce)
  785. {
  786. addedIndex.EnsureIndexWriter();
  787. addedIndex.Flush(Etag.Empty);
  788. }
  789. UpdateIndexMappingFile();
  790. }
  791. public Query GetDocumentQuery(string index, IndexQuery query, OrderedPartCollection<AbstractIndexQueryTrigger> indexQueryTriggers)
  792. {
  793. var value = TryIndexByName(index);
  794. if (value == null)
  795. {
  796. log.Debug("Query on non existing index {0}", index);
  797. throw new InvalidOperationException("Index '" + index + "' does not exists");
  798. }
  799. var fieldsToFetch = new FieldsToFetch(new string[0], false, null);
  800. return new Index.IndexQueryOperation(value, query, _ => false, fieldsToFetch, indexQueryTriggers).GetDocumentQuery();
  801. }
  802. private Index TryIndexByName(string name)
  803. {
  804. return indexes.Where(index => String.Compare(index.Value.PublicName, name, StringComparison.OrdinalIgnoreCase) == 0)
  805. .Select(x => x.Value)
  806. .FirstOrDefault();
  807. }
  808. public IEnumerable<IndexQueryResult> Query(string index,
  809. IndexQuery query,
  810. Func<IndexQueryResult, bool> shouldIncludeInResults,
  811. FieldsToFetch fieldsToFetch,
  812. OrderedPartCollection<AbstractIndexQueryTrigger> indexQueryTriggers,
  813. CancellationToken token,
  814. Action<double> parseTiming = null
  815. )
  816. {
  817. Index value = TryIndexByName(index);
  818. if (value == null)
  819. {
  820. log.Debug("Query on non existing index '{0}'", index);
  821. throw new InvalidOperationException("Index '" + index + "' does not exists");
  822. }
  823. if ((value.Priority.HasFlag(IndexingPriority.Idle) || value.Priority.HasFlag(IndexingPriority.Abandoned)) &&
  824. value.Priority.HasFlag(IndexingPriority.Forced) == false)
  825. {
  826. documentDatabase.TransactionalStorage.Batch(accessor =>
  827. {
  828. value.Priority = IndexingPriority.Normal;
  829. try
  830. {
  831. accessor.Indexing.SetIndexPriority(value.indexId, IndexingPriority.Normal);
  832. }
  833. catch (Exception e)
  834. {
  835. if (accessor.IsWriteConflict(e) == false)
  836. throw;
  837. // we explciitly ignore write conflicts here, it is okay if we got set twice (two concurrent queries, or setting while indexing).
  838. }
  839. documentDatabase.WorkContext.ShouldNotifyAboutWork(() => "Idle index queried");
  840. documentDatabase.Notifications.RaiseNotifications(new IndexChangeNotification()
  841. {
  842. Name = value.PublicName,
  843. Type = IndexChangeTypes.IndexPromotedFromIdle
  844. });
  845. });
  846. }
  847. var indexQueryOperation = new Index.IndexQueryOperation(value, query, shouldIncludeInResults, fieldsToFetch, indexQueryTriggers);
  848. if (parseTiming != null)
  849. parseTiming(indexQueryOperation.QueryParseDuration.TotalMilliseconds);
  850. if (query.Query != null && query.Query.Contains(Constants.IntersectSeparator))
  851. return indexQueryOperation.IntersectionQuery(token);
  852. return indexQueryOperation.Query(token);
  853. }
  854. public IEnumerable<RavenJObject> IndexEntires(
  855. string indexName,
  856. IndexQuery query,
  857. List<string> reduceKeys,
  858. OrderedPartCollection<AbstractIndexQueryTrigger> indexQueryTriggers,
  859. Reference<int> totalResults)
  860. {
  861. Index value = TryIndexByName(indexName);
  862. if (value == null)
  863. {
  864. if (log.IsDebugEnabled)
  865. log.Debug("Query on non existing index '{0}'", indexName);
  866. throw new InvalidOperationException("Index '" + indexName + "' does not exists");
  867. }
  868. var indexQueryOperation = new Index.IndexQueryOperation(value, query, null, new FieldsToFetch(null, false, null), indexQueryTriggers, reduceKeys);
  869. return indexQueryOperation.IndexEntries(totalResults);
  870. }
  871. public void RemoveFromIndex(int index, string[] keys, WorkContext context)
  872. {
  873. Index value;
  874. if (indexes.TryGetValue(index, out value) == false)
  875. {
  876. if (log.IsDebugEnabled)
  877. log.Debug("Removing from non existing index '{0}', ignoring", index);
  878. return;
  879. }
  880. value.Remove(keys, context);
  881. context.RaiseIndexChangeNotification(new IndexChangeNotification
  882. {
  883. Name = value.PublicName,
  884. Type = IndexChangeTypes.RemoveFromIndex
  885. });
  886. }
  887. [CLSCompliant(false)]
  888. public IndexingPerformanceStats Index(int index, AbstractViewGenerator viewGenerator, IndexingBatch batch, WorkContext context, IStorageActionsAccessor actions, DateTime minimumTimestamp, CancellationToken token)
  889. {
  890. Index value;
  891. if (indexes.TryGetValue(index, out value) == false)
  892. {
  893. log.Debug("Tried to index on a non existent index {0}, ignoring", index);
  894. return null;
  895. }
  896. using (CultureHelper.EnsureInvariantCulture())
  897. using (DocumentCacher.SkipSetAndGetDocumentsInDocumentCache())
  898. {
  899. var performance = value.IndexDocuments(viewGenerator, batch, actions, minimumTimestamp, token);
  900. context.RaiseIndexChangeNotification(new IndexChangeNotification
  901. {
  902. Name = value.PublicName,
  903. Type = IndexChangeTypes.MapCompleted
  904. });
  905. return performance;
  906. }
  907. }
  908. [CLSCompliant(false)]
  909. public IndexingPerformanceStats Reduce(
  910. int index,
  911. AbstractViewGenerator viewGenerator,
  912. IEnumerable<IGrouping<int, object>> mappedResults,
  913. int level,
  914. WorkContext context,
  915. IStorageActionsAccessor actions,
  916. HashSet<string> reduceKeys,
  917. int inputCount)
  918. {
  919. Index value;
  920. if (indexes.TryGetValue(index, out value) == false)
  921. {
  922. log.Debug("Tried to index on a non existent index {0}, ignoring", index);
  923. return null;
  924. }
  925. var mapReduceIndex = value as MapReduceIndex;
  926. if (mapReduceIndex == null)
  927. {
  928. log.Warn("Tried to reduce on an index that is not a map/reduce index: {0}, ignoring", index);
  929. return null;
  930. }
  931. using (CultureHelper.EnsureInvariantCulture())
  932. {
  933. var reduceDocuments = new MapReduceIndex.ReduceDocuments(mapReduceIndex, viewGenerator, mappedResults, level, context, actions, reduceKeys, inputCount);

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