26

When unit testing with RavenDb, it is often the case that newly added data is retrieved or otherwise processed. This can lead to 'stale index' exceptions e.g.

Bulk operation cancelled because the index is stale and allowStale is false

According to a number of answers

The way to force the database (the IDocumentStore instance) to wait until its indexes are not stale before processing a query or batch operation is to use DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites during the IDocumentStore initialisation, like this:

public class InMemoryRavenSessionProvider : IRavenSessionProvider
{
    private static IDocumentStore documentStore;

    public static IDocumentStore DocumentStore
    {
        get { return (documentStore ?? (documentStore = CreateDocumentStore())); }
    }

    private static IDocumentStore CreateDocumentStore()
    {
        var store = new EmbeddableDocumentStore
        {
            RunInMemory = true,
            Conventions = new DocumentConvention
            {
                DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites,
                IdentityPartsSeparator = "-"
            }
        };
        store.Initialize();
        IndexCreation.CreateIndexes(typeof (RavenIndexes).Assembly, store);
        return store;
    }

    public IDocumentSession GetSession()
    {
        return DocumentStore.OpenSession();
    }
}

Unfortunately, the code above does not work. I am still receiving exceptions regarding stale indexes. These can be resolved by putting in dummy queries that include .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()).

This is fine, as long as these can be contained in the Unit Test, but what if they can't? I am finding that these WaitForNonStaleResults* calls are creeping into production code just so I can get unit-tests to pass.

So, is there a sure fire way, using the latest version of RavenDb, to force the indexes to freshen before allowing commands to be processed - for the purposes of unit testing only?

Edit 1

Here is a solution based on the answer give below that forces a wait until the index is not stale. I have written it as an extension method for the sake of unit-testing convenience;

public static class IDocumentSessionExt
{
    public static void ClearStaleIndexes(this IDocumentSession db)
    {
        while (db.Advanced.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)
        {
            Thread.Sleep(10);
        }
    }
}

And here is a Unit Test that was using the verbose WaitForNonStaleResultsAsOfLastWrite technique but now uses the neater extension method.

[Fact]
public void Should_return_list_of_Relationships_for_given_mentor()
{
    using (var db = Fake.Db())
    {
        var mentorId = Fake.Mentor(db).Id;
        Fake.Relationship(db, mentorId, Fake.Mentee(db).Id);
        Fake.Relationship(db, mentorId, Fake.Mentee(db).Id);
        Fake.Relationship(db, Fake.Mentor(db).Id, Fake.Mentee(db).Id);

        //db.Query<Relationship>()
        //  .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
        //  .Count()
        //  .ShouldBe(3);

        db.ClearStaleIndexes();
        db.Query<Relationship>().Count().ShouldBe(3);
        MentorService.GetRelationships(db, mentorId).Count.ShouldBe(2);
    }
}
Community
  • 1
  • 1
biofractal
  • 18,963
  • 12
  • 70
  • 116
  • 2
    This is now held against the DocumentStore rather than the DocumentSession, so the extension method would change to use something like db.Advanced.DocumentStore.DatabaseCommands.GetStatistics().StaleIndexes.Any(), or just hand the DocumentStore in directly if you can – adrian Aug 12 '14 at 15:12

3 Answers3

31

If you have a Map/Reduce index, DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites won't work. You need to use an alternative method.

In your units tests, call code like this, straight after you've inserted any data, this will force the all indexes to update before you do anything else:

while (documentStore.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)
{
    Thread.Sleep(10);
}

Update You can of course put it in an extension method if you want to:

public static class IDocumentSessionExt
{
    public static void ClearStaleIndexes(this IDocumentSession db)
    {
        while (db.Advanced.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)
        {
            Thread.Sleep(10);
        }
    }
}

Then you can say:

db.ClearStaleIndexes();
Anthony Grist
  • 38,173
  • 8
  • 62
  • 76
Matt Warren
  • 10,279
  • 7
  • 48
  • 63
  • I will try the `Thread.Sleep(10)` idea. However I have Map indexes (they have a map function but not a reduce function) and it is these indexes that are throwing the `Bulk operation .. index is stale` exception. Is the `ConsistencyOptions.QueryYourWrites` expected to work for Map-only indexes? – biofractal Apr 25 '12 at 14:05
  • It should do, but the Bulk Operation might be the issue. Use the code above to make sure all indexes are upto date (after inserting docs and before querying) and you should be fine. – Matt Warren Apr 25 '12 at 14:18
  • 1
    Please see Edit 1. It works a treat, although you might want to update your answer to the latest syntax. Encapsulating the wait as an extension method makes everything nice and tidy :-) – biofractal Apr 25 '12 at 15:02
  • Helpers like this are others seem to be included in a Test Helper package from the creators of Raven: http://ravendb.net/docs/samples/raven-tests/createraventests (i've not used it yet) – Matt Kocaj Aug 21 '13 at 00:11
15

You can actually add a query listener on the DocumentStore to wait for nonstale results. This can be used just for unit tests as it is on the document store and not each operation.

// Initialise the Store.
var documentStore = new EmbeddableDocumentStore
                {
                    RunInMemory = true
                };
documentStore.Initialize();

// Force queries to wait for indexes to catch up. Unit Testing only :P
documentStore.RegisterListener(new NoStaleQueriesListener());

....


#region Nested type: NoStaleQueriesListener

public class NoStaleQueriesListener : IDocumentQueryListener
{
    #region Implementation of IDocumentQueryListener

    public void BeforeQueryExecuted(IDocumentQueryCustomization queryCustomization)
    {
        queryCustomization.WaitForNonStaleResults();
    }

    #endregion
}

#endregion

(Shamelessly stolen from RavenDB how to flush?)

Community
  • 1
  • 1
Rangoric
  • 2,739
  • 1
  • 18
  • 18
  • 2
    I tried this out and it worked for normal `Query<>()` calls (no stale indexes) but it failed for a Map index (no reduce) called via `db.Advanced.DatabaseCommands.UpdateByIndex(..)`. Is this the expected behaviour? – biofractal Apr 25 '12 at 14:59
  • Hrm, that would be a different from a regular query. I'll have to look into this (or suggest something to be implemented) as there should be a non-invasive way to do this. – Rangoric Apr 25 '12 at 15:49
  • 2
    Using a listener would certainly be a better low-intervention solution than manually calling an extension method as per the accepted answer, however so far none of the DocumentStore level fixes seem to work with a bulk update i.e. when I use an index via `UpdateByIndex(..)`. Good luck! – biofractal Apr 26 '12 at 09:03
  • Did anyone ever find a global, non-intrusive solution that works for all cases? – Yngvar Kristiansen Jun 27 '14 at 11:47
2

Be aware that StaleIndexes also include abondoned and disabled indices - which will never get up to date.

So to avoid waiting indefinetely use this property instead:

 var staleIndices = store.DatabaseCommands.GetStatistics().CountOfStaleIndexesExcludingDisabledAndAbandoned;
  • Just bumped into an issue because StaleIndexes also include Abandoned ones. Your approach seems to solve it. – Fjut Nov 06 '17 at 09:53