Author: mreutegg
Date: Thu Dec 13 10:41:35 2018
New Revision: 1848837

URL: http://svn.apache.org/viewvc?rev=1848837&view=rev
Log:
OAK-7959: MongoDocumentStore causes scan of entire nodes collection on startup

Modified:
    
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
    
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtils.java
    
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtilsTest.java

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java?rev=1848837&r1=1848836&r2=1848837&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
 Thu Dec 13 10:41:35 2018
@@ -274,13 +274,13 @@ public class MongoDocumentStore implemen
         useClientSession = !builder.isClientSessionDisabled()
                 && 
Boolean.parseBoolean(System.getProperty("oak.mongo.clientSession", "true"));
 
-        // counting the number of documents in the nodes collection and
-        // checking existing indexes is performed against the MongoDB primary
-        // this ensure the information is up-to-date and accurate
-        long initialDocsCount = getNodesCount();
+        // reading documents in the nodes collection and checking
+        // existing indexes is performed against the MongoDB primary
+        // this ensures the information is up-to-date and accurate
+        boolean emptyNodesCollection = execute(session -> 
MongoUtils.isCollectionEmpty(nodes, session));
 
         // compound index on _modified and _id
-        if (initialDocsCount == 0) {
+        if (emptyNodesCollection) {
             // this is an empty store, create a compound index
             // on _modified and _id (OAK-3071)
             createIndex(nodes, new String[]{NodeDocument.MODIFIED_IN_SECS, 
Document.ID},
@@ -300,7 +300,7 @@ public class MongoDocumentStore implemen
 
         // index on _deleted for fast lookup of potentially garbage
         // depending on the MongoDB version, create a partial index
-        if (initialDocsCount == 0) {
+        if (emptyNodesCollection) {
             if (mongoStatus.isVersion(3, 2)) {
                 createPartialIndex(nodes, new String[]{DELETED_ONCE, 
MODIFIED_IN_SECS},
                         new boolean[]{true, true}, "{" + DELETED_ONCE + 
":true}");
@@ -320,7 +320,7 @@ public class MongoDocumentStore implemen
         }
 
         // compound index on _sdType and _sdMaxRevTime
-        if (initialDocsCount == 0) {
+        if (emptyNodesCollection) {
             // this is an empty store, create compound index
             // on _sdType and _sdMaxRevTime (OAK-6129)
             createIndex(nodes, new String[]{SD_TYPE, SD_MAX_REV_TIME_IN_SECS},
@@ -1853,25 +1853,6 @@ public class MongoDocumentStore implemen
         });
     }
 
-    /**
-     * Returns the number of documents in the {@link #nodes} collection. The 
read
-     * always happens on the MongoDB primary.
-     *
-     * @return the number of documents in the {@link #nodes} collection.
-     */
-    private long getNodesCount() {
-        return execute(session -> {
-            MongoCollection<?> c = 
nodes.withReadPreference(ReadPreference.primary());
-            long count;
-            if (session != null) {
-                count = c.countDocuments(session);
-            } else {
-                count = c.countDocuments();
-            }
-            return count;
-        });
-    }
-
     private boolean withClientSession() {
         return status.isClientSessionSupported() && useClientSession;
     }

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtils.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtils.java?rev=1848837&r1=1848836&r2=1848837&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtils.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtils.java
 Thu Dec 13 10:41:35 2018
@@ -25,12 +25,17 @@ import com.mongodb.MongoException;
 import com.mongodb.MongoNotPrimaryException;
 import com.mongodb.MongoSocketException;
 import com.mongodb.MongoWriteConcernException;
+import com.mongodb.ReadPreference;
 import com.mongodb.WriteConcernException;
+import com.mongodb.client.ClientSession;
+import com.mongodb.client.FindIterable;
 import com.mongodb.client.MongoCollection;
 import com.mongodb.client.model.IndexOptions;
 
 import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException.Type;
 import org.bson.Document;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
@@ -140,6 +145,26 @@ class MongoUtils {
     }
 
     /**
+     * Returns {@code true} if the given collection is empty, {@code false}
+     * otherwise. The check always happens on the MongoDB primary.
+     *
+     * @param collection the collection to check.
+     * @param session an optional client session.
+     * @return {@code true} if empty, {@code false} otherwise.
+     */
+    static boolean isCollectionEmpty(@NotNull MongoCollection<?> collection,
+                                     @Nullable ClientSession session) {
+        MongoCollection<?> c = 
collection.withReadPreference(ReadPreference.primary());
+        FindIterable<BasicDBObject> result;
+        if (session != null) {
+            result = c.find(session, BasicDBObject.class);
+        } else {
+            result = c.find(BasicDBObject.class);
+        }
+        return result.limit(1).first() == null;
+    }
+
+    /**
      * Returns the {@code DocumentStoreException} {@link Type} for the given
      * throwable.
      *

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtilsTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtilsTest.java?rev=1848837&r1=1848836&r2=1848837&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtilsTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoUtilsTest.java
 Thu Dec 13 10:41:35 2018
@@ -20,14 +20,17 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.mongodb.BasicDBObject;
 import com.mongodb.DuplicateKeyException;
 import com.mongodb.MongoCommandException;
 import com.mongodb.MongoException;
 import com.mongodb.MongoSocketException;
 import com.mongodb.ServerAddress;
 import com.mongodb.WriteConcernException;
+import com.mongodb.client.ClientSession;
 import com.mongodb.client.MongoCollection;
 
+import org.apache.jackrabbit.oak.plugins.document.Collection;
 import org.apache.jackrabbit.oak.plugins.document.MongoConnectionFactory;
 import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
 import org.bson.BsonDocument;
@@ -155,6 +158,48 @@ public class MongoUtilsTest {
         assertEquals(TRANSIENT, getDocumentStoreExceptionTypeFor(new 
MongoSocketException("message", new ServerAddress())));
     }
 
+    @Test
+    public void isCollectionEmpty() {
+        MongoConnection c = connectionFactory.getConnection();
+        assertNotNull(c);
+        c.getDatabase().drop();
+
+        String collectionName = Collection.NODES.toString();
+        MongoStatus status = new MongoStatus(c.getMongoClient(), 
c.getDBName());
+
+        // consider empty when collection doesn't exist
+        MongoCollection<BasicDBObject> nodes = c.getDatabase()
+                .getCollection(collectionName, BasicDBObject.class);
+        assertTrue(MongoUtils.isCollectionEmpty(nodes, null));
+        if (status.isClientSessionSupported()) {
+            try (ClientSession s = c.getMongoClient().startSession()) {
+                assertTrue(MongoUtils.isCollectionEmpty(nodes, s));
+            }
+        }
+
+        // insert a document
+        nodes.insertOne(new BasicDBObject("p", "v"));
+
+        // check again
+        assertFalse(MongoUtils.isCollectionEmpty(nodes, null));
+        if (status.isClientSessionSupported()) {
+            try (ClientSession s = c.getMongoClient().startSession()) {
+                assertFalse(MongoUtils.isCollectionEmpty(nodes, s));
+            }
+        }
+
+        // remove any document
+        nodes.deleteMany(new BasicDBObject());
+
+        // must be empty again
+        assertTrue(MongoUtils.isCollectionEmpty(nodes, null));
+        if (status.isClientSessionSupported()) {
+            try (ClientSession s = c.getMongoClient().startSession()) {
+                assertTrue(MongoUtils.isCollectionEmpty(nodes, s));
+            }
+        }
+    }
+
     private static MongoCommandException newMongoCommandException(int code) {
         return new MongoCommandException(response(code), new ServerAddress());
     }


Reply via email to