From 9f0904fda87bfcf921deeccdbaeafe834fbad696 Mon Sep 17 00:00:00 2001
From: Yannick Lecaillez <yannick.lecaillez@forgerock.com>
Date: Fri, 24 Apr 2015 14:30:47 +0000
Subject: [PATCH] OPENDJ-1725: Persistit: very long recovery and many discarded txns after addrate test

---
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ID2CountTest.java                              |  253 ++++
 opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java                                 |   67 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java                             |    2 
 opendj-server-legacy/src/main/java/org/opends/server/backends/RootDSEBackend.java                                      |   48 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/CursorTransformer.java                         |    6 
 opendj-core/clirr-ignored-api-changes.xml                                                                              |    8 
 opendj-server-legacy/resource/schema/02-config.ldif                                                                    |    3 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java                                     |    2 
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java              |   10 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Suffix.java                                    |    5 
 opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java                                         |   58 
 opendj-server-legacy/src/main/java/org/opends/server/monitors/BackendMonitor.java                                      |    2 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java                            |  423 ++----
 opendj-server-legacy/src/main/java/org/opends/server/backends/BackupBackend.java                                       |   32 
 opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java                                                    |   24 
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/DN2IDTest.java                                 |  296 ++++
 opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java                                    |   16 
 opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java                                     |   55 
 opendj-server-legacy/src/main/java/org/opends/server/backends/NullBackend.java                                         |   13 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Count.java                                  |  172 ++
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java                            |    3 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java                                  |   20 
 opendj-server-legacy/src/test/java/org/opends/server/backends/task/TaskBackendTestCase.java                            |    9 
 opendj-server-legacy/src/test/java/org/opends/server/backends/LDIFBackendTestCase.java                                 |   25 
 opendj-server-legacy/src/main/java/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java           |    8 
 opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java                     |    4 
 opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java                                                  |   13 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2ID.java                                     |  201 +++
 opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java                                   |   16 
 opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java                          |   51 
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/StateTest.java                                 |   11 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java                              |   41 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java                                 |  562 +-------
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ControlsTestCase.java                          |    1 
 opendj-server-legacy/src/main/java/org/opends/server/backends/MonitorBackend.java                                      |   39 
 opendj-server-legacy/src/main/java/org/opends/server/backends/ChangelogBackend.java                                    |   14 
 opendj-server-legacy/src/main/java/org/opends/server/backends/MemoryBackend.java                                       |   35 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/SequentialCursor.java                      |   74 +
 opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/EntryContainer.java                                  |   19 
 opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java                                       |   13 
 opendj-server-legacy/src/main/java/org/opends/server/types/DN.java                                                     |    5 
 opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java                                             |   23 
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/Utils.java                                     |    7 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ImportIDSet.java                               |   53 
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/EntryIDSetTest.java                            |   69 
 opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java                |   31 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Cursor.java                                |   34 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Importer.java                                  |  178 --
 opendj-server-legacy/src/main/java/org/opends/server/api/Backend.java                                                  |   40 
 /dev/null                                                                                                              |  191 ---
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml |   31 
 opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java                                 |   33 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java                               |  134 +
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/JebFormat.java                                 |    2 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java                               |   19 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java                                |  133 -
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/SuffixContainer.java                           |    5 
 57 files changed, 1,983 insertions(+), 1,659 deletions(-)

diff --git a/opendj-core/clirr-ignored-api-changes.xml b/opendj-core/clirr-ignored-api-changes.xml
index 69d76ba..4bc7588 100644
--- a/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-core/clirr-ignored-api-changes.xml
@@ -21,7 +21,7 @@
   !
   ! CDDL HEADER END
   !
-  !      Copyright 2014 ForgeRock AS
+  !      Copyright 2014-2015 ForgeRock AS
   !
 -->
 <differences>
@@ -420,4 +420,10 @@
     <method>java.util.Comparator comparator(org.forgerock.opendj.ldap.schema.Schema)</method>
     <justification>OPENDJ-1689 method has been removed because all matching rules should support the default comparator</justification>
   </difference>
+  <difference>
+    <className>org/forgerock/opendj/ldap/ByteSequence</className>
+    <differenceType>7012</differenceType>
+    <method>boolean startsWith(org.forgerock.opendj.ldap.ByteSequence)</method>
+    <justification>Lack of startsWith() forced to re-implement it multiple times at different location</justification>
+  </difference>
 </differences>
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java
index 9676275..5893396 100755
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
- *      Portions copyright 2011-2014 ForgeRock AS
+ *      Portions copyright 2011-2015 ForgeRock AS
  */
 package org.forgerock.opendj.ldap;
 
@@ -317,6 +317,17 @@
     ByteSequence subSequence(int start, int end);
 
     /**
+     * Tests if this ByteSequence starts with the specified prefix.
+     *
+     * @param prefix
+     *            The prefix.
+     * @return true if the byte sequence represented by the argument is a prefix of the byte sequence represented by
+     *         this ByteSequence; false otherwise. Note also that true will be returned if the argument is an empty
+     *         sequence or is equal to this ByteSequence object as determined by the equals(Object) method.
+     */
+    boolean startsWith(ByteSequence prefix);
+
+    /**
      * Returns the Base64 encoded string representation of this byte string.
      *
      * @return The Base64 encoded string representation of this byte string.
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java
index ab13d24..565c5d3 100755
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java
@@ -608,11 +608,13 @@
      * @return The {@link ByteSequenceReader} which can be used to incrementally
      *         read and decode data from this byte string.
      */
+    @Override
     public ByteSequenceReader asReader() {
         return new ByteSequenceReader(this);
     }
 
     /** {@inheritDoc} */
+    @Override
     public byte byteAt(final int index) {
         if (index >= length || index < 0) {
             throw new IndexOutOfBoundsException();
@@ -621,12 +623,14 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public int compareTo(final byte[] bytes, final int offset, final int length) {
         checkArrayBounds(bytes, offset, length);
         return compareTo(this.buffer, this.offset, this.length, bytes, offset, length);
     }
 
     /** {@inheritDoc} */
+    @Override
     public int compareTo(final ByteSequence o) {
         if (this == o) {
             return 0;
@@ -635,12 +639,14 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public byte[] copyTo(final byte[] bytes) {
         copyTo(bytes, 0);
         return bytes;
     }
 
     /** {@inheritDoc} */
+    @Override
     public byte[] copyTo(final byte[] bytes, final int offset) {
         if (offset < 0) {
             throw new IndexOutOfBoundsException();
@@ -650,6 +656,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public ByteBuffer copyTo(final ByteBuffer byteBuffer) {
         byteBuffer.put(buffer, offset, length);
         byteBuffer.flip();
@@ -657,6 +664,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public ByteStringBuilder copyTo(final ByteStringBuilder builder) {
         builder.append(buffer, offset, length);
         return builder;
@@ -683,12 +691,14 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public OutputStream copyTo(final OutputStream stream) throws IOException {
         stream.write(buffer, offset, length);
         return stream;
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean equals(final byte[] bytes, final int offset, final int length) {
         checkArrayBounds(bytes, offset, length);
         return equals(this.buffer, this.offset, this.length, bytes, offset, length);
@@ -734,11 +744,13 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public int length() {
         return length;
     }
 
     /** {@inheritDoc} */
+    @Override
     public ByteString subSequence(final int start, final int end) {
         if (start < 0 || start > end || end > length) {
             throw new IndexOutOfBoundsException();
@@ -747,6 +759,16 @@
     }
 
     /** {@inheritDoc} */
+    @Override
+    public boolean startsWith(ByteSequence prefix) {
+        if (prefix == null || prefix.length() > length) {
+            return false;
+        }
+        return prefix.equals(buffer, 0, prefix.length());
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public String toBase64String() {
         return Base64.encode(this);
     }
@@ -872,11 +894,13 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public byte[] toByteArray() {
         return copyTo(new byte[length]);
     }
 
     /** {@inheritDoc} */
+    @Override
     public ByteString toByteString() {
         return this;
     }
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java
index 5ce2579..da42335 100755
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java
@@ -44,6 +44,12 @@
  * A mutable sequence of bytes backed by a byte array.
  */
 public final class ByteStringBuilder implements ByteSequence {
+
+    /**
+     * Maximum value that can be stored with a compacted representation.
+     */
+    public static final long COMPACTED_MAX_VALUE = 0xFFFFFFFFFFFFFFL;
+
     /** Output stream implementation. */
     private final class OutputStreamImpl extends OutputStream {
         @Override
@@ -230,6 +236,14 @@
             return new SubSequence(subOffset + start, end - start);
         }
 
+        /** {@inheritDoc} */
+        @Override
+        public boolean startsWith(ByteSequence prefix) {
+            if (prefix == null || prefix.length() > length) {
+                return false;
+            }
+            return prefix.equals(buffer, 0, prefix.length());
+        }
 
         /** {@inheritDoc} */
         @Override
@@ -1178,6 +1192,15 @@
 
     /** {@inheritDoc} */
     @Override
+    public boolean startsWith(ByteSequence prefix) {
+        if (prefix == null || prefix.length() > length) {
+            return false;
+        }
+        return prefix.equals(buffer, 0, prefix.length());
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public String toBase64String() {
         return Base64.encode(this);
     }
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml
index b3be01a..1545223 100644
--- a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml
@@ -22,7 +22,7 @@
   ! CDDL HEADER END
   !
   !
-  !      Copyright 2014 ForgeRock AS.
+  !      Copyright 2014-2015 ForgeRock AS.
   ! -->
 <adm:managed-object abstract="true" name="pluggable-backend"
   plural-name="pluggable-backends" package="org.forgerock.opendj.server.config"
@@ -295,33 +295,4 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
-  <adm:property name="subordinate-indexes-enabled" advanced="true">
-    <adm:synopsis>
-      Indicates whether id2children and id2subtree indexes should be used for
-      this backend. These indexes are used for constraining filtered searches
-      to the search request's scope as well as for generating values for the
-      hasSubordinates and numSubordinates virtual attributes.
-    </adm:synopsis>
-    <adm:description>
-      Subordinate indexing is enabled by default and should only be disabled
-      for specialized use cases. A typical use case is where the backend is
-      to be subjected to heavy add/delete load beneath the same parent entry
-      such as when used as a session database. Disabling the subordinate
-      indexes means that the numSubordinates and hasSubordinates virtual
-      attributes will not be supported.
-    </adm:description>
-    <adm:default-behavior>
-      <adm:defined>
-        <adm:value>true</adm:value>
-      </adm:defined>
-    </adm:default-behavior>
-    <adm:syntax>
-      <adm:boolean />
-    </adm:syntax>
-    <adm:profile name="ldap">
-      <ldap:attribute>
-        <ldap:name>ds-cfg-subordinate-indexes-enabled</ldap:name>
-      </ldap:attribute>
-    </adm:profile>
-  </adm:property>
 </adm:managed-object>
diff --git a/opendj-server-legacy/resource/schema/02-config.ldif b/opendj-server-legacy/resource/schema/02-config.ldif
index c327a4e..bf239ae 100644
--- a/opendj-server-legacy/resource/schema/02-config.ldif
+++ b/opendj-server-legacy/resource/schema/02-config.ldif
@@ -5759,8 +5759,7 @@
         ds-cfg-entries-compressed $
         ds-cfg-compact-encoding $
         ds-cfg-index-filter-analyzer-enabled $
-        ds-cfg-index-filter-analyzer-max-filters $
-        ds-cfg-subordinate-indexes-enabled )
+        ds-cfg-index-filter-analyzer-max-filters )
   X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.23
   NAME 'ds-cfg-persistit-backend'
diff --git a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
index d25fc7a..a8e56b8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
@@ -26,6 +26,7 @@
  */
 package org.opends.guitools.controlpanel.util;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.server.util.StaticUtils.*;
 
@@ -367,10 +368,21 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-  throws DirectoryException
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return numSubordinates(parentDN, false);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
+    checkNotNull(baseDN, "baseDN must not be null");
+    return numSubordinates(baseDN, true) + 1;
+  }
+
+  private long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException
   {
-    ConfigEntry baseEntry = configEntries.get(entryDN);
+    final ConfigEntry baseEntry = configEntries.get(entryDN);
     if (baseEntry == null)
     {
       return -1;
@@ -380,16 +392,13 @@
     {
       return baseEntry.getChildren().size();
     }
-    else
+    long count = 0;
+    for (ConfigEntry child : baseEntry.getChildren().values())
     {
-      long count = 0;
-      for(ConfigEntry child : baseEntry.getChildren().values())
-      {
-        count += numSubordinates(child.getDN(), true);
-        count ++;
-      }
-      return count;
+      count += numSubordinates(child.getDN(), true);
+      count++;
     }
+    return count;
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/api/Backend.java b/opendj-server-legacy/src/main/java/org/opends/server/api/Backend.java
index 636bb23..46896fa 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/api/Backend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/api/Backend.java
@@ -393,23 +393,33 @@
   public abstract ConditionResult hasSubordinates(DN entryDN) throws DirectoryException;
 
   /**
-   * Retrieves the number of subordinates for the requested entry.
+   * Retrieves the number of subordinates immediately below the requested entry.
    *
-   * @param entryDN The distinguished name of the entry.
-   *
-   * @param subtree <code>true</code> to include all entries from the
-   *                      requested entry to the lowest level in the
-   *                      tree or <code>false</code> to only include
-   *                      the entries immediately below the requested
-   *                      entry.
-   *
-   * @return The number of subordinate entries for the requested entry
-   *         or -1 if it can not be determined.
-   *
-   * @throws DirectoryException  If a problem occurs while trying to
-   *                              retrieve the entry.
+   * @param parentDN
+   *          The distinguished name of the parent.
+   * @return The number of subordinate entries for the requested entry.
+   * @throws DirectoryException
+   *           If baseDN isn't a base dn managed by this backend or if a problem occurs while trying to retrieve the
+   *           entry.
+   * @throws NullPointerException
+   *           if baseDN is null.
    */
-  public abstract long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException;
+  public abstract long getNumberOfChildren(DN parentDN) throws DirectoryException;
+
+  /**
+   * Retrieves the number of entries for the specified base DN including all entries from the requested entry to the
+   * lowest level in the tree.
+   *
+   * @param baseDN
+   *          The base distinguished name.
+   * @return The number of subordinate entries including the base dn.
+   * @throws DirectoryException
+   *           If baseDN isn't a base dn managed by this backend or if a problem occurs while trying to retrieve the
+   *           entry.
+   * @throws NullPointerException
+   *           if baseDN is null.
+   */
+  public abstract long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException;
 
   /**
    * Indicates whether an entry with the specified DN exists in the backend. The default
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/BackupBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/BackupBackend.java
index 1a1369f..3151938 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/BackupBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/BackupBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.schema.BooleanSyntax.*;
@@ -328,7 +329,7 @@
   @Override
   public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
   {
-    long ret = numSubordinates(entryDN, false);
+    long ret = getNumberOfSubordinates(entryDN, false);
     if(ret < 0)
     {
       return ConditionResult.UNDEFINED;
@@ -336,19 +337,22 @@
     return ConditionResult.valueOf(ret != 0);
   }
 
-
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
+    checkNotNull(baseDN, "baseDN must not be null");
+    return getNumberOfSubordinates(baseDN, true) + 1;
+  }
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-      throws DirectoryException
-  {
-    // If the requested entry was null, then return undefined.
-    if (entryDN == null)
-    {
-      return -1;
-    }
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return getNumberOfSubordinates(parentDN, false);
+  }
 
+  private long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException
+  {
     // If the requested entry was the backend base entry, then return
     // the number of backup directories.
     if (backupBaseDN.equals(entryDN))
@@ -366,8 +370,9 @@
 
         // If subtree is included, count the number of entries for each
         // backup directory.
-        if (subtree)
+        if (includeSubtree)
         {
+          count++;
           try
           {
             BackupDirectory backupDirectory = backupDirectories.get(dir).getBackupDirectory();
@@ -375,7 +380,8 @@
           }
           catch (Exception e)
           {
-            return -1;
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(
+                entryDN, e.getMessage()));
           }
         }
 
@@ -429,8 +435,6 @@
     }
   }
 
-
-
   /** {@inheritDoc} */
   @Override
   public Entry getEntry(DN entryDN)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/ChangelogBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/ChangelogBackend.java
index b88fb84..382406c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/ChangelogBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/ChangelogBackend.java
@@ -25,6 +25,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.ReplicationMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
@@ -395,9 +396,16 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(final DN entryDN, final boolean subtree) throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(final DN baseDN) throws DirectoryException
   {
-    return -1;
+    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(final DN parentDN) throws DirectoryException
+  {
+    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
   }
 
   /**
@@ -659,7 +667,7 @@
   {
     try
     {
-      return numSubordinates(CHANGELOG_BASE_DN, true) + 1;
+      return getNumberOfEntriesInBaseDN(CHANGELOG_BASE_DN) + 1;
     }
     catch (DirectoryException e)
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
index 0610720..8232acf 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -425,8 +426,27 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-         throws DirectoryException
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return getNumberOfSubordinates(parentDN, false);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
+  {
+    checkNotNull(baseDN, "baseDN must not be null");
+    if (!Arrays.asList(baseDNs).contains(baseDN))
+    {
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY
+          .get(baseDN));
+    }
+    final int baseDNIfExists = childDNs.containsKey(baseDN) ? 1 : 0;
+    return getNumberOfSubordinates(baseDN, true) + baseDNIfExists;
+  }
+
+  private long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException
   {
     backendLock.readLock().lock();
 
@@ -441,30 +461,22 @@
         {
           return 0L;
         }
-        else
-        {
-          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
-              ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY.get(entryDN));
-        }
+        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY
+            .get(entryDN));
       }
-      else
-      {
-        if(!subtree)
-        {
-          return childDNSet.size();
-        }
-        else
-        {
-          long count = 0;
-          for(DN childDN : childDNSet)
-          {
-            count += numSubordinates(childDN, true);
-            count ++;
-          }
-          return count;
-        }
 
+      if (!includeSubtree)
+      {
+        return childDNSet.size();
       }
+
+      long count = 0;
+      for (DN childDN : childDNSet)
+      {
+        count += getNumberOfSubordinates(childDN, true);
+        count++;
+      }
+      return count;
     }
     finally
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/MemoryBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/MemoryBackend.java
index cb45e4b..b85555f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/MemoryBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/MemoryBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -263,7 +264,7 @@
   public synchronized ConditionResult hasSubordinates(DN entryDN)
          throws DirectoryException
   {
-    long ret = numSubordinates(entryDN, false);
+    long ret = getNumberOfSubordinates(entryDN, false);
     if(ret < 0)
     {
       return ConditionResult.UNDEFINED;
@@ -273,11 +274,22 @@
 
   /** {@inheritDoc} */
   @Override
-  public synchronized long numSubordinates(DN entryDN, boolean subtree)
-         throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
+    checkNotNull(baseDN, "baseDN must not be null");
+    return getNumberOfSubordinates(baseDN, true) + 1;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return getNumberOfSubordinates(parentDN, false);
+  }
+
+  private synchronized long getNumberOfSubordinates(DN entryDN, boolean includeSubtree) throws DirectoryException
   {
     // Try to look up the immediate children for the DN
-    Set<DN> children = childDNs.get(entryDN);
+    final Set<DN> children = childDNs.get(entryDN);
     if (children == null)
     {
       if(entryMap.get(entryDN) != null)
@@ -288,20 +300,17 @@
       return -1;
     }
 
-    if(!subtree)
+    if(!includeSubtree)
     {
       return children.size();
     }
-    else
+    long count = 0;
+    for (DN child : children)
     {
-      long count = 0;
-      for(DN child : children)
-      {
-        count += numSubordinates(child, true);
-        count++;
-      }
-      return count;
+      count += getNumberOfSubordinates(child, true);
+      count++;
     }
+    return count;
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/MonitorBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/MonitorBackend.java
index 630215b..a9358e6 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/MonitorBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/MonitorBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
@@ -456,31 +457,39 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(final DN entryDN, final boolean subtree)
-      throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(final DN baseDN) throws DirectoryException {
+    checkNotNull(baseDN, "baseDN must not be null");
+    return getNumberOfSubordinates(baseDN, true) + 1;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(final DN parentDN) throws DirectoryException {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return getNumberOfSubordinates(parentDN, false);
+  }
+
+  private long getNumberOfSubordinates(final DN entryDN, final boolean includeSubtree) throws DirectoryException
   {
     final NavigableMap<DN, MonitorProvider<?>> dit = getDIT();
     if (!dit.containsKey(entryDN))
     {
       return -1L;
     }
-    else
+    long count = 0;
+    final int childDNSize = entryDN.size() + 1;
+    for (final DN dn : dit.tailMap(entryDN, false).navigableKeySet())
     {
-      long count = 0;
-      final int childDNSize = entryDN.size() + 1;
-      for (final DN dn : dit.tailMap(entryDN, false).navigableKeySet())
+      if (!dn.isDescendantOf(entryDN))
       {
-        if (!dn.isDescendantOf(entryDN))
-        {
-          break;
-        }
-        else if (subtree || dn.size() == childDNSize)
-        {
-          count++;
-        }
+        break;
       }
-      return count;
+      else if (includeSubtree || dn.size() == childDNSize)
+      {
+        count++;
+      }
     }
+    return count;
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/NullBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/NullBackend.java
index 31fd185..79f42ae 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/NullBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/NullBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -264,10 +265,16 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-         throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
   {
-    return -1;
+    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/RootDSEBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/RootDSEBackend.java
index 35e9e3a..0c3e804 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/RootDSEBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/RootDSEBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
@@ -341,7 +342,7 @@
   public ConditionResult hasSubordinates(DN entryDN)
          throws DirectoryException
   {
-    long ret = numSubordinates(entryDN, false);
+    final long ret = getNumberOfChildren(entryDN);
     if(ret < 0)
     {
       return ConditionResult.UNDEFINED;
@@ -351,10 +352,36 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-         throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
   {
-    if (entryDN == null || ! entryDN.isRootDN())
+    checkNotNull(baseDN, "baseDN must not be null");
+    if (!baseDN.isRootDN())
+    {
+      return -1;
+    }
+
+    long count = 1;
+    for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
+    {
+      DN subBase = entry.getKey();
+      Backend<?> b = entry.getValue();
+      Entry subBaseEntry = b.getEntry(subBase);
+      if (subBaseEntry != null)
+      {
+        count++;
+        count += b.getNumberOfEntriesInBaseDN(subBase);
+      }
+    }
+
+    return count;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    checkNotNull(parentDN, "parentDN must not be null");
+    if (!parentDN.isRootDN())
     {
       return -1;
     }
@@ -364,20 +391,9 @@
     for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
     {
       DN subBase = entry.getKey();
-      Backend<?> b = entry.getValue();
-      Entry subBaseEntry = b.getEntry(subBase);
+      Entry subBaseEntry = entry.getValue().getEntry(subBase);
       if (subBaseEntry != null)
       {
-        if(subtree)
-        {
-          long subCount = b.numSubordinates(subBase, true);
-          if(subCount < 0)
-          {
-            return -1;
-          }
-
-          count += subCount;
-        }
         count ++;
       }
     }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
index 77e72a1..c6ac617 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.messages.SchemaMessages.*;
@@ -559,9 +560,17 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-         throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
   {
+    checkNotNull(baseDN, "baseDN must not be null");
+    return 1L;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    checkNotNull(parentDN, "parentDN must not be null");
     return 0L;
   }
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java
index 35b26dc..ba33cbc 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.util.ServerConstants.*;
@@ -745,11 +746,18 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-      throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
   {
-    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-        ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
+    checkNotNull(baseDN, "baseDN must not be null");
+    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    checkNotNull(parentDN, "parentDN must not be null");
+    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java
index 5069936..b1906ab 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java
@@ -27,6 +27,7 @@
 package org.opends.server.backends.jeb;
 
 import static com.sleepycat.je.EnvironmentConfig.*;
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.backends.jeb.ConfigurableEnvironment.*;
@@ -35,7 +36,14 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -58,9 +66,30 @@
 import org.opends.server.backends.RebuildConfig;
 import org.opends.server.backends.VerifyConfig;
 import org.opends.server.backends.pluggable.spi.StorageStatus;
-import org.opends.server.core.*;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.core.ServerContext;
 import org.opends.server.extensions.DiskSpaceMonitor;
-import org.opends.server.types.*;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.IdentifiedException;
+import org.opends.server.types.IndexType;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.LDIFImportResult;
+import org.opends.server.types.Operation;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RestoreConfig;
 import org.opends.server.util.RuntimeInformation;
 
 import com.sleepycat.je.DatabaseException;
@@ -384,12 +413,26 @@
     return ConditionResult.valueOf(ret != 0);
   }
 
-
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
+    checkNotNull(baseDN, "baseDN must not be null");
+    EntryContainer ec = rootContainer.getEntryContainer(baseDN);
+    if (ec == null || !ec.getBaseDN().equals(baseDN))
+    {
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(baseDN));
+    }
+    return numSubordinates(baseDN, true);
+  }
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-      throws DirectoryException
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return numSubordinates(parentDN, false);
+  }
+
+  private long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException
   {
     checkRootContainerInitialized();
     EntryContainer ec = rootContainer.getEntryContainer(entryDN);
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/EntryContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/EntryContainer.java
index 5184f4c..d8f36a2 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/EntryContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/EntryContainer.java
@@ -61,7 +61,6 @@
 import com.sleepycat.je.*;
 
 import static com.sleepycat.je.LockMode.*;
-
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.backends.jeb.JebFormat.*;
 import static org.opends.server.core.DirectoryServer.*;
@@ -688,9 +687,10 @@
    * Determine the number of subordinate entries for a given entry.
    *
    * @param entryDN The distinguished name of the entry.
-   * @param subtree <code>true</code> will include all the entries under the
-   *                given entries. <code>false</code> will only return the
-   *                number of entries immediately under the given entry.
+   * @param subtree <code>true</code> will include the entry and all the
+   *                entries under the given entries. <code>false</code>
+   *                will only return the number of entries immediately
+   *                under the given entry.
    * @return The number of subordinate entries for the given entry or -1 if
    *         the entry does not exist.
    * @throws DatabaseException If an error occurs in the JE database.
@@ -702,20 +702,23 @@
     if (entryID != null)
     {
       DatabaseEntry key = new DatabaseEntry(entryIDToDatabase(entryID.longValue()));
-      EntryIDSet entryIDSet;
+      final EntryIDSet entryIDSet;
+      long count;
       if (subtree)
       {
+        count = dn2id.get(null, entryDN, LockMode.DEFAULT) != null ? 1 : 0;
         entryIDSet = id2subtree.readKey(key, null, LockMode.DEFAULT);
       }
       else
       {
+        count = 0;
         entryIDSet = id2children.readKey(key, null, LockMode.DEFAULT);
       }
-      long count = entryIDSet.size();
-      if(count != Long.MAX_VALUE)
+      if(entryIDSet.size() == Long.MAX_VALUE)
       {
-        return count;
+        return -1;
       }
+      return count + entryIDSet.size();
     }
     return -1;
   }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java
index 8716784..94d05ce 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java
@@ -38,6 +38,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -92,18 +93,23 @@
   {
     private ByteString currentKey;
     private ByteString currentValue;
-    private final Exchange ex;
+    private final Exchange exchange;
 
     private CursorImpl(final Exchange exchange)
     {
-      this.ex = exchange;
+      this.exchange = exchange;
     }
 
     @Override
     public void close()
     {
       // Release immediately because this exchange did not come from the txn cache
-      db.releaseExchange(ex);
+      db.releaseExchange(exchange);
+    }
+
+    @Override
+    public boolean isDefined() {
+      return exchange.getValue().isDefined();
     }
 
     @Override
@@ -111,7 +117,8 @@
     {
       if (currentKey == null)
       {
-        currentKey = keyToBytes(ex.getKey());
+        throwIfUndefined();
+        currentKey = ByteString.wrap(exchange.getKey().reset().decodeByteArray());
       }
       return currentKey;
     }
@@ -121,7 +128,8 @@
     {
       if (currentValue == null)
       {
-        currentValue = valueToBytes(ex.getValue());
+        throwIfUndefined();
+        currentValue = ByteString.wrap(exchange.getValue().getByteArray());
       }
       return currentValue;
     }
@@ -132,7 +140,7 @@
       clearCurrentKeyAndValue();
       try
       {
-        return ex.next();
+        return exchange.next();
       }
       catch (final PersistitException e)
       {
@@ -144,11 +152,11 @@
     public boolean positionToKey(final ByteSequence key)
     {
       clearCurrentKeyAndValue();
-      bytesToKey(ex.getKey(), key);
+      bytesToKey(exchange.getKey(), key);
       try
       {
-        ex.fetch();
-        return ex.getValue().isDefined();
+        exchange.fetch();
+        return exchange.getValue().isDefined();
       }
       catch (final PersistitException e)
       {
@@ -160,11 +168,11 @@
     public boolean positionToKeyOrNext(final ByteSequence key)
     {
       clearCurrentKeyAndValue();
-      bytesToKey(ex.getKey(), key);
+      bytesToKey(exchange.getKey(), key);
       try
       {
-        ex.fetch();
-        return ex.getValue().isDefined() || ex.next();
+        exchange.fetch();
+        return exchange.getValue().isDefined() || exchange.next();
       }
       catch (final PersistitException e)
       {
@@ -177,12 +185,12 @@
     {
       // There doesn't seem to be a way to optimize this using Persistit.
       clearCurrentKeyAndValue();
-      ex.getKey().to(Key.BEFORE);
+      exchange.getKey().to(Key.BEFORE);
       try
       {
         for (int i = 0; i <= index; i++)
         {
-          if (!ex.next())
+          if (!exchange.next())
           {
             return false;
           }
@@ -199,10 +207,10 @@
     public boolean positionToLastKey()
     {
       clearCurrentKeyAndValue();
-      ex.getKey().to(Key.AFTER);
+      exchange.getKey().to(Key.AFTER);
       try
       {
-        return ex.previous();
+        return exchange.previous();
       }
       catch (final PersistitException e)
       {
@@ -215,6 +223,12 @@
       currentKey = null;
       currentValue = null;
     }
+
+    private void throwIfUndefined() {
+      if (!isDefined()) {
+        throw new NoSuchElementException();
+      }
+    }
   }
 
   /** PersistIt implementation of the {@link Importer} interface. */
@@ -762,11 +776,6 @@
     return value;
   }
 
-  private ByteString keyToBytes(final Key key)
-  {
-    return ByteString.wrap(key.reset().decodeByteArray());
-  }
-
   private ByteString valueToBytes(final Value value)
   {
     if (value.isDefined())
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
index c93a5b4..634ff2c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
@@ -120,8 +120,7 @@
 
     private MatchingRuleIndex(WriteableTransaction txn, BackendIndexCfg cfg, Indexer indexer)
     {
-      super(getIndexName(attributeType, indexer.getIndexID()), state, cfg.getIndexEntryLimit(), false, txn,
-          entryContainer);
+      super(getIndexName(attributeType, indexer.getIndexID()), state, cfg.getIndexEntryLimit(), txn, entryContainer);
       this.indexer = indexer;
     }
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
index 27b3ec9..f2b0f6e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends.pluggable;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.core.DirectoryServer.*;
@@ -171,7 +172,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       LocalizableMessage message = WARN_JEB_GET_ENTRY_COUNT_FAILED.get(e.getMessage());
       throw new InitializationException(message, e);
     }
@@ -184,7 +184,6 @@
       }
       catch (Exception e)
       {
-        logger.traceException(e);
         throw new InitializationException(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(dn, e), e);
       }
     }
@@ -318,18 +317,72 @@
   @Override
   public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
   {
-    long ret = numSubordinates(entryDN, false);
-    if(ret < 0)
-    {
-      return ConditionResult.UNDEFINED;
+    EntryContainer container;
+    try {
+      container = accessBegin(null, entryDN);
     }
-    return ConditionResult.valueOf(ret != 0);
+    catch (DirectoryException de)
+    {
+      if (de.getResultCode() == ResultCode.UNDEFINED)
+      {
+        return ConditionResult.UNDEFINED;
+      }
+      throw de;
+    }
+
+    container.sharedLock.lock();
+    try
+    {
+      return ConditionResult.valueOf(container.hasSubordinates(entryDN));
+    }
+    catch (StorageRuntimeException e)
+    {
+      throw createDirectoryException(e);
+    }
+    finally
+    {
+      container.sharedLock.unlock();
+      accessEnd();
+    }
   }
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
   {
+    checkNotNull(baseDN, "baseDN must not be null");
+    final EntryContainer ec;
+
+    try {
+      ec = accessBegin(null, baseDN);
+    }
+    catch (DirectoryException de)
+    {
+      throw de;
+    }
+
+    ec.sharedLock.lock();
+    try
+    {
+      return ec.getNumberOfEntriesInBaseDN();
+    }
+    catch (Exception e)
+    {
+      throw new DirectoryException(
+          DirectoryServer.getServerErrorResultCode(), LocalizableMessage.raw(e.getMessage()), e);
+    }
+    finally
+    {
+      ec.sharedLock.unlock();
+      accessEnd();
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    checkNotNull(parentDN, "parentDN must not be null");
     EntryContainer ec;
 
     /*
@@ -337,7 +390,7 @@
      * error if the EntryContainer is null...
      */
     try {
-      ec = accessBegin(null, entryDN);
+      ec = accessBegin(null, parentDN);
     }
     catch (DirectoryException de)
     {
@@ -351,17 +404,31 @@
     ec.sharedLock.lock();
     try
     {
-      long count = ec.getNumSubordinates(entryDN, subtree);
-      if(count == Long.MAX_VALUE)
-      {
-        // The index entry limit has exceeded and there is no count maintained.
-        return -1;
-      }
-      return count;
+      return ec.getNumberOfChildren(parentDN);
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
+      throw createDirectoryException(e);
+    }
+    finally
+    {
+      ec.sharedLock.unlock();
+      accessEnd();
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean entryExists(final DN entryDN) throws DirectoryException
+  {
+    EntryContainer ec = accessBegin(null, entryDN);
+    ec.sharedLock.lock();
+    try
+    {
+      return ec.entryExists(entryDN);
+    }
+    catch (StorageRuntimeException e)
+    {
       throw createDirectoryException(e);
     }
     finally
@@ -383,7 +450,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -406,7 +472,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -430,7 +495,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -455,7 +519,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -488,7 +551,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -512,7 +574,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -552,12 +613,10 @@
     }
     catch (IOException ioe)
     {
-      logger.traceException(ioe);
       throw new DirectoryException(errorRC, ERR_JEB_EXPORT_IO_ERROR.get(ioe.getMessage()), ioe);
     }
     catch (StorageRuntimeException de)
     {
-      logger.traceException(de);
       throw createDirectoryException(de);
     }
     catch (ConfigException ce)
@@ -570,7 +629,6 @@
       {
         throw (DirectoryException) e;
       }
-      logger.traceException(e);
       throw new DirectoryException(errorRC, e.getMessageObject(), e);
     }
     finally
@@ -619,7 +677,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw new DirectoryException(getServerErrorResultCode(), LocalizableMessage.raw(e.getMessage()), e);
     }
     catch (DirectoryException e)
@@ -628,12 +685,10 @@
     }
     catch (OpenDsException e)
     {
-      logger.traceException(e);
       throw new DirectoryException(getServerErrorResultCode(), e.getMessageObject(), e);
     }
     catch (ConfigException e)
     {
-      logger.traceException(e);
       throw new DirectoryException(getServerErrorResultCode(), e.getMessageObject(), e);
     }
     finally
@@ -673,13 +728,10 @@
       {
         rootContainer = getReadOnlyRootContainer();
       }
-
-      VerifyJob verifyJob = new VerifyJob(verifyConfig);
-      return verifyJob.verifyBackend(rootContainer);
+      return new VerifyJob(rootContainer, verifyConfig).verifyBackend();
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       throw createDirectoryException(e);
     }
     finally
@@ -737,28 +789,23 @@
     }
     catch (ExecutionException execEx)
     {
-      logger.traceException(execEx);
-      throw new DirectoryException(errorRC, ERR_EXECUTION_ERROR.get(execEx.getMessage()));
+      throw new DirectoryException(errorRC, ERR_EXECUTION_ERROR.get(execEx.getMessage()), execEx);
     }
     catch (InterruptedException intEx)
     {
-      logger.traceException(intEx);
-      throw new DirectoryException(errorRC, ERR_INTERRUPTED_ERROR.get(intEx.getMessage()));
+      throw new DirectoryException(errorRC, ERR_INTERRUPTED_ERROR.get(intEx.getMessage()), intEx);
     }
     catch (ConfigException ce)
     {
-      logger.traceException(ce);
-      throw new DirectoryException(errorRC, ce.getMessageObject());
+      throw new DirectoryException(errorRC, ce.getMessageObject(), ce);
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
-      throw new DirectoryException(errorRC, LocalizableMessage.raw(e.getMessage()));
+      throw new DirectoryException(errorRC, LocalizableMessage.raw(e.getMessage()), e);
     }
     catch (InitializationException e)
     {
-      logger.traceException(e);
-      throw new InitializationException(e.getMessageObject());
+      throw new InitializationException(e.getMessageObject(), e);
     }
     finally
     {
@@ -961,7 +1008,6 @@
     }
     catch (StorageRuntimeException e)
     {
-      logger.traceException(e);
       LocalizableMessage message = ERR_JEB_OPEN_ENV_FAIL.get(e.getMessage());
       throw new InitializationException(message, e);
     }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/CursorTransformer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/CursorTransformer.java
index aa38f18..edfcb24 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/CursorTransformer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/CursorTransformer.java
@@ -172,6 +172,12 @@
     return input.positionToIndex(index);
   }
 
+  @Override
+  public boolean isDefined()
+  {
+    return input.isDefined();
+  }
+
   private void clearCache()
   {
     cachedTransformedKey = null;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2ID.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2ID.java
index 917c324..0daa986 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2ID.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2ID.java
@@ -27,13 +27,20 @@
 package org.opends.server.backends.pluggable;
 
 import static org.opends.server.backends.pluggable.JebFormat.*;
+import static org.opends.server.backends.pluggable.CursorTransformer.*;
 
+import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.util.promise.Function;
+import org.opends.server.backends.pluggable.spi.Cursor;
 import org.opends.server.backends.pluggable.spi.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.SequentialCursor;
 import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
 import org.opends.server.backends.pluggable.spi.TreeName;
 import org.opends.server.backends.pluggable.spi.WriteableTransaction;
 import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
 
 /**
  * This class represents the DN database, or dn2id, which has one record
@@ -42,19 +49,40 @@
  */
 class DN2ID extends AbstractDatabaseContainer
 {
-  private final int prefixRDNComponents;
+  private static final Function<ByteString, Void, DirectoryException> TO_VOID_KEY =
+      new Function<ByteString, Void, DirectoryException>()
+      {
+        @Override
+        public Void apply(ByteString value) throws DirectoryException
+        {
+          return null;
+        }
+      };
+
+  private static final CursorTransformer.ValueTransformer<ByteString, ByteString, EntryID, Exception> TO_ENTRY_ID =
+      new CursorTransformer.ValueTransformer<ByteString, ByteString, EntryID, Exception>()
+      {
+        @Override
+        public EntryID transform(ByteString key, ByteString value) throws Exception
+        {
+          return new EntryID(value);
+        }
+      };
+
+  private final DN baseDN;
+
 
   /**
    * Create a DN2ID instance for the DN database in a given entryContainer.
    *
    * @param treeName The name of the DN database.
-   * @param entryContainer The entryContainer of the DN database.
+   * @param baseDN The base DN of the database.
    * @throws StorageRuntimeException If an error occurs in the database.
    */
-  DN2ID(TreeName treeName, EntryContainer entryContainer) throws StorageRuntimeException
+  DN2ID(TreeName treeName, DN baseDN) throws StorageRuntimeException
   {
     super(treeName);
-    this.prefixRDNComponents = entryContainer.getBaseDN().size();
+    this.baseDN = baseDN;
   }
 
   /**
@@ -65,11 +93,13 @@
    * @throws StorageRuntimeException If an error occurred while attempting to insert
    * the new record.
    */
-  void put(WriteableTransaction txn, DN dn, EntryID id) throws StorageRuntimeException
+  void put(final WriteableTransaction txn, DN dn, final EntryID id) throws StorageRuntimeException
   {
-    ByteString key = dnToDNKey(dn, prefixRDNComponents);
-    ByteString value = id.toByteString();
-    txn.put(getName(), key, value);
+    txn.put(getName(), dnToKey(dn), id.toByteString());
+  }
+
+  private ByteString dnToKey(DN dn) {
+    return dnToDNKey(dn, baseDN.size());
   }
 
   /**
@@ -82,9 +112,7 @@
    */
   boolean remove(WriteableTransaction txn, DN dn) throws StorageRuntimeException
   {
-    ByteString key = dnToDNKey(dn, prefixRDNComponents);
-
-    return txn.delete(getName(), key);
+    return txn.delete(getName(), dnToKey(dn));
   }
 
   /**
@@ -96,12 +124,153 @@
    */
   EntryID get(ReadableTransaction txn, DN dn) throws StorageRuntimeException
   {
-    ByteString key = dnToDNKey(dn, prefixRDNComponents);
-    ByteString value = txn.read(getName(), key);
-    if (value != null)
+    final ByteString value = txn.read(getName(), dnToKey(dn));
+    return value != null ? new EntryID(value) : null;
+  }
+
+  Cursor<Void, EntryID> openCursor(ReadableTransaction txn, DN dn)
+  {
+    return transformKeysAndValues(openCursor0(txn, dn), TO_VOID_KEY, TO_ENTRY_ID);
+  }
+
+  private Cursor<ByteString, ByteString> openCursor0(ReadableTransaction txn, DN dn) {
+    final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName());
+    cursor.positionToKey(dnToKey(dn));
+    return cursor;
+  }
+
+  SequentialCursor<Void, EntryID> openChildrenCursor(ReadableTransaction txn, DN dn)
+  {
+    return new ChildrenCursor(openCursor0(txn, dn));
+  }
+
+  SequentialCursor<Void, EntryID> openSubordinatesCursor(ReadableTransaction txn, DN dn) {
+    return new SubtreeCursor(openCursor0(txn, dn));
+  }
+
+
+  /**
+   * Check if two DN have a parent-child relationship.
+   *
+   * @param parent
+   *          The potential parent
+   * @param child
+   *          The potential child of parent
+   * @return true if child is a direct children of parent, false otherwise.
+   */
+  static boolean isChild(ByteSequence parent, ByteSequence child)
+  {
+    if (!child.startsWith(parent))
     {
-      return new EntryID(value);
+      return false;
     }
-    return null;
+    // Immediate children should only have one RDN separator past the parent length
+    for (int i = child.length(); i >= parent.length(); i--)
+    {
+      if (child.byteAt(i) == DN.NORMALIZED_RDN_SEPARATOR && i != parent.length())
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Decorator overriding the next() behavior to iterate through children of the entry pointed by the given cursor at
+   * creation.
+   */
+  private static final class ChildrenCursor extends SequentialCursorForwarding {
+    private final ByteStringBuilder builder;
+    private final ByteString parentDN;
+    private boolean cursorOnParent;
+
+    ChildrenCursor(Cursor<ByteString, ByteString> delegate)
+    {
+      super(delegate);
+      builder = new ByteStringBuilder(128);
+      parentDN = delegate.isDefined() ? delegate.getKey() : null;
+      cursorOnParent = true;
+    }
+
+    @Override
+    public boolean next()
+    {
+      if (cursorOnParent) {
+        /** Go to the first children */
+        delegate.next();
+        cursorOnParent = false;
+      } else {
+        /** Go to the next sibling */
+        delegate.positionToKeyOrNext(nextSibling());
+      }
+      return isDefined() && delegate.getKey().startsWith(parentDN);
+    }
+
+    private ByteStringBuilder nextSibling()
+    {
+      return builder.clear().append(delegate.getKey()).append((byte) 0x1);
+    }
+  }
+
+  /**
+   * Decorator overriding the next() behavior to iterate through subordinates of the entry pointed by the given cursor
+   * at creation.
+   */
+  private static final class SubtreeCursor extends SequentialCursorForwarding {
+    private final ByteString baseDN;
+
+    SubtreeCursor(Cursor<ByteString, ByteString> delegate)
+    {
+      super(delegate);
+      baseDN = delegate.isDefined() ? delegate.getKey() : null;
+    }
+
+    @Override
+    public boolean next()
+    {
+      return delegate.next() && delegate.getKey().startsWith(baseDN);
+    }
+  }
+
+  /**
+   * Decorator allowing to partially overrides methods of a given cursor while keeping the default behavior for other
+   * methods.
+   */
+  private static class SequentialCursorForwarding implements SequentialCursor<Void, EntryID> {
+    final Cursor<ByteString, ByteString> delegate;
+
+    SequentialCursorForwarding(Cursor<ByteString, ByteString> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public boolean isDefined()
+    {
+      return delegate.isDefined();
+    }
+
+    @Override
+    public boolean next()
+    {
+      return delegate.next();
+    }
+
+    @Override
+    public Void getKey()
+    {
+      return null;
+    }
+
+    @Override
+    public EntryID getValue()
+    {
+      return new EntryID(delegate.getValue());
+    }
+
+    @Override
+    public void close()
+    {
+      delegate.close();
+    }
   }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java
index 14ceb70..bd68494 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java
@@ -60,10 +60,6 @@
 
   /** The limit on the number of entry IDs that may be indexed by one key. */
   private int indexEntryLimit;
-  /**
-   * Whether to maintain a count of IDs for a key once the entry limit has exceeded.
-   */
-  private final boolean maintainCount;
 
   private final State state;
 
@@ -87,8 +83,6 @@
    *          The state database to persist index state info.
    * @param indexEntryLimit
    *          The configured limit on the number of entry IDs that may be indexed by one key.
-   * @param maintainCount
-   *          Whether to maintain a count of IDs for a key once the entry limit has exceeded.
    * @param txn
    *          a non null database transaction
    * @param entryContainer
@@ -96,12 +90,11 @@
    * @throws StorageRuntimeException
    *           If an error occurs in the database.
    */
-  DefaultIndex(TreeName name, State state, int indexEntryLimit, boolean maintainCount, WriteableTransaction txn,
-      EntryContainer entryContainer) throws StorageRuntimeException
+  DefaultIndex(TreeName name, State state, int indexEntryLimit, WriteableTransaction txn, EntryContainer entryContainer)
+      throws StorageRuntimeException
   {
     super(name);
     this.indexEntryLimit = indexEntryLimit;
-    this.maintainCount = maintainCount;
     this.state = state;
 
     final EnumSet<IndexFlag> flags = state.getIndexFlags(txn, getName());
@@ -137,7 +130,7 @@
     ByteString value = txn.read(getName(), key);
     if (value != null)
     {
-      final ImportIDSet importIDSet = new ImportIDSet(key, codec.decode(key, value), indexEntryLimit, maintainCount);
+      final ImportIDSet importIDSet = new ImportIDSet(key, codec.decode(key, value), indexEntryLimit);
       importIDSet.merge(idsToBeAdded);
       txn.put(getName(), key, importIDSet.valueToByteString(codec));
     }
@@ -154,7 +147,7 @@
     ByteString value = txn.read(getName(), key);
     if (value != null)
     {
-      final ImportIDSet importIDSet = new ImportIDSet(key, codec.decode(key, value), indexEntryLimit, maintainCount);
+      final ImportIDSet importIDSet = new ImportIDSet(key, codec.decode(key, value), indexEntryLimit);
       importIDSet.remove(idsToBeRemoved);
       if (importIDSet.isDefined() && importIDSet.size() == 0)
       {
@@ -178,8 +171,7 @@
   {
     /*
      * Check the special condition where both deletedIDs and addedIDs are null. This is used when
-     * deleting entries and corresponding id2children and id2subtree records must be completely
-     * removed.
+     * deleting entries must be completely removed.
      */
     if (deletedIDs == null && addedIDs == null)
     {
@@ -203,7 +195,7 @@
      * Avoid taking a write lock on a record which has hit all IDs because it is likely to be a
      * point of contention.
      */
-    if (!maintainCount && !get(txn, key).isDefined())
+    if (!get(txn, key).isDefined())
     {
       return;
     }
@@ -241,12 +233,12 @@
     });
   }
 
-  private boolean isNullOrEmpty(EntryIDSet entryIDSet)
+  private static boolean isNullOrEmpty(EntryIDSet entryIDSet)
   {
     return entryIDSet == null || entryIDSet.size() == 0;
   }
 
-  private boolean isNotEmpty(EntryIDSet entryIDSet)
+  private static boolean isNotEmpty(EntryIDSet entryIDSet)
   {
     return entryIDSet != null && entryIDSet.size() > 0;
   }
@@ -265,22 +257,13 @@
         }
         if (idCountDelta + entryIDSet.size() >= indexEntryLimit)
         {
-          if (maintainCount)
-          {
-            entryIDSet = newUndefinedSetWithSize(key, entryIDSet.size() + idCountDelta);
-          }
-          else
-          {
-            entryIDSet = newUndefinedSet();
-          }
-
+          entryIDSet = newUndefinedSetWithKey(key);
           if (logger.isTraceEnabled())
           {
             StringBuilder builder = new StringBuilder();
             StaticUtils.byteArrayToHexPlusAscii(builder, key.toByteArray(), 4);
             logger.trace("Index entry exceeded in index %s. " + "Limit: %d. ID list size: %d.\nKey:%s", getName(),
                 indexEntryLimit, idCountDelta + addedIDs.size(), builder);
-
           }
         }
         else
@@ -373,10 +356,4 @@
   {
     return trusted;
   }
-
-  @Override
-  public final boolean getMaintainCount()
-  {
-    return maintainCount;
-  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
index 786251f..bba447a 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
@@ -27,13 +27,12 @@
  */
 package org.opends.server.backends.pluggable;
 
-import static org.forgerock.util.Utils.closeSilently;
+import static org.forgerock.util.Utils.*;
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.backends.pluggable.EntryIDSet.*;
 import static org.opends.server.backends.pluggable.IndexFilter.*;
 import static org.opends.server.backends.pluggable.JebFormat.*;
-import static org.opends.server.backends.pluggable.VLVIndex.encodeTargetAssertion;
-import static org.opends.server.backends.pluggable.VLVIndex.encodeVLVKey;
+import static org.opends.server.backends.pluggable.VLVIndex.*;
 import static org.opends.server.core.DirectoryServer.*;
 import static org.opends.server.protocols.ldap.LDAPResultCode.*;
 import static org.opends.server.types.AdditionalLogItem.*;
@@ -47,6 +46,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.TreeMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -61,6 +61,7 @@
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
+import org.opends.messages.CoreMessages;
 import org.opends.server.admin.server.ConfigurationAddListener;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.server.ConfigurationDeleteListener;
@@ -73,10 +74,10 @@
 import org.opends.server.api.VirtualAttributeProvider;
 import org.opends.server.api.plugin.PluginResult.SubordinateDelete;
 import org.opends.server.api.plugin.PluginResult.SubordinateModifyDN;
-import org.opends.server.backends.pluggable.State.IndexFlag;
 import org.opends.server.backends.pluggable.spi.Cursor;
 import org.opends.server.backends.pluggable.spi.ReadOperation;
 import org.opends.server.backends.pluggable.spi.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.SequentialCursor;
 import org.opends.server.backends.pluggable.spi.Storage;
 import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
 import org.opends.server.backends.pluggable.spi.TreeName;
@@ -124,14 +125,14 @@
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
+  /** Number of EntryID to considers when building EntryIDSet from DN2ID. */
+  private static final int SCOPE_IDSET_LIMIT = 4096;
   /** The name of the entry database. */
   private static final String ID2ENTRY_DATABASE_NAME = ID2ENTRY_INDEX_NAME;
   /** The name of the DN database. */
   private static final String DN2ID_DATABASE_NAME = DN2ID_INDEX_NAME;
   /** The name of the children index database. */
-  private static final String ID2CHILDREN_DATABASE_NAME = ID2CHILDREN_INDEX_NAME;
-  /** The name of the subtree index database. */
-  private static final String ID2SUBTREE_DATABASE_NAME = ID2SUBTREE_INDEX_NAME;
+  private static final String ID2CHILDREN_COUNT_DATABASE_NAME = ID2CHILDREN_COUNT_NAME;
   /** The name of the referral database. */
   private static final String REFERRAL_DATABASE_NAME = REFERRAL_INDEX_NAME;
   /** The name of the state database. */
@@ -158,17 +159,15 @@
   private final Storage storage;
 
   /** The DN database maps a normalized DN string to an entry ID (8 bytes). */
-  private DN2ID dn2id;
+  private final DN2ID dn2id;
   /** The entry database maps an entry ID (8 bytes) to a complete encoded entry. */
   private ID2Entry id2entry;
-  /** Index maps entry ID to an entry ID list containing its children. */
-  private Index id2children;
-  /** Index maps entry ID to an entry ID list containing its subordinates. */
-  private Index id2subtree;
+  /** Store the number of children for each entry. */
+  private final ID2Count id2childrenCount;
   /** The referral database maps a normalized DN string to labeled URIs. */
-  private DN2URI dn2uri;
+  private final DN2URI dn2uri;
   /** The state database maps a config DN to config entries. */
-  private State state;
+  private final State state;
 
   /** The set of attribute indexes. */
   private final HashMap<AttributeType, AttributeIndex> attrIndexMap = new HashMap<AttributeType, AttributeIndex>();
@@ -455,6 +454,10 @@
     this.storage = env;
     this.rootContainer = rootContainer;
     this.databasePrefix = baseDN.toNormalizedUrlSafeString();
+    this.id2childrenCount = new ID2Count(getIndexName(ID2CHILDREN_COUNT_DATABASE_NAME));
+    this.dn2id = new DN2ID(getIndexName(DN2ID_DATABASE_NAME), baseDN);
+    this.dn2uri = new DN2URI(getIndexName(REFERRAL_DATABASE_NAME), this);
+    this.state = new State(getIndexName(STATE_DATABASE_NAME));
 
     config.addPluggableChangeListener(this);
 
@@ -490,16 +493,9 @@
 
       id2entry = new ID2Entry(getIndexName(ID2ENTRY_DATABASE_NAME), entryDataConfig);
       id2entry.open(txn);
-
-      dn2id = new DN2ID(getIndexName(DN2ID_DATABASE_NAME), this);
+      id2childrenCount.open(txn);
       dn2id.open(txn);
-
-      state = new State(getIndexName(STATE_DATABASE_NAME));
       state.open(txn);
-
-      openSubordinateIndexes(txn, config);
-
-      dn2uri = new DN2URI(getIndexName(REFERRAL_DATABASE_NAME), this);
       dn2uri.open(txn);
 
       for (String idx : config.listBackendIndexes())
@@ -538,15 +534,6 @@
     }
   }
 
-  private NullIndex openNewNullIndex(WriteableTransaction txn, String name)
-  {
-    final TreeName treeName = getIndexName(name);
-    final NullIndex index = new NullIndex(treeName);
-    state.removeFlagsFromIndex(txn, treeName, IndexFlag.TRUSTED);
-    txn.deleteTree(treeName);
-    return index;
-  }
-
   /**
    * Closes the entry container.
    *
@@ -617,20 +604,9 @@
    *
    * @return The children database.
    */
-  Index getID2Children()
+  ID2Count getID2ChildrenCount()
   {
-    return id2children;
-  }
-
-  /**
-   * Get the subtree database used by this entry container.
-   * The entryContainer must have been opened.
-   *
-   * @return The subtree database.
-   */
-  Index getID2Subtree()
-  {
-    return id2subtree;
+    return id2childrenCount;
   }
 
   /**
@@ -711,19 +687,37 @@
     }
   }
 
+  boolean hasSubordinates(final DN dn)
+  {
+    try
+    {
+      return storage.read(new ReadOperation<Boolean>()
+      {
+        @Override
+        public Boolean run(final ReadableTransaction txn) throws Exception
+        {
+          try (final SequentialCursor<?, ?> cursor = dn2id.openChildrenCursor(txn, dn))
+          {
+            return cursor.next();
+          }
+        }
+      });
+    }
+    catch (Exception e)
+    {
+      throw new StorageRuntimeException(e);
+    }
+  }
+
   /**
-   * Determine the number of subordinate entries for a given entry.
+   * Determine the number of children entries for a given entry.
    *
    * @param entryDN The distinguished name of the entry.
-   * @param subtree <code>true</code> will include all the entries under the
-   *                given entries. <code>false</code> will only return the
-   *                number of entries immediately under the given entry.
-   * @return The number of subordinate entries for the given entry or -1 if
+   * @return The number of children entries for the given entry or -1 if
    *         the entry does not exist.
    * @throws StorageRuntimeException If an error occurs in the database.
    */
-  long getNumSubordinates(final DN entryDN, final boolean subtree)
-  throws StorageRuntimeException
+  long getNumberOfChildren(final DN entryDN) throws StorageRuntimeException
   {
     try
     {
@@ -732,18 +726,8 @@
         @Override
         public Long run(ReadableTransaction txn) throws Exception
         {
-          EntryID entryID = dn2id.get(txn, entryDN);
-          if (entryID != null)
-          {
-            final Index index = subtree ? id2subtree : id2children;
-            final EntryIDSet entryIDSet = index.get(txn, entryID.toByteString());
-            long count = entryIDSet.size();
-            if (count != Long.MAX_VALUE)
-            {
-              return count;
-            }
-          }
-          return -1L;
+          final EntryID entryID = dn2id.get(txn, entryDN);
+          return entryID != null ? id2childrenCount.getCount(txn, entryID) : -1;
         }
       });
     }
@@ -897,30 +881,7 @@
 
             if (!isBelowFilterThreshold(entryIDSet))
             {
-              // Evaluate the search scope against the id2children and id2subtree indexes
-              EntryID baseID = dn2id.get(txn, aBaseDN);
-              if (baseID == null)
-              {
-                LocalizableMessage message = ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(aBaseDN);
-                DN matchedDN = getMatchedDN(txn, aBaseDN);
-                throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null);
-              }
-              ByteString baseIDData = baseID.toByteString();
-
-              EntryIDSet scopeSet;
-              if (searchScope == SearchScope.SINGLE_LEVEL)
-              {
-                scopeSet = id2children.get(txn, baseIDData);
-              }
-              else
-              {
-                scopeSet = id2subtree.get(txn, baseIDData);
-                if (searchScope == SearchScope.WHOLE_SUBTREE)
-                {
-                  // The id2subtree list does not include the base entry ID.
-                  scopeSet.add(baseID);
-                }
-              }
+              final EntryIDSet scopeSet = getIDSetFromScope(txn, aBaseDN, searchScope);
               entryIDSet.retainAll(scopeSet);
               if (debugBuffer != null)
               {
@@ -1023,6 +984,46 @@
           }
           return null;
         }
+
+        private EntryIDSet getIDSetFromScope(final ReadableTransaction txn, DN aBaseDN, SearchScope searchScope)
+            throws DirectoryException
+        {
+          final EntryIDSet scopeSet;
+          try
+          {
+            switch (searchScope.asEnum())
+            {
+            case BASE_OBJECT:
+              try (final SequentialCursor<?, EntryID> scopeCursor = dn2id.openCursor(txn, aBaseDN))
+              {
+                scopeSet = EntryIDSet.newDefinedSet(scopeCursor.getValue().longValue());
+              }
+              break;
+            case SINGLE_LEVEL:
+              try (final SequentialCursor<?, EntryID> scopeCursor = dn2id.openChildrenCursor(txn, aBaseDN))
+              {
+                scopeSet = newIDSetFromCursor(scopeCursor, false);
+              }
+              break;
+            case SUBORDINATES:
+            case WHOLE_SUBTREE:
+              try (final SequentialCursor<?, EntryID> scopeCursor = dn2id.openSubordinatesCursor(txn, aBaseDN))
+              {
+                scopeSet = newIDSetFromCursor(scopeCursor, searchScope.equals(SearchScope.WHOLE_SUBTREE));
+              }
+              break;
+            default:
+              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                  CoreMessages.INFO_ERROR_SEARCH_SCOPE_NOT_ALLOWED.get());
+            }
+          }
+          catch (NoSuchElementException e)
+          {
+            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(aBaseDN),
+                getMatchedDN(txn, aBaseDN), e);
+          }
+          return scopeSet;
+        }
       });
     }
     catch (Exception e)
@@ -1031,6 +1032,21 @@
     }
   }
 
+  private static EntryIDSet newIDSetFromCursor(SequentialCursor<?, EntryID> cursor, boolean includeCurrent)
+  {
+    final long ids[] = new long[SCOPE_IDSET_LIMIT];
+    int offset = 0;
+    if (includeCurrent) {
+      ids[offset++] = cursor.getValue().longValue();
+    }
+    for(; offset < ids.length && cursor.next() ; offset++) {
+      ids[offset] = cursor.getValue().longValue();
+    }
+    return offset == SCOPE_IDSET_LIMIT
+        ? EntryIDSet.newUndefinedSet()
+        : EntryIDSet.newDefinedSet(Arrays.copyOf(ids, offset));
+  }
+
   private <E1 extends Exception, E2 extends Exception>
       void throwAllowedExceptionTypes(Exception e, Class<E1> clazz1, Class<E2> clazz2)
           throws E1, E2
@@ -1515,6 +1531,7 @@
                 DN matchedDN = getMatchedDN(txn, baseDN);
                 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null);
               }
+              id2childrenCount.addDelta(txn, parentID, 1);
             }
 
             EntryID entryID = rootContainer.getNextEntryID();
@@ -1526,31 +1543,6 @@
             final IndexBuffer indexBuffer = new IndexBuffer(EntryContainer.this);
             indexInsertEntry(indexBuffer, entry, entryID);
 
-            // Insert into id2children and id2subtree.
-            // The database transaction locks on these records will be hotly
-            // contested so we do them last so as to hold the locks for the
-            // shortest duration.
-            if (parentDN != null)
-            {
-              final ByteString parentIDKeyBytes = parentID.toByteString();
-              indexBuffer.put(id2children, parentIDKeyBytes, entryID);
-              indexBuffer.put(id2subtree, parentIDKeyBytes, entryID);
-
-              // Iterate up through the superior entries, starting above the
-              // parent.
-              for (DN dn = getParentWithinBase(parentDN); dn != null; dn = getParentWithinBase(dn))
-              {
-                // Read the ID from dn2id.
-                EntryID nodeID = dn2id.get(txn, dn);
-                if (nodeID == null)
-                {
-                  throw new StorageRuntimeException(ERR_JEB_MISSING_DN2ID_RECORD.get(dn).toString());
-                }
-
-                // Insert into id2subtree for this node.
-                indexBuffer.put(id2subtree, nodeID.toByteString(), entryID);
-              }
-            }
             indexBuffer.flush(txn);
 
             if (addOperation != null)
@@ -1827,31 +1819,19 @@
     // Remove from the indexes, in index config order.
     indexRemoveEntry(indexBuffer, entry, leafID);
 
-    // Remove the id2c and id2s records for this entry.
-    final ByteString leafIDKeyBytes = leafID.toByteString();
-    indexBuffer.remove(id2children, leafIDKeyBytes);
-    indexBuffer.remove(id2subtree, leafIDKeyBytes);
+    // Remove the children counter for this entry.
+    id2childrenCount.deleteCount(txn, leafID);
 
     // Iterate up through the superior entries from the target entry.
-    boolean isParent = true;
-    for (DN parentDN = getParentWithinBase(targetDN); parentDN != null;
-    parentDN = getParentWithinBase(parentDN))
+    final DN parentDN = getParentWithinBase(targetDN);
+    if (parentDN != null)
     {
-      // Read the ID from dn2id.
-      EntryID parentID = dn2id.get(txn, parentDN);
+      final EntryID parentID = dn2id.get(txn, parentDN);
       if (parentID == null)
       {
         throw new StorageRuntimeException(ERR_JEB_MISSING_DN2ID_RECORD.get(parentDN).toString());
       }
-
-      ByteString parentIDBytes = parentID.toByteString();
-      // Remove from id2children.
-      if (isParent)
-      {
-        indexBuffer.remove(id2children, parentIDBytes, leafID);
-        isParent = false;
-      }
-      indexBuffer.remove(id2subtree, parentIDBytes, leafID);
+      id2childrenCount.addDelta(txn, parentID, -1);
     }
 
     // Remove the entry from the entry cache.
@@ -1884,6 +1864,32 @@
     return dn2id.get(txn, entryDN) != null;
   }
 
+
+  boolean entryExists(final DN entryDN) throws StorageRuntimeException
+  {
+    final EntryCache<?> entryCache = DirectoryServer.getEntryCache();
+    if (entryCache != null && entryCache.containsEntry(entryDN))
+    {
+      return true;
+    }
+
+    try
+    {
+      return storage.read(new ReadOperation<Boolean>()
+      {
+        @Override
+        public Boolean run(ReadableTransaction txn) throws Exception
+        {
+          return dn2id.get(txn, entryDN) != null;
+        }
+      });
+    }
+    catch (Exception e)
+    {
+      throw new StorageRuntimeException(e);
+    }
+  }
+
   /**
    * Fetch an entry by DN, trying the entry cache first, then the database.
    * Retrieves the requested entry, trying the entry cache first,
@@ -2346,21 +2352,12 @@
       indexInsertEntry(buffer, newEntry, newID);
     }
 
-    // Add the new ID to id2children and id2subtree of new apex parent entry.
     if(isApexEntryMoved)
     {
-      boolean isParent = true;
-      for (DN dn = getParentWithinBase(newEntry.getName()); dn != null;
-           dn = getParentWithinBase(dn))
+      final DN parentDN = getParentWithinBase(newEntry.getName());
+      if (parentDN != null)
       {
-        EntryID parentID = dn2id.get(txn, dn);
-        ByteString parentIDKeyBytes = parentID.toByteString();
-        if(isParent)
-        {
-          buffer.put(id2children, parentIDKeyBytes, newID);
-          isParent = false;
-        }
-        buffer.put(id2subtree, parentIDKeyBytes, newID);
+        id2childrenCount.addDelta(txn, dn2id.get(txn, parentDN), 1);
       }
     }
   }
@@ -2391,32 +2388,19 @@
 
     tail.next = new MovedEntry(newID, newEntry, !newID.equals(oldID));
 
-    // Remove the old ID from id2children and id2subtree of
-    // the old apex parent entry.
     if(oldSuperiorDN != null && isApexEntryMoved)
     {
-      boolean isParent = true;
-      for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn))
-      {
-        EntryID parentID = dn2id.get(txn, dn);
-        ByteString parentIDKeyBytes = parentID.toByteString();
-        if(isParent)
-        {
-          buffer.remove(id2children, parentIDKeyBytes, oldID);
-          isParent = false;
-        }
-        buffer.remove(id2subtree, parentIDKeyBytes, oldID);
-      }
+      // Since entry has moved, oldSuperiorDN has lost a child
+      id2childrenCount.addDelta(txn, dn2id.get(txn, oldSuperiorDN), -1);
+    }
+
+    if (!newID.equals(oldID))
+    {
+      id2childrenCount.addDelta(txn, newID, id2childrenCount.deleteCount(txn, oldID));
     }
 
     if (!newID.equals(oldID) || modifyDNOperation == null)
     {
-      // All the subordinates will be renumbered so we have to rebuild
-      // id2c and id2s with the new ID.
-      ByteString oldIDKeyBytes = oldID.toByteString();
-      buffer.remove(id2children, oldIDKeyBytes);
-      buffer.remove(id2subtree, oldIDKeyBytes);
-
       // Reindex the entry with the new ID.
       indexRemoveEntry(buffer, oldEntry, oldID);
     }
@@ -2499,24 +2483,9 @@
 
     tail.next = new MovedEntry(newID, newEntry, !newID.equals(oldID));
 
-    if(isApexEntryMoved)
-    {
-      // Remove the old ID from id2subtree of old apex superior entries.
-      for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn))
-      {
-        EntryID parentID = dn2id.get(txn, dn);
-        ByteString parentIDKeyBytes = parentID.toByteString();
-        buffer.remove(id2subtree, parentIDKeyBytes, oldID);
-      }
-    }
-
     if (!newID.equals(oldID))
     {
-      // All the subordinates will be renumbered so we have to rebuild
-      // id2c and id2s with the new ID.
-      ByteString oldIDKeyBytes = oldID.toByteString();
-      buffer.remove(id2children, oldIDKeyBytes);
-      buffer.remove(id2subtree, oldIDKeyBytes);
+      id2childrenCount.deleteCount(txn, oldID);
 
       // Reindex the entry with the new ID.
       indexRemoveEntry(buffer, oldEntry, oldID);
@@ -2642,35 +2611,31 @@
   }
 
   /**
-   * Get a count of the number of entries stored in this entry container.
+   * Get a count of the number of entries stored in this entry container including the baseDN
    *
-   * @param txn a non null database transaction
-   * @return The number of entries stored in this entry container.
-   * @throws StorageRuntimeException If an error occurs in the database.
+   * @param txn
+   *          a non null database transaction
+   * @return The number of entries stored in this entry container including the baseDN.
+   * @throws StorageRuntimeException
+   *           If an error occurs in the database.
    */
-  long getEntryCount(ReadableTransaction txn) throws StorageRuntimeException
+  long getNumberOfEntriesInBaseDN() throws StorageRuntimeException
   {
-    final EntryID entryID = dn2id.get(txn, baseDN);
-    if (entryID != null)
+    try
     {
-      final EntryIDSet entryIDSet = id2subtree.get(txn, entryID.toByteString());
-      long count = entryIDSet.size();
-      if(count != Long.MAX_VALUE)
+      return storage.read(new ReadOperation<Long>()
       {
-        // Add the base entry itself
-        return ++count;
-      }
-      else
-      {
-        // The count is not maintained. Fall back to the slow method
-        return id2entry.getRecordCount(txn);
-      }
+        @Override
+        public Long run(ReadableTransaction txn) throws Exception
+        {
+          final int baseDnIfExists = dn2id.get(txn, baseDN) != null ? 1 : 0;
+          return id2childrenCount.getTotalCount(txn) + baseDnIfExists;
+        }
+      });
     }
-    else
+    catch (Exception e)
     {
-      // Base entry doesn't not exist so this entry container
-      // must not have any entries
-      return 0;
+      throw new StorageRuntimeException(e);
     }
   }
 
@@ -2870,26 +2835,6 @@
         @Override
         public void run(WriteableTransaction txn) throws Exception
         {
-          if (config.isSubordinateIndexesEnabled() != cfg.isSubordinateIndexesEnabled())
-          {
-            openSubordinateIndexes(txn, cfg);
-          }
-
-          if (config.getIndexEntryLimit() != cfg.getIndexEntryLimit())
-          {
-            if (id2children.setIndexEntryLimit(cfg.getIndexEntryLimit()))
-            {
-              ccr.setAdminActionRequired(true);
-              ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(id2children.getName()));
-            }
-
-            if (id2subtree.setIndexEntryLimit(cfg.getIndexEntryLimit()))
-            {
-              ccr.setAdminActionRequired(true);
-              ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(id2subtree.getName()));
-            }
-          }
-
           DataConfig entryDataConfig = new DataConfig(cfg.isEntriesCompressed(),
               cfg.isCompactEncoding(), rootContainer.getCompressedSchema());
           id2entry.setDataConfig(entryDataConfig);
@@ -2969,11 +2914,7 @@
     databases.add(dn2id);
     databases.add(id2entry);
     databases.add(dn2uri);
-    if (config.isSubordinateIndexesEnabled())
-    {
-      databases.add(id2children);
-      databases.add(id2subtree);
-    }
+    databases.add(id2childrenCount);
     databases.add(state);
 
     for (AttributeIndex index : attrIndexMap.values())
@@ -3032,38 +2973,6 @@
     return null;
   }
 
-  /** Opens the id2children and id2subtree indexes. */
-  private void openSubordinateIndexes(WriteableTransaction txn, PluggableBackendCfg cfg)
-  {
-    if (cfg.isSubordinateIndexesEnabled())
-    {
-      TreeName name = getIndexName(ID2CHILDREN_DATABASE_NAME);
-      id2children = new DefaultIndex(name, state, config.getIndexEntryLimit(), true, txn, this);
-      id2children.open(txn);
-      if (!id2children.isTrusted())
-      {
-        logger.info(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD, name);
-      }
-
-      name = getIndexName(ID2SUBTREE_DATABASE_NAME);
-      id2subtree = new DefaultIndex(name, state, config.getIndexEntryLimit(), true, txn, this);
-      id2subtree.open(txn);
-      if (!id2subtree.isTrusted())
-      {
-        logger.info(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD, name);
-      }
-    }
-    else
-    {
-      // Disabling subordinate indexes. Use a null index and ensure that
-      // future attempts to use the real indexes will fail.
-      id2children = openNewNullIndex(txn, ID2CHILDREN_DATABASE_NAME);
-      id2subtree = openNewNullIndex(txn, ID2SUBTREE_DATABASE_NAME);
-      logger.info(NOTE_JEB_SUBORDINATE_INDEXES_DISABLED, cfg.getBackendId());
-    }
-  }
-
-
   /**
    * Checks if any modifications apply to this indexed attribute.
    * @param index the indexed attributes.
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java
index 5b96e85..f21f709 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java
@@ -276,10 +276,7 @@
       {
         return new long[] { entryIDs[0], entryIDs[entryIDs.length - 1] };
       }
-      else
-      {
-        return NO_ENTRY_IDS_RANGE;
-      }
+      return NO_ENTRY_IDS_RANGE;
     }
 
     @Override
@@ -295,25 +292,19 @@
   private static final class UndefinedImpl implements EntryIDSetImplementor
   {
     /**
-     * The number of entry IDs in the set if the size is being maintained, otherwise Long.MAX_VALUE
-     */
-    private long undefinedSize;
-
-    /**
      * The database key containing this set, if the set was constructed directly from the database.
      */
     private final ByteSequence databaseKey;
 
-    UndefinedImpl(ByteSequence key, long size)
+    UndefinedImpl(ByteSequence key)
     {
       databaseKey = checkNotNull(key, "key must not be null");
-      undefinedSize = size;
     }
 
     @Override
     public long size()
     {
-      return undefinedSize;
+      return Long.MAX_VALUE;
     }
 
     @Override
@@ -323,21 +314,12 @@
       {
         buffer.append("[NOT-INDEXED]");
       }
-      else if (maintainUndefinedSize())
-      {
-        buffer.append("[LIMIT-EXCEEDED:").append(undefinedSize).append("]");
-      }
       else
       {
         buffer.append("[LIMIT-EXCEEDED]");
       }
     }
 
-    private boolean maintainUndefinedSize()
-    {
-      return undefinedSize != Long.MAX_VALUE;
-    }
-
     @Override
     public boolean isDefined()
     {
@@ -347,20 +329,12 @@
     @Override
     public boolean add(EntryID entryID)
     {
-      if (maintainUndefinedSize())
-      {
-        undefinedSize++;
-      }
       return true;
     }
 
     @Override
     public boolean remove(EntryID entryID)
     {
-      if (maintainUndefinedSize() && undefinedSize > 0)
-      {
-        undefinedSize--;
-      }
       return true;
     }
 
@@ -373,21 +347,11 @@
     @Override
     public void addAll(EntryIDSet that)
     {
-      // Assume there are no overlap between IDs in that set with this set
-      if (maintainUndefinedSize())
-      {
-        undefinedSize += that.size();
-      }
     }
 
     @Override
     public void removeAll(EntryIDSet that)
     {
-      // Assume all IDs in the given set exists in this set.
-      if (maintainUndefinedSize())
-      {
-        undefinedSize = Math.max(0, undefinedSize - that.size());
-      }
     }
 
     @Override
@@ -481,7 +445,7 @@
       else if ((value.byteAt(0) & 0x80) == 0x80)
       {
         // Entry limit has exceeded and there is an encoded undefined set size.
-        return newUndefinedSetWithSize(key, decodeUndefinedSize(value));
+        return newUndefinedSetWithKey(key);
       }
       else
       {
@@ -490,19 +454,12 @@
       }
     }
 
-    private int getEstimatedSize(EntryIDSet idSet)
+    private static int getEstimatedSize(EntryIDSet idSet)
     {
-      if (idSet.isDefined())
-      {
-        return idSet.getIDs().length * LONG_SIZE;
-      }
-      else
-      {
-        return LONG_SIZE;
-      }
+      return idSet.isDefined() ? idSet.getIDs().length * LONG_SIZE : LONG_SIZE;
     }
 
-    private long[] decodeRaw(ByteSequenceReader reader, int nbEntriesToDecode)
+    private static long[] decodeRaw(ByteSequenceReader reader, int nbEntriesToDecode)
     {
       checkNotNull(reader, "builder must not be null");
       Reject.ifFalse(nbEntriesToDecode >= 0, "nbEntriesToDecode must be >= 0");
@@ -514,7 +471,7 @@
       return ids;
     }
 
-    private ByteStringBuilder append(ByteStringBuilder builder, EntryIDSet idSet)
+    private static ByteStringBuilder append(ByteStringBuilder builder, EntryIDSet idSet)
     {
       checkNotNull(idSet, "idSet must not be null");
       checkNotNull(builder, "builder must not be null");
@@ -527,17 +484,8 @@
         }
         return builder;
       }
-      else
-      {
-        // Set top bit.
-        return builder.append(idSet.size() | Long.MIN_VALUE);
-      }
-    }
-
-    private static long decodeUndefinedSize(ByteSequence bytes)
-    {
-      // remove top bit
-      return bytes.length() == LONG_SIZE ? bytes.asReader().getLong() & Long.MAX_VALUE : Long.MAX_VALUE;
+      // Set top bit.
+      return builder.append((byte) 0x80);
     }
   }
 
@@ -564,17 +512,15 @@
     {
       checkNotNull(key, "key must not be null");
       checkNotNull(value, "value must not be null");
-
-      final ByteSequenceReader reader = value.asReader();
-      if ( reader.get() == UNDEFINED_SET) {
-          return newUndefinedSetWithSize(key, reader.getLong());
-      } else {
-          reader.rewind();
-          return newDefinedSet(decodeRaw(reader, (int) reader.getCompactUnsigned()));
+      if (value.byteAt(0) == UNDEFINED_SET)
+      {
+        return newUndefinedSetWithKey(key);
       }
+      final ByteSequenceReader reader = value.asReader();
+      return newDefinedSet(decodeRaw(reader, (int) reader.getCompactUnsigned()));
     }
 
-    private ByteStringBuilder append(ByteStringBuilder builder, EntryIDSet idSet)
+    private static ByteStringBuilder append(ByteStringBuilder builder, EntryIDSet idSet)
     {
       checkNotNull(idSet, "idSet must not be null");
       checkNotNull(builder, "builder must not be null");
@@ -592,48 +538,41 @@
       else
       {
         builder.append(UNDEFINED_SET);
-        builder.append(idSet.size());
       }
       return builder;
     }
 
-    private int getEstimatedSize(EntryIDSet idSet)
+    private static int getEstimatedSize(EntryIDSet idSet)
     {
       checkNotNull(idSet, "idSet must not be null");
       return idSet.getIDs().length * LONG_SIZE + INT_SIZE;
     }
 
-    private long[] decodeRaw(ByteSequenceReader reader, int nbEntriesToDecode)
+    private static long[] decodeRaw(ByteSequenceReader reader, int nbEntriesToDecode)
     {
       checkNotNull(reader, "reader must not be null");
       Reject.ifFalse(nbEntriesToDecode >= 0, "nbEntriesToDecode must be >= 0");
 
       if ( nbEntriesToDecode == 0 ) {
         return EMPTY_LONG_ARRAY;
-      } else {
-        final long ids[] = new long[nbEntriesToDecode];
-        ids[0] = reader.getCompactUnsigned();
-        for(int i = 1 ; i < nbEntriesToDecode ; i++) {
-          ids[i] = ids[i-1] + reader.getCompactUnsigned();
-        }
-        return ids;
       }
+      final long ids[] = new long[nbEntriesToDecode];
+      ids[0] = reader.getCompactUnsigned();
+      for(int i = 1 ; i < nbEntriesToDecode ; i++) {
+        ids[i] = ids[i-1] + reader.getCompactUnsigned();
+      }
+      return ids;
     }
   }
 
   static EntryIDSet newUndefinedSet()
   {
-    return new EntryIDSet(new UndefinedImpl(NO_KEY, Long.MAX_VALUE));
+    return newUndefinedSetWithKey(NO_KEY);
   }
 
   static EntryIDSet newUndefinedSetWithKey(ByteSequence key)
   {
-    return newUndefinedSetWithSize(key, Long.MAX_VALUE);
-  }
-
-  static EntryIDSet newUndefinedSetWithSize(ByteSequence key, long undefinedSize)
-  {
-    return new EntryIDSet(new UndefinedImpl(key, undefinedSize));
+    return new EntryIDSet(new UndefinedImpl(key));
   }
 
   /**
@@ -708,7 +647,7 @@
 
     if (containsUndefinedSet)
     {
-      return newUndefinedSetWithSize(null, count);
+      return newUndefinedSet();
     }
 
     boolean needSort = false;
@@ -742,10 +681,7 @@
     {
       return newDefinedSet(n1);
     }
-    else
-    {
-      return newDefinedSet(Arrays.copyOf(n1, j));
-    }
+    return newDefinedSet(Arrays.copyOf(n1, j));
   }
 
   private EntryIDSetImplementor concreteImpl;
@@ -878,7 +814,7 @@
         // performed by the implementation.
         concreteImpl = new DefinedImpl(that.getIDs());
       } else {
-        concreteImpl = new UndefinedImpl(NO_KEY, that.size());
+        concreteImpl = new UndefinedImpl(NO_KEY);
       }
       return;
     }
@@ -948,6 +884,12 @@
     return concreteImpl.getRange();
   }
 
+  static long addWithoutOverflow(long a, long b) {
+    /** a and b must be > 0 */
+    final long result = a + b;
+    return result >= 0 ? result : Long.MAX_VALUE;
+  }
+
   private static long[] mergeOverlappingEntryIDSet(long set1[], long set2[])
   {
     final long[] a, b;
@@ -989,10 +931,7 @@
     {
       return Arrays.copyOf(newEntryIDs, targetIndex);
     }
-    else
-    {
-      return newEntryIDs;
-    }
+    return newEntryIDs;
   }
 
   private static int copyRemainder(long[] sourceIDSet, final long[] newEntryIDs, int offset, int remainerIndex)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Count.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Count.java
new file mode 100644
index 0000000..5259156
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Count.java
@@ -0,0 +1,172 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2015 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable;
+
+import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.util.Reject;
+import org.forgerock.util.promise.Function;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.opends.server.backends.pluggable.spi.Cursor;
+import org.opends.server.backends.pluggable.spi.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.TreeName;
+import org.opends.server.backends.pluggable.spi.UpdateFunction;
+import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+
+/**
+ * Store a counter associated to a key. Counter value is sharded amongst multiple database key to allow concurrent
+ * update without contention (at the price of a slower read).
+ */
+final class ID2Count extends AbstractDatabaseContainer
+{
+  /**
+   * Must be a power of 2 @see <a href="http://en.wikipedia.org/wiki/Modulo_operation#Performance_issues">Performance
+   * issues</a>
+   */
+  private static final long SHARD_COUNT = 4096;
+  private static final int LONG_SIZE = Long.SIZE / Byte.SIZE;
+  private static final EntryID TOTAL_COUNT_ENTRY_ID = new EntryID(ByteStringBuilder.COMPACTED_MAX_VALUE);
+
+  ID2Count(TreeName name)
+  {
+    super(name);
+  }
+
+  Cursor<EntryID, Long> openCursor(ReadableTransaction txn) {
+    return CursorTransformer.transformKeysAndValues(txn.openCursor(getName()),
+        new Function<ByteString, EntryID, Exception>()
+        {
+          @Override
+          public EntryID apply(ByteString value) throws Exception
+          {
+            return new EntryID(value.asReader().getCompactUnsigned());
+          }
+        }, new CursorTransformer.ValueTransformer<ByteString, ByteString, Long, NeverThrowsException>()
+        {
+          @Override
+          public Long transform(ByteString key, ByteString value) throws NeverThrowsException
+          {
+            return value.toLong();
+          }
+        });
+  }
+
+  /**
+   * Add a value to the counter associated to the given key
+   * @param txn Database transaction
+   * @param entryID The entryID identifying to the counter
+   * @param delta The value to add. Can be negative to decrease counter value.
+   */
+  void addDelta(WriteableTransaction txn, EntryID entryID, final long delta)
+  {
+    Reject.ifTrue(entryID.longValue() >= TOTAL_COUNT_ENTRY_ID.longValue(), "EntryID overflow.");
+
+    addToCounter(txn, entryID, delta);
+    addToCounter(txn, TOTAL_COUNT_ENTRY_ID, delta);
+  }
+
+  private void addToCounter(WriteableTransaction txn, EntryID entryID, final long delta)
+  {
+    final long bucket = (Thread.currentThread().getId() & (SHARD_COUNT - 1));
+    final ByteSequence shardedKey = getKeyFromEntryIDAndBucket(entryID, bucket);
+    txn.update(getName(), shardedKey, new UpdateFunction()
+    {
+      @Override
+      public ByteSequence computeNewValue(ByteSequence oldValue)
+      {
+        final long currentValue = oldValue != null ? oldValue.asReader().getLong() : 0;
+        return ByteString.valueOf(currentValue + delta);
+      }
+    });
+  }
+
+  /**
+   * Get the counter value for the specified key
+   * @param txn The database transaction
+   * @param entryID The entryID identifying to the counter
+   * @return Value of the counter. 0 if no counter is associated yet.
+   */
+  long getCount(ReadableTransaction txn, EntryID entryID)
+  {
+    long counterValue = 0;
+    try(final Cursor<EntryID, Long> cursor = openCursor(txn)) {
+      cursor.positionToKeyOrNext(getKeyFromEntryID(entryID));
+      while (cursor.isDefined() && cursor.getKey().equals(entryID))
+      {
+        counterValue += cursor.getValue().longValue();
+        cursor.next();
+      }
+    }
+
+    return counterValue;
+  }
+
+  private static final ByteSequence getKeyFromEntryID(EntryID entryID) {
+    return new ByteStringBuilder(LONG_SIZE).appendCompactUnsigned(entryID.longValue());
+  }
+
+  private static final ByteSequence getKeyFromEntryIDAndBucket(EntryID entryID, long bucket) {
+    return new ByteStringBuilder(LONG_SIZE + LONG_SIZE).appendCompactUnsigned(entryID.longValue())
+        .appendCompactUnsigned(bucket);
+  }
+
+  /**
+   * Get the total counter value. The total counter maintain the sum of all
+   * the counter contained in this tree.
+   * @param txn The database transaction
+   * @return Sum of all the counter contained in this tree
+   */
+  long getTotalCount(ReadableTransaction txn)
+  {
+    return getCount(txn, TOTAL_COUNT_ENTRY_ID);
+  }
+
+  /**
+   * Delete the counter associated to the given key
+   * @param txn The database transaction
+   * @param entryID The entryID identifying the counter
+   * @return Value of the counter before it's deletion.
+   */
+  long deleteCount(WriteableTransaction txn, EntryID entryID)
+  {
+    long counterValue = 0;
+    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) {
+      final ByteSequence encodedEntryID = getKeyFromEntryID(entryID);
+      if (cursor.positionToKeyOrNext(encodedEntryID)) {
+        while (cursor.getKey().startsWith(encodedEntryID))
+        {
+          counterValue += cursor.getValue().asReader().getLong();
+          txn.delete(getName(), cursor.getKey());
+          cursor.next();
+        }
+      }
+    }
+    addToCounter(txn, TOTAL_COUNT_ENTRY_ID, -counterValue);
+
+    return counterValue;
+  }
+
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
index 912c64e..9a23d94 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends.pluggable;
 
+import static org.forgerock.util.Reject.*;
 import static org.forgerock.util.Utils.*;
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.core.DirectoryServer.*;
@@ -44,6 +45,7 @@
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.forgerock.opendj.ldap.DecodeException;
 import org.opends.server.api.CompressedSchema;
+import org.opends.server.backends.pluggable.spi.Cursor;
 import org.opends.server.backends.pluggable.spi.ReadableTransaction;
 import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
 import org.opends.server.backends.pluggable.spi.TreeName;
@@ -362,6 +364,24 @@
     return get0(id, txn.read(getName(), id.toByteString()));
   }
 
+  /**
+   * Check that a record entry exists in the entry database.
+   *
+   * @param txn a non null database transaction
+   * @param id The entry ID which forms the key.
+   * @return True if an entry with entryID exists
+   * @throws DirectoryException If a problem occurs while getting the entry.
+   * @throws StorageRuntimeException If an error occurs in the database.
+   */
+  public boolean containsEntryID(ReadableTransaction txn, EntryID id)
+ {
+    checkNotNull(txn, "txn must not be null");
+    checkNotNull(id, "id must not be null");
+    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName())) {
+      return cursor.positionToKey(id.toByteString());
+    }
+ }
+
   private Entry get0(EntryID id, ByteString value) throws DirectoryException
   {
     if (value == null)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ImportIDSet.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ImportIDSet.java
index d0bc347..adccc4e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ImportIDSet.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ImportIDSet.java
@@ -50,20 +50,22 @@
   private final ByteSequence key;
   /** The index entry limit size. */
   private final int indexEntryLimitSize;
-  /** Set to true if a count of ids above the index entry limit should be kept. */
-  private final boolean maintainCount;
 
   /**
    * Create an import ID set managing the entry limit of the provided EntryIDSet.
    *
-   * @param key The key associated to this ID set
-   * @param entryIDSet The entryIDSet that will be managed by this object
-   * @param limit The index entry limit or 0 if unlimited.
-   * @param maintainCount whether to maintain the count when size is undefined.
-   * @throws NullPointerException if key or entryIDSet is null
-   * @throws IllegalArgumentException if limit is < 0
+   * @param key
+   *          The key associated to this ID set
+   * @param entryIDSet
+   *          The entryIDSet that will be managed by this object
+   * @param limit
+   *          The index entry limit or 0 if unlimited.
+   * @throws NullPointerException
+   *           if key or entryIDSet is null
+   * @throws IllegalArgumentException
+   *           if limit is < 0
    */
-  public ImportIDSet(ByteSequence key, EntryIDSet entryIDSet, int limit, boolean maintainCount)
+  public ImportIDSet(ByteSequence key, EntryIDSet entryIDSet, int limit)
   {
     checkNotNull(key, "key must not be null");
     checkNotNull(entryIDSet, "entryIDSet must not be null");
@@ -73,7 +75,6 @@
     this.entryIDSet = entryIDSet;
     // FIXME: What to do if entryIDSet.size()> limit yet ?
     this.indexEntryLimitSize = limit == 0 ? Integer.MAX_VALUE : limit;
-    this.maintainCount = maintainCount;
   }
 
   /**
@@ -88,10 +89,6 @@
     entryIDSet = newUndefinedSetWithKey(key);
   }
 
-  private void setUndefinedWithSize(final long newSize) {
-    entryIDSet = maintainCount ? newUndefinedSetWithSize(key, newSize) : newUndefinedSetWithKey(key);
-  }
-
   /**
    * @param entryID The entry ID to add to an import ID set.
    * @throws NullPointerException if entryID is null
@@ -105,10 +102,13 @@
    */
   void addEntryID(long entryID)
   {
-    Reject.ifTrue(entryID < 0, "entryID must always be positive");
-    if (isDefined() && size() + 1 > indexEntryLimitSize) {
-      setUndefinedWithSize(size() + 1);
-    } else if (isDefined() || maintainCount) {
+    Reject.ifTrue(entryID < 0, "entryID must be positive");
+    if (!isDefined()) {
+      return;
+    }
+    if (size() + 1 > indexEntryLimitSize) {
+      entryIDSet = newUndefinedSetWithKey(key);
+    } else {
       entryIDSet.add(new EntryID(entryID));
     }
   }
@@ -120,10 +120,9 @@
   void remove(ImportIDSet importIdSet)
   {
     checkNotNull(importIdSet, "importIdSet must not be null");
-
     if (!importIdSet.isDefined()) {
       setUndefined();
-    } else if (isDefined() || maintainCount) {
+    } else if (isDefined()) {
       entryIDSet.removeAll(importIdSet.entryIDSet);
     }
   }
@@ -142,26 +141,16 @@
 
     if (!definedBeforeMerge || !importIdSet.isDefined() || mergedSize > indexEntryLimitSize)
     {
-      setUndefinedWithSize(mergedSize);
+      entryIDSet = newUndefinedSetWithKey(key);
       return definedBeforeMerge;
     }
-    else if (isDefined() || maintainCount)
+    else if (isDefined())
     {
       entryIDSet.addAll(importIdSet.entryIDSet);
     }
     return false;
   }
 
-  private static long addWithoutOverflow(long a, long b) {
-    /** a and b must be > 0 */
-    final boolean willAdditionOverflow = (~(a ^ b) & (a ^ (a + b))) < 0;
-    if (willAdditionOverflow) {
-      return Long.MAX_VALUE;
-    }
-    return a + b;
-  }
-
-
   /**
    * @return The current size of an import ID set.
    * @throws IllegalStateException if this set is undefined
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Importer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Importer.java
index e3f0e02..ef2f193 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Importer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Importer.java
@@ -90,7 +90,6 @@
 import org.forgerock.opendj.ldap.ByteSequenceReader;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ByteStringBuilder;
-import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.spi.IndexingOptions;
 import org.forgerock.util.Utils;
 import org.opends.server.admin.std.meta.BackendIndexCfgDefn.IndexType;
@@ -250,7 +249,6 @@
 
   /** Used to shutdown import if an error occurs in phase one. */
   private volatile boolean isCanceled;
-  private volatile boolean isPhaseOneDone;
 
   /** Number of phase one buffers. */
   private int phaseOneBufferCount;
@@ -287,7 +285,7 @@
     this.serverContext = serverContext;
     this.tmpEnv = null;
     this.threadCount = 1;
-    this.rebuildManager = new RebuildIndexManager(rebuildConfig, cfg);
+    this.rebuildManager = new RebuildIndexManager(rootContainer.getStorage(), rebuildConfig, cfg);
     this.indexCount = rebuildManager.getIndexCount();
     this.clearedBackend = false;
     this.scratchFileWriterList =
@@ -386,7 +384,7 @@
      */
   }
 
-  private File getTempDir(PluggableBackendCfg backendCfg, String tmpDirectory)
+  private static File getTempDir(PluggableBackendCfg backendCfg, String tmpDirectory)
   {
     File parentDir;
     if (tmpDirectory != null)
@@ -400,8 +398,7 @@
     return new File(parentDir, backendCfg.getBackendId());
   }
 
-  private int getTotalIndexCount(PluggableBackendCfg backendCfg)
-      throws ConfigException
+  private static int getTotalIndexCount(PluggableBackendCfg backendCfg) throws ConfigException
   {
     int indexes = 2; // dn2id, dn2uri
     for (String indexName : backendCfg.listBackendIndexes())
@@ -770,14 +767,14 @@
     }
   }
 
-  private void clearSuffix(EntryContainer entryContainer)
+  private static void clearSuffix(EntryContainer entryContainer)
   {
     entryContainer.lock();
     entryContainer.clear();
     entryContainer.unlock();
   }
 
-  private boolean isAnyNotEqualAndAncestorOf(List<DN> dns, DN childDN)
+  private static boolean isAnyNotEqualAndAncestorOf(List<DN> dns, DN childDN)
   {
     for (DN dn : dns)
     {
@@ -789,7 +786,7 @@
     return true;
   }
 
-  private boolean isAnyAncestorOf(List<DN> dns, DN childDN)
+  private static boolean isAnyAncestorOf(List<DN> dns, DN childDN)
   {
     for (DN dn : dns)
     {
@@ -920,7 +917,6 @@
 
       final long startTime = System.currentTimeMillis();
       importPhaseOne();
-      isPhaseOneDone = true;
       final long phaseOneFinishTime = System.currentTimeMillis();
 
       if (!skipDNValidation)
@@ -1090,12 +1086,12 @@
     indexKeyQueueMap.clear();
   }
 
-  private void scheduleAtFixedRate(ScheduledThreadPoolExecutor timerService, Runnable task)
+  private static void scheduleAtFixedRate(ScheduledThreadPoolExecutor timerService, Runnable task)
   {
     timerService.scheduleAtFixedRate(task, TIMER_INTERVAL, TIMER_INTERVAL, TimeUnit.MILLISECONDS);
   }
 
-  private void shutdownAll(ExecutorService... executorServices) throws InterruptedException
+  private static void shutdownAll(ExecutorService... executorServices) throws InterruptedException
   {
     for (ExecutorService executorService : executorServices)
     {
@@ -1107,7 +1103,7 @@
     }
   }
 
-  private void clearAll(Collection<?>... cols)
+  private static void clearAll(Collection<?>... cols)
   {
     for (Collection<?> col : cols)
     {
@@ -1118,7 +1114,7 @@
   private void importPhaseTwo() throws InterruptedException, ExecutionException
   {
     ScheduledThreadPoolExecutor timerService = new ScheduledThreadPoolExecutor(1);
-    scheduleAtFixedRate(timerService, new SecondPhaseProgressTask(reader.getEntriesRead()));
+    scheduleAtFixedRate(timerService, new SecondPhaseProgressTask());
     try
     {
       processIndexFiles();
@@ -1218,7 +1214,7 @@
     }
   }
 
-  private <T> void getAll(List<Future<T>> futures) throws InterruptedException, ExecutionException
+  private static <T> void getAll(List<Future<T>> futures) throws InterruptedException, ExecutionException
   {
     for (Future<?> result : futures)
     {
@@ -1430,8 +1426,8 @@
       }
     }
 
-    void processEntry(WriteableTransaction txn, Entry entry, Suffix suffix)
-        throws DirectoryException, StorageRuntimeException, InterruptedException
+    void processEntry(WriteableTransaction txn, Entry entry, Suffix suffix) throws DirectoryException,
+        StorageRuntimeException, InterruptedException
     {
       DN entryDN = entry.getName();
       DN2ID dn2id = suffix.getDN2ID();
@@ -2031,11 +2027,11 @@
     {
       if (indexMgr.isDN2ID())
       {
-        return new ImportIDSet(record.getKey(), newDefinedSet(), 1, false);
+        return new ImportIDSet(record.getKey(), newDefinedSet(), 1);
       }
 
       final Index index = indexIDToIndexMap.get(record.getIndexID());
-      return new ImportIDSet(record.getKey(), newDefinedSet(), index.getIndexEntryLimit(), index.getMaintainCount());
+      return new ImportIDSet(record.getKey(), newDefinedSet(), index.getIndexEntryLimit());
     }
 
     private void addToDB(WriteableTransaction txn, int indexID, ImportIDSet insertSet, ImportIDSet deleteSet)
@@ -2071,7 +2067,7 @@
       }
       if (dnState.checkParent(txn, idSet))
       {
-        dnState.writeToDN2ID(txn, idSet);
+        dnState.writeToDN2ID(txn, idSet.getKey());
       }
     }
 
@@ -2091,10 +2087,7 @@
       private final EntryContainer entryContainer;
       private final TreeName dn2id;
       private final TreeMap<ByteString, EntryID> parentIDMap = new TreeMap<ByteString, EntryID>();
-      private final Map<ByteString, ImportIDSet> id2childTree = new TreeMap<ByteString, ImportIDSet>();
-      private final Map<ByteString, ImportIDSet> id2subtreeTree = new TreeMap<ByteString, ImportIDSet>();
-      private final int childLimit, subTreeLimit;
-      private final boolean childDoCount, subTreeDoCount;
+      private final Map<EntryID, AtomicLong> id2childrenCountTree = new TreeMap<EntryID, AtomicLong>();
       private ByteSequence parentDN;
       private final ByteStringBuilder lastDN = new ByteStringBuilder();
       private EntryID parentID, lastID, entryID;
@@ -2103,12 +2096,6 @@
       {
         this.entryContainer = entryContainer;
         dn2id = entryContainer.getDN2ID().getName();
-        final Index id2c = entryContainer.getID2Children();
-        childLimit = id2c.getIndexEntryLimit();
-        childDoCount = id2c.getMaintainCount();
-        final Index id2s = entryContainer.getID2Subtree();
-        subTreeLimit = id2s.getIndexEntryLimit();
-        subTreeDoCount = id2s.getMaintainCount();
       }
 
       private ByteSequence getParent(ByteSequence dn)
@@ -2186,62 +2173,15 @@
         return true;
       }
 
-      private void id2child(WriteableTransaction txn, EntryID childID) throws DirectoryException
+      private AtomicLong getId2childrenCounter()
       {
-        if (parentID == null)
+        AtomicLong counter = id2childrenCountTree.get(parentID);
+        if (counter == null)
         {
-          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_PARENT_ENTRY_IS_MISSING.get());
+          counter = new AtomicLong();
+          id2childrenCountTree.put(parentID, counter);
         }
-
-        getId2childtreeImportIDSet().addEntryID(childID);
-        if (id2childTree.size() > DN_STATE_CACHE_SIZE)
-        {
-          flushToDB(txn, id2childTree.values(), entryContainer.getID2Children(), true);
-        }
-      }
-
-      private ImportIDSet getId2childtreeImportIDSet()
-      {
-        final ByteString parentIDBytes = parentID.toByteString();
-        ImportIDSet idSet = id2childTree.get(parentIDBytes);
-        if (idSet == null)
-        {
-          idSet = new ImportIDSet(parentIDBytes, newDefinedSet(), childLimit, childDoCount);
-          id2childTree.put(parentIDBytes, idSet);
-        }
-        return idSet;
-      }
-
-      private void id2SubTree(WriteableTransaction txn, EntryID childID) throws DirectoryException
-      {
-        if (parentID == null)
-        {
-          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_PARENT_ENTRY_IS_MISSING.get());
-        }
-
-        getId2subtreeImportIDSet(parentID).addEntryID(childID);
-        // TODO:
-        // Instead of doing this,
-        // we can just walk to parent cache if available
-        for (ByteSequence dn = getParent(parentDN); dn != null; dn = getParent(dn))
-        {
-          EntryID nodeID = getParentID(txn, dn);
-          if (nodeID != null)
-          {
-            getId2subtreeImportIDSet(nodeID).addEntryID(childID);
-          }
-          // else we have a missing parent. Maybe parent checking was turned off?
-          // Just ignore.
-        }
-        if (id2subtreeTree.size() > DN_STATE_CACHE_SIZE)
-        {
-          flushToDB(txn, id2subtreeTree.values(), entryContainer.getID2Subtree(), true);
-        }
-      }
-
-      private EntryID getParentID(ReadableTransaction txn, ByteSequence dn) throws StorageRuntimeException
-      {
-        return bypassCacheForAppendMode() ? get(txn, dn2id, dn) : parentIDMap.get(dn);
+        return counter;
       }
 
       /**
@@ -2259,45 +2199,34 @@
         return value != null ? new EntryID(value) : null;
       }
 
-      private ImportIDSet getId2subtreeImportIDSet(EntryID entryID)
+      public void writeToDN2ID(WriteableTransaction txn, ByteSequence key) throws DirectoryException
       {
-        ByteString entryIDBytes = entryID.toByteString();
-        ImportIDSet idSet = id2subtreeTree.get(entryIDBytes);
-        if (idSet == null)
-        {
-          idSet = new ImportIDSet(entryIDBytes, newDefinedSet(), subTreeLimit, subTreeDoCount);
-          id2subtreeTree.put(entryIDBytes, idSet);
-        }
-        return idSet;
-      }
-
-      public void writeToDN2ID(WriteableTransaction txn, ImportIDSet idSet) throws DirectoryException
-      {
-        txn.put(dn2id, idSet.getKey(), entryID.toByteString());
+        txn.put(dn2id, key, entryID.toByteString());
         indexMgr.addTotDNCount(1);
-        if (parentDN != null)
+        if (parentID != null)
         {
-          id2child(txn, entryID);
-          id2SubTree(txn, entryID);
+          incrementChildrenCounter(txn);
         }
       }
 
-      public void flush(WriteableTransaction txn)
+      private void incrementChildrenCounter(WriteableTransaction txn)
       {
-        flushToDB(txn, id2childTree.values(), entryContainer.getID2Children(), false);
-        flushToDB(txn, id2subtreeTree.values(), entryContainer.getID2Subtree(), false);
+        final AtomicLong counter = getId2childrenCounter();
+        counter.incrementAndGet();
+        if (id2childrenCountTree.size() > DN_STATE_CACHE_SIZE)
+        {
+          flush(txn);
+        }
       }
 
-      private void flushToDB(WriteableTransaction txn, Collection<ImportIDSet> idSets, Index index, boolean clearIDSets)
+      private void flush(WriteableTransaction txn)
       {
-        for (ImportIDSet idSet : idSets)
+        for (Map.Entry<EntryID, AtomicLong> childrenCounter : id2childrenCountTree.entrySet())
         {
-          index.importPut(txn, idSet);
+          entryContainer.getID2ChildrenCount()
+              .addDelta(txn, childrenCounter.getKey(), childrenCounter.getValue().get());
         }
-        if (clearIDSets)
-        {
-          idSets.clear();
-        }
+        id2childrenCountTree.clear();
       }
     }
   }
@@ -2800,9 +2729,9 @@
      * @param cfg
      *          The local DB configuration to use.
      */
-    public RebuildIndexManager(RebuildConfig rebuildConfig, PluggableBackendCfg cfg)
+    public RebuildIndexManager(Storage storage, RebuildConfig rebuildConfig, PluggableBackendCfg cfg)
     {
-      super(null);
+      super(storage);
       this.rebuildConfig = rebuildConfig;
       this.cfg = cfg;
     }
@@ -2945,9 +2874,7 @@
         rebuildIndexMap(txn, false);
         // falls through
       case DEGRADED:
-        if (mode == RebuildMode.ALL
-            || !entryContainer.getID2Children().isTrusted()
-            || !entryContainer.getID2Subtree().isTrusted())
+        if (mode == RebuildMode.ALL)
         {
           dn2id = entryContainer.getDN2ID();
         }
@@ -3032,15 +2959,8 @@
       {
         // dn2uri does not have a trusted status.
         entryContainer.clearDatabase(txn, entryContainer.getDN2URI());
-      }
-
-      if (!onlyDegraded
-          || !entryContainer.getID2Children().isTrusted()
-          || !entryContainer.getID2Subtree().isTrusted())
-      {
         entryContainer.clearDatabase(txn, entryContainer.getDN2ID());
-        entryContainer.clearDatabase(txn, entryContainer.getID2Children());
-        entryContainer.clearDatabase(txn, entryContainer.getID2Subtree());
+        entryContainer.clearDatabase(txn, entryContainer.getID2ChildrenCount());
       }
 
       for (Map.Entry<IndexKey, MatchingRuleIndex> mapEntry : indexMap.entrySet())
@@ -3065,12 +2985,6 @@
     {
       try
       {
-        if (dn2id != null)
-        {
-          EntryContainer ec = suffix.getEntryContainer();
-          ec.getID2Children().setTrusted(txn, trusted);
-          ec.getID2Subtree().setTrusted(txn, trusted);
-        }
         setTrusted(txn, indexMap.values(), trusted);
         for (VLVIndex vlvIndex : vlvIndexes)
         {
@@ -3123,7 +3037,7 @@
 
     private void rebuildIndexesPhaseTwo() throws InterruptedException, ExecutionException
     {
-      final Timer timer = scheduleAtFixedRate(new SecondPhaseProgressTask(entriesProcessed.get()));
+      final Timer timer = scheduleAtFixedRate(new SecondPhaseProgressTask());
       try
       {
         processIndexFiles();
@@ -3482,7 +3396,6 @@
   {
     /** The time in milliseconds of the previous progress report. */
     private long previousTime;
-    private long latestCount;
 
     /**
      * Create a new import progress task.
@@ -3490,10 +3403,9 @@
      * @param latestCount
      *          The latest count of entries processed in phase one.
      */
-    public SecondPhaseProgressTask(long latestCount)
+    public SecondPhaseProgressTask()
     {
       previousTime = System.currentTimeMillis();
-      this.latestCount = latestCount;
     }
 
     /** The action to be performed by this timer task. */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java
index b7de9e4..ddccb6e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java
@@ -43,8 +43,6 @@
 
   int getIndexEntryLimit();
 
-  boolean getMaintainCount();
-
   // Ignores trusted state.
   void importPut(WriteableTransaction txn, ImportIDSet idsToBeAdded);
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java
index ab05cb5..38442a4 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java
@@ -63,9 +63,7 @@
 
   /**
    * A simple class representing a pair of added and deleted indexed IDs. Initially both addedIDs
-   * and deletedIDs are {@code null} indicating that that the whole record should be deleted. This
-   * state is only ever used when updating the id2children and id2subtree indexes when deleting an
-   * entry.
+   * and deletedIDs are {@code null} indicating that that the whole record should be deleted.
    */
   private static class BufferedIndexValues
   {
@@ -214,21 +212,6 @@
         vlvIndex.updateIndex(txn, bufferedVLVValues.addedSortKeys, bufferedVLVValues.deletedSortKeys);
       }
     }
-
-    final Index id2children = entryContainer.getID2Children();
-    flushIndex(id2children, txn, bufferedIndexes.remove(id2children));
-
-    final Index id2subtree = entryContainer.getID2Subtree();
-    final TreeMap<ByteString, BufferedIndexValues> bufferedValues = bufferedIndexes.remove(id2subtree);
-    if (bufferedValues != null)
-    {
-      /*
-       * OPENDJ-1375: add keys in reverse order to be consistent with single
-       * entry processing in add/delete processing. This is necessary in order
-       * to avoid deadlocks.
-       */
-      flushIndex(id2subtree, txn, bufferedValues.descendingMap());
-    }
   }
 
   void put(Index index, ByteString key, EntryID entryID)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/JebFormat.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/JebFormat.java
index 881d1c6..b0edc73 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/JebFormat.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/JebFormat.java
@@ -79,7 +79,7 @@
    */
   static ByteString dnToDNKey(DN dn, int prefixRDNs)
   {
-    final ByteStringBuilder builder = new ByteStringBuilder();
+    final ByteStringBuilder builder = new ByteStringBuilder(128);
     final int startSize = dn.size() - prefixRDNs - 1;
     for (int i = startSize; i >= 0; i--)
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/NullIndex.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/NullIndex.java
deleted file mode 100644
index e61486e..0000000
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/NullIndex.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *      Copyright 2011-2015 ForgeRock AS
- */
-package org.opends.server.backends.pluggable;
-
-import static org.opends.server.backends.pluggable.EntryIDSet.newUndefinedSet;
-
-import org.forgerock.opendj.ldap.ByteSequence;
-import org.forgerock.opendj.ldap.ByteString;
-import org.opends.server.backends.pluggable.spi.Cursor;
-import org.opends.server.backends.pluggable.spi.ReadableTransaction;
-import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
-import org.opends.server.backends.pluggable.spi.TreeName;
-import org.opends.server.backends.pluggable.spi.WriteableTransaction;
-
-/**
- * A null index which replaces id2children and id2subtree when they have been disabled.
- */
-final class NullIndex implements Index
-{
-  private final TreeName name;
-
-  NullIndex(TreeName name)
-  {
-    this.name = name;
-  }
-
-  @Override
-  public void update(WriteableTransaction txn, ByteString key, EntryIDSet deletedIDs, EntryIDSet addedIDs)
-      throws StorageRuntimeException
-  {
-    // Do nothing.
-  }
-
-  @Override
-  public EntryIDSet get(ReadableTransaction txn, ByteSequence key)
-  {
-    return newUndefinedSet();
-  }
-
-  @Override
-  public boolean setIndexEntryLimit(int indexEntryLimit)
-  {
-    return false;
-  }
-
-  @Override
-  public int getIndexEntryLimit()
-  {
-    return 0;
-  }
-
-  @Override
-  public void setTrusted(WriteableTransaction txn, boolean trusted) throws StorageRuntimeException
-  {
-    // Do nothing.
-  }
-
-  @Override
-  public boolean isTrusted()
-  {
-    return true;
-  }
-
-  @Override
-  public boolean getMaintainCount()
-  {
-    return false;
-  }
-
-  @Override
-  public long getRecordCount(ReadableTransaction txn) throws StorageRuntimeException
-  {
-    return 0;
-  }
-
-  @Override
-  public Cursor<ByteString, EntryIDSet> openCursor(ReadableTransaction txn)
-  {
-    return new Cursor<ByteString, EntryIDSet>()
-    {
-
-      @Override
-      public boolean positionToKey(ByteSequence key)
-      {
-        return false;
-      }
-
-      @Override
-      public boolean positionToKeyOrNext(ByteSequence key)
-      {
-        return false;
-      }
-
-      @Override
-      public boolean positionToLastKey()
-      {
-        return false;
-      }
-
-      @Override
-      public boolean positionToIndex(int index)
-      {
-        return false;
-      }
-
-      @Override
-      public boolean next()
-      {
-        return false;
-      }
-
-      @Override
-      public ByteString getKey()
-      {
-        return null;
-      }
-
-      @Override
-      public EntryIDSet getValue()
-      {
-        return null;
-      }
-
-      @Override
-      public void close()
-      {
-        // Nothing to do.
-      }
-
-    };
-  }
-
-  @Override
-  public void importRemove(WriteableTransaction txn, ImportIDSet idsToBeRemoved) throws StorageRuntimeException
-  {
-    // Do nothing.
-  }
-
-  @Override
-  public void importPut(WriteableTransaction txn, ImportIDSet idsToBeAdded) throws StorageRuntimeException
-  {
-    // Do nothing.
-  }
-
-  @Override
-  public TreeName getName()
-  {
-    return name;
-  }
-
-  @Override
-  public void open(WriteableTransaction txn) throws StorageRuntimeException
-  {
-    // Do nothing.
-  }
-
-  @Override
-  public void delete(WriteableTransaction txn) throws StorageRuntimeException
-  {
-    // Do nothing.
-  }
-
-  @Override
-  public void setName(TreeName name)
-  {
-    // Do nothing.
-  }
-
-}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java
index e1a4573..9534b86 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java
@@ -641,7 +641,7 @@
             ec.sharedLock.lock();
             try
             {
-              entryCount += ec.getEntryCount(txn);
+              entryCount += ec.getNumberOfEntriesInBaseDN();
             }
             finally
             {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Suffix.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Suffix.java
index e34988c..e1076f1 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Suffix.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Suffix.java
@@ -249,8 +249,7 @@
 
 
   /**
-   * Sets the trusted status of all of the indexes, vlvIndexes, id2children
-   * and id2subtree indexes.
+   * Sets the trusted status of all of the indexes and vlvIndexes.
    *
    * @param txn a non null database transaction
    * @param trusted True if the indexes should be trusted or false otherwise.
@@ -258,8 +257,6 @@
    */
   public void setIndexesTrusted(WriteableTransaction txn, boolean trusted) throws StorageRuntimeException
   {
-    entryContainer.getID2Children().setTrusted(txn, trusted);
-    entryContainer.getID2Subtree().setTrusted(txn, trusted);
     for (AttributeIndex attributeIndex : entryContainer.getAttributeIndexes())
     {
       setTrusted(txn, attributeIndex.getNameToIndexes().values(), trusted);
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/SuffixContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/SuffixContainer.java
index 6e5dab3..10fd358 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/SuffixContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/SuffixContainer.java
@@ -56,6 +56,11 @@
    */
   String ID2CHILDREN_INDEX_NAME = "id2children";
   /**
+   * The name of the index associating an entry id to the number of immediate
+   * children below it.
+   */
+  String ID2CHILDREN_COUNT_NAME = "id2childrencount";
+  /**
    * The name of the index associating an entry id to the entry id set of all
    * its subordinates, i.e. the children, grand-children, grand-grand-children,
    * ....
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
index c7f1559..1da0f1c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
@@ -38,8 +38,10 @@
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -73,7 +75,7 @@
   /** The verify configuration. */
   private final VerifyConfig verifyConfig;
   /** The root container used for the verify job. */
-  private RootContainer rootContainer;
+  private final RootContainer rootContainer;
 
   /** The number of milliseconds between job progress reports. */
   private final long progressInterval = 10000;
@@ -99,19 +101,15 @@
 
   /** Indicates whether the DN database is to be verified. */
   private boolean verifyDN2ID;
-  /** Indicates whether the children database is to be verified. */
-  private boolean verifyID2Children;
-  /** Indicates whether the subtree database is to be verified. */
-  private boolean verifyID2Subtree;
+  /** Indicates whether the children count database is to be verified. */
+  private boolean verifyID2ChildrenCount;
 
   /** The entry database. */
   private ID2Entry id2entry;
   /** The DN database. */
   private DN2ID dn2id;
   /** The children database. */
-  private Index id2c;
-  /** The subtree database. */
-  private Index id2s;
+  private ID2Count id2childrenCount;
 
   /** A list of the attribute indexes to be verified. */
   private final ArrayList<AttributeIndex> attrIndexList = new ArrayList<AttributeIndex>();
@@ -123,8 +121,9 @@
    *
    * @param verifyConfig The verify configuration.
    */
-  VerifyJob(VerifyConfig verifyConfig)
+  VerifyJob(RootContainer rootContainer, VerifyConfig verifyConfig)
   {
+    this.rootContainer = rootContainer;
     this.verifyConfig = verifyConfig;
   }
 
@@ -136,7 +135,7 @@
    * @throws StorageRuntimeException If an error occurs in the database.
    * @throws DirectoryException If an error occurs while verifying the backend.
    */
-  long verifyBackend(final RootContainer rootContainer) throws StorageRuntimeException,
+  long verifyBackend() throws StorageRuntimeException,
       DirectoryException
   {
     try
@@ -146,7 +145,7 @@
         @Override
         public Long run(ReadableTransaction txn) throws Exception
         {
-          return verifyBackend0(txn, rootContainer);
+          return verifyBackend0(txn);
         }
       });
     }
@@ -160,10 +159,8 @@
     }
   }
 
-  private long verifyBackend0(ReadableTransaction txn, RootContainer rootContainer)
-      throws StorageRuntimeException, DirectoryException
+  private long verifyBackend0(ReadableTransaction txn) throws StorageRuntimeException, DirectoryException
   {
-    this.rootContainer = rootContainer;
     EntryContainer entryContainer =
         rootContainer.getEntryContainer(verifyConfig.getBaseDN());
 
@@ -177,11 +174,7 @@
       if (completeList.isEmpty() && cleanList.isEmpty())
       {
         verifyDN2ID = true;
-        if (rootContainer.getConfiguration().isSubordinateIndexesEnabled())
-        {
-          verifyID2Children = true;
-          verifyID2Subtree = true;
-        }
+        verifyID2ChildrenCount = true;
         attrIndexList.addAll(entryContainer.getAttributeIndexes());
       }
       else
@@ -204,31 +197,9 @@
           {
             verifyDN2ID = true;
           }
-          else if ("id2children".equals(lowerName))
+          if ("id2childrencount".equals(lowerName))
           {
-            if (rootContainer.getConfiguration().isSubordinateIndexesEnabled())
-            {
-              verifyID2Children = true;
-            }
-            else
-            {
-              LocalizableMessage msg = NOTE_JEB_SUBORDINATE_INDEXES_DISABLED
-                  .get(rootContainer.getConfiguration().getBackendId());
-              throw new StorageRuntimeException(msg.toString());
-            }
-          }
-          else if ("id2subtree".equals(lowerName))
-          {
-            if (rootContainer.getConfiguration().isSubordinateIndexesEnabled())
-            {
-              verifyID2Subtree = true;
-            }
-            else
-            {
-              LocalizableMessage msg = NOTE_JEB_SUBORDINATE_INDEXES_DISABLED
-                  .get(rootContainer.getConfiguration().getBackendId());
-              throw new StorageRuntimeException(msg.toString());
-            }
+            verifyID2ChildrenCount = true;
           }
           else if(lowerName.startsWith("vlv."))
           {
@@ -275,8 +246,7 @@
       // the entry entryContainer methods.
       id2entry = entryContainer.getID2Entry();
       dn2id = entryContainer.getDN2ID();
-      id2c = entryContainer.getID2Children();
-      id2s = entryContainer.getID2Subtree();
+      id2childrenCount = entryContainer.getID2ChildrenCount();
 
       // Make a note of the time we started.
       long startTime = System.currentTimeMillis();
@@ -387,8 +357,7 @@
    */
   private void iterateID2Entry(ReadableTransaction txn) throws StorageRuntimeException
   {
-    Cursor<ByteString, ByteString> cursor = txn.openCursor(id2entry.getName());
-    try
+    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(id2entry.getName()))
     {
       long storedEntryCount = id2entry.getRecordCount(txn);
       while (cursor.next())
@@ -445,10 +414,6 @@
         }
       }
     }
-    finally
-    {
-      cursor.close();
-    }
   }
 
   /**
@@ -465,13 +430,9 @@
     {
       iterateDN2ID(txn);
     }
-    else if (verifyID2Children)
+    else if (verifyID2ChildrenCount)
     {
-      iterateID2Children(txn);
-    }
-    else if (verifyID2Subtree)
-    {
-      iterateID2Subtree(txn);
+      iterateID2ChildrenCount(txn);
     }
     else if (attrIndexList.size() > 0)
     {
@@ -496,34 +457,31 @@
    */
   private void iterateDN2ID(ReadableTransaction txn) throws StorageRuntimeException
   {
-    Cursor<ByteString, ByteString> cursor = txn.openCursor(dn2id.getName());
-    try
+    final Queue<ChildrenCount> childrenCounters = new LinkedList<>();
+    ChildrenCount currentNode = null;
+
+    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(dn2id.getName()))
     {
       while (cursor.next())
       {
         keyCount++;
 
-        ByteString key = cursor.getKey();
-        ByteString value = cursor.getValue();
-
-        EntryID entryID;
+        final ByteString key = cursor.getKey();
+        final EntryID entryID;
         try
         {
-          entryID = new EntryID(value);
+          entryID =  new EntryID(cursor.getValue());
         }
         catch (Exception e)
         {
           errorCount++;
-          if (logger.isTraceEnabled())
-          {
-            logger.traceException(e);
-
-            logger.trace("File dn2id has malformed ID for DN <%s>:%n%s%n", key, StaticUtils.bytesToHex(value));
-          }
+          logger.trace("File dn2id has malformed ID for DN <%s>", key, e);
           continue;
         }
 
-        Entry entry;
+        currentNode = verifyID2ChildrenCount(txn, childrenCounters, key, entryID);
+
+        final Entry entry;
         try
         {
           entry = id2entry.get(txn, entryID);
@@ -538,261 +496,68 @@
         if (entry == null)
         {
           errorCount++;
-          if (logger.isTraceEnabled())
-          {
-            logger.trace("File dn2id has DN <%s> referencing unknown ID %d%n", key, entryID);
-          }
+          logger.trace("File dn2id has DN <%s> referencing unknown ID %d%n", key, entryID);
         }
         else if (!key.equals(dnToDNKey(entry.getName(), verifyConfig.getBaseDN().size())))
         {
           errorCount++;
-          if (logger.isTraceEnabled())
-          {
-            logger.trace("File dn2id has DN <%s> referencing entry with wrong DN <%s>%n", key, entry.getName());
-          }
+          logger.trace("File dn2id has DN <%s> referencing entry with wrong DN <%s>%n", key, entry.getName());
         }
       }
-    }
-    finally
-    {
-      cursor.close();
+
+      while ((currentNode = childrenCounters.poll()) != null)
+      {
+        verifyID2ChildrenCount(txn, currentNode);
+      }
     }
   }
 
-  /**
-   * Iterate through the entries in ID2Children to perform a check for
-   * index cleanliness.
-   *
-   * @throws StorageRuntimeException If an error occurs in the database.
-   */
-  private void iterateID2Children(ReadableTransaction txn) throws StorageRuntimeException
+  private ChildrenCount verifyID2ChildrenCount(ReadableTransaction txn, final Queue<ChildrenCount> childrenCounters,
+      final ByteString key, final EntryID entryID)
   {
-    Cursor<ByteString, EntryIDSet> cursor = id2c.openCursor(txn);
-    try
+    while (childrenCounters.peek() != null && !DN2ID.isChild(childrenCounters.peek().baseDN, key))
     {
-      while (cursor.next())
-      {
-        keyCount++;
-
-        ByteString key = cursor.getKey();
-
-        EntryID entryID;
-        try
-        {
-          entryID = new EntryID(key);
-        }
-        catch (Exception e)
-        {
-          errorCount++;
-          if (logger.isTraceEnabled())
-          {
-            logger.traceException(e);
-
-            logger.trace("File id2children has malformed ID %s%n", StaticUtils.bytesToHex(key));
-          }
-          continue;
-        }
-
-        EntryIDSet entryIDSet;
-
-        try
-        {
-          entryIDSet = cursor.getValue();
-        }
-        catch (Exception e)
-        {
-          errorCount++;
-          logger.traceException(e);
-          logger.trace("File id2children has malformed ID list for ID %s", entryID);
-          continue;
-        }
-
-        updateIndexStats(entryIDSet);
-
-        if (entryIDSet.isDefined())
-        {
-          Entry entry;
-          try
-          {
-            entry = id2entry.get(txn, entryID);
-          }
-          catch (Exception e)
-          {
-            logger.traceException(e);
-            errorCount++;
-            continue;
-          }
-
-          if (entry == null)
-          {
-            errorCount++;
-            if (logger.isTraceEnabled())
-            {
-              logger.trace("File id2children has unknown ID %d%n", entryID);
-            }
-            continue;
-          }
-
-          for (EntryID id : entryIDSet)
-          {
-            Entry childEntry;
-            try
-            {
-              childEntry = id2entry.get(txn, id);
-            }
-            catch (Exception e)
-            {
-              logger.traceException(e);
-              errorCount++;
-              continue;
-            }
-
-            if (childEntry == null)
-            {
-              errorCount++;
-              if (logger.isTraceEnabled())
-              {
-                logger.trace("File id2children has ID %d referencing unknown ID %d%n", entryID, id);
-              }
-              continue;
-            }
-
-            if (!childEntry.getName().isDescendantOf(entry.getName()) ||
-                 childEntry.getName().size() !=
-                 entry.getName().size() + 1)
-            {
-              errorCount++;
-              if (logger.isTraceEnabled())
-              {
-                logger.trace("File id2children has ID %d with DN <%s> " +
-                    "referencing ID %d with non-child DN <%s>%n",
-                    entryID, entry.getName(), id, childEntry.getName());
-              }
-            }
-          }
-        }
-      }
+      // This subtree is fully processed, pop the counter of the parent DN from the stack and verify it's value
+      verifyID2ChildrenCount(txn, childrenCounters.remove());
     }
-    finally
+    if (childrenCounters.peek() != null)
     {
-      cursor.close();
+      childrenCounters.peek().numberOfChildren++;
+    }
+    final ChildrenCount node = new ChildrenCount(key, entryID);
+    childrenCounters.add(node);
+    return node;
+  }
+
+  private void verifyID2ChildrenCount(ReadableTransaction txn, ChildrenCount parent) {
+    final long expected = parent.numberOfChildren;
+    final long currentValue = id2childrenCount.getCount(txn, parent.entryID);
+    if (expected != currentValue)
+    {
+      errorCount++;
+      logger.trace("File id2childrenCount has wrong number of children for DN <%s> (got %d, expecting %d)",
+          parent.baseDN, currentValue, expected);
     }
   }
 
-  /**
-   * Iterate through the entries in ID2Subtree to perform a check for
-   * index cleanliness.
-   *
-   * @throws StorageRuntimeException If an error occurs in the database.
-   */
-  private void iterateID2Subtree(ReadableTransaction txn) throws StorageRuntimeException
+  private void iterateID2ChildrenCount(ReadableTransaction txn) throws StorageRuntimeException
   {
-    Cursor<ByteString, EntryIDSet> cursor = id2s.openCursor(txn);
-    try
-    {
-      while (cursor.next())
-      {
-        keyCount++;
-
-        ByteString key = cursor.getKey();
-        EntryID entryID;
-        try
-        {
-          entryID = new EntryID(key);
-        }
-        catch (Exception e)
-        {
-          errorCount++;
-          if (logger.isTraceEnabled())
-          {
-            logger.traceException(e);
-
-            logger.trace("File id2subtree has malformed ID %s%n", StaticUtils.bytesToHex(key));
-          }
-          continue;
-        }
-
-        EntryIDSet entryIDSet;
-        try
-        {
-          entryIDSet = cursor.getValue();
-        }
-        catch (Exception e)
-        {
-          errorCount++;
-          logger.traceException(e);
-          logger.trace("File id2subtree has malformed ID list  for ID %s", entryID);
-          continue;
-        }
-
-        updateIndexStats(entryIDSet);
-
-        if (entryIDSet.isDefined())
-        {
-          Entry entry;
-          try
-          {
-            entry = id2entry.get(txn, entryID);
-          }
-          catch (Exception e)
-          {
-            logger.traceException(e);
-            errorCount++;
-            continue;
-          }
-
-          if (entry == null)
-          {
-            errorCount++;
-            if (logger.isTraceEnabled())
-            {
-              logger.trace("File id2subtree has unknown ID %d%n", entryID);
-            }
-            continue;
-          }
-
-          for (EntryID id : entryIDSet)
-          {
-            Entry subordEntry;
-            try
-            {
-              subordEntry = id2entry.get(txn, id);
-            }
-            catch (Exception e)
-            {
-              logger.traceException(e);
-              errorCount++;
-              continue;
-            }
-
-            if (subordEntry == null)
-            {
-              errorCount++;
-              if (logger.isTraceEnabled())
-              {
-                logger.trace("File id2subtree has ID %d referencing " +
-                    "unknown ID %d%n", entryID, id);
-              }
-              continue;
-            }
-
-            if (!subordEntry.getName().isDescendantOf(entry.getName()))
-            {
-              errorCount++;
-              if (logger.isTraceEnabled())
-              {
-                logger.trace("File id2subtree has ID %d with DN <%s> " +
-                    "referencing ID %d with non-subordinate DN <%s>%n",
-                    entryID, entry.getName(), id, subordEntry.getName());
-              }
-            }
-          }
-        }
-      }
+    Cursor<EntryID, Long> cursor = id2childrenCount.openCursor(txn);
+    if  (!cursor.next()) {
+      return;
     }
-    finally
-    {
-      cursor.close();
+
+    EntryID currentEntryID = new EntryID(-1);
+    while(cursor.next()) {
+      if (cursor.getKey().equals(currentEntryID)) {
+        /** Sharded cursor may return the same EntryID multiple times */
+        continue;
+      }
+      currentEntryID = cursor.getKey();
+      if (!id2entry.containsEntryID(txn, currentEntryID)) {
+        logger.trace("File id2ChildrenCount reference non-existing EntryID <%d>%n", currentEntryID);
+        errorCount++;
+      }
     }
   }
 
@@ -864,8 +629,7 @@
       return;
     }
 
-    Cursor<ByteString, ByteString> cursor = txn.openCursor(vlvIndex.getName());
-    try
+    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(vlvIndex.getName()))
     {
       while (cursor.next())
       {
@@ -906,10 +670,6 @@
 
       }
     }
-    finally
-    {
-      cursor.close();
-    }
   }
 
   /**
@@ -926,8 +686,7 @@
       return;
     }
 
-    Cursor<ByteString,EntryIDSet> cursor = index.openCursor(txn);
-    try
+    try(final Cursor<ByteString,EntryIDSet> cursor = index.openCursor(txn))
     {
       while (cursor.next())
       {
@@ -1042,10 +801,6 @@
         }
       }
     }
-    finally
-    {
-      cursor.close();
-    }
   }
 
   /**
@@ -1060,14 +815,6 @@
     {
       verifyDN2ID(txn, entryID, entry);
     }
-    if (verifyID2Children)
-    {
-      verifyID2Children(txn, entryID, entry);
-    }
-    if (verifyID2Subtree)
-    {
-      verifyID2Subtree(txn, entryID, entry);
-    }
     verifyIndex(txn, entryID, entry);
   }
 
@@ -1141,137 +888,6 @@
   }
 
   /**
-   * Check that the ID2Children index is complete for a given entry.
-   *
-   * @param entryID The entry ID.
-   * @param entry The entry to be checked.
-   */
-  private void verifyID2Children(ReadableTransaction txn, EntryID entryID, Entry entry)
-  {
-    DN dn = entry.getName();
-
-    DN parentDN = getParent(dn);
-    if (parentDN != null)
-    {
-      EntryID parentID = null;
-      try
-      {
-        parentID = dn2id.get(txn, parentDN);
-        if (parentID == null)
-        {
-          if (logger.isTraceEnabled())
-          {
-            logger.trace("File dn2id is missing key %s.%n", parentDN);
-          }
-          errorCount++;
-        }
-      }
-      catch (Exception e)
-      {
-        if (logger.isTraceEnabled())
-        {
-          logger.traceException(e);
-          logger.trace("File dn2id has error reading key %s: %s.", parentDN, e.getMessage());
-        }
-        errorCount++;
-      }
-      if (parentID != null)
-      {
-        try
-        {
-          ConditionResult cr = indexContainsID(id2c, txn, parentID.toByteString(), entryID);
-          if (cr == ConditionResult.FALSE)
-          {
-            if (logger.isTraceEnabled())
-            {
-              logger.trace("File id2children is missing ID %d for key %d.%n", entryID, parentID);
-            }
-            errorCount++;
-          }
-          else if (cr == ConditionResult.UNDEFINED)
-          {
-            incrEntryLimitStats(id2c, parentID.toByteString());
-          }
-        }
-        catch (StorageRuntimeException e)
-        {
-          if (logger.isTraceEnabled())
-          {
-            logger.traceException(e);
-
-            logger.trace("File id2children has error reading key %d: %s.", parentID, e.getMessage());
-          }
-          errorCount++;
-        }
-      }
-    }
-  }
-
-  /**
-   * Check that the ID2Subtree index is complete for a given entry.
-   *
-   * @param entryID The entry ID.
-   * @param entry The entry to be checked.
-   */
-  private void verifyID2Subtree(ReadableTransaction txn, EntryID entryID, Entry entry)
-  {
-    for (DN dn = getParent(entry.getName()); dn != null; dn = getParent(dn))
-    {
-      EntryID id = null;
-      try
-      {
-        id = dn2id.get(txn, dn);
-        if (id == null)
-        {
-          if (logger.isTraceEnabled())
-          {
-            logger.trace("File dn2id is missing key %s.%n", dn);
-          }
-          errorCount++;
-        }
-      }
-      catch (Exception e)
-      {
-        if (logger.isTraceEnabled())
-        {
-          logger.traceException(e);
-          logger.trace("File dn2id has error reading key %s: %s.%n", dn, e.getMessage());
-        }
-        errorCount++;
-      }
-      if (id != null)
-      {
-        try
-        {
-          ConditionResult cr = indexContainsID(id2s, txn, id.toByteString(), entryID);
-          if (cr == ConditionResult.FALSE)
-          {
-            if (logger.isTraceEnabled())
-            {
-              logger.trace("File id2subtree is missing ID %d for key %d.%n", entryID, id);
-            }
-            errorCount++;
-          }
-          else if (cr == ConditionResult.UNDEFINED)
-          {
-            incrEntryLimitStats(id2s, id.toByteString());
-          }
-        }
-        catch (StorageRuntimeException e)
-        {
-          if (logger.isTraceEnabled())
-          {
-            logger.traceException(e);
-
-            logger.trace("File id2subtree has error reading key %d: %s.%n", id, e.getMessage());
-          }
-          errorCount++;
-        }
-      }
-    }
-  }
-
-  /**
    * Construct a printable string from a raw key value.
    *
    * @param indexName
@@ -1280,7 +896,7 @@
    *          The bytes of the key.
    * @return A string that may be logged or printed.
    */
-  private String keyDump(String indexName, ByteSequence key)
+  private static String keyDump(String indexName, ByteSequence key)
   {
     StringBuilder buffer = new StringBuilder(128);
     buffer.append("Index: ");
@@ -1391,7 +1007,7 @@
     }
   }
 
-  private ConditionResult indexContainsID(Index index, ReadableTransaction txn, ByteString key, EntryID entryID)
+  private static ConditionResult indexContainsID(Index index, ReadableTransaction txn, ByteString key, EntryID entryID)
   {
     EntryIDSet entryIDSet = index.get(txn, key);
     if (entryIDSet.isDefined())
@@ -1416,6 +1032,20 @@
     return dn.getParentDNInSuffix();
   }
 
+  /**
+   * This class maintain the number of children for a given dn
+   */
+  private static final class ChildrenCount {
+    private final ByteString baseDN;
+    private final EntryID entryID;
+    private long numberOfChildren;
+
+    private ChildrenCount(ByteString dn, EntryID id) {
+      this.baseDN = dn;
+      this.entryID = id;
+    }
+  }
+
   /** This class reports progress of the verify job at fixed intervals. */
   private final class ProgressTask extends TimerTask
   {
@@ -1454,13 +1084,9 @@
         {
           totalCount = dn2id.getRecordCount(txn);
         }
-        else if (verifyID2Children)
+        else if (verifyID2ChildrenCount)
         {
-          totalCount = id2c.getRecordCount(txn);
-        }
-        else if (verifyID2Subtree)
-        {
-          totalCount = id2s.getRecordCount(txn);
+          totalCount = id2childrenCount.getRecordCount(txn);
         }
         else if (!attrIndexList.isEmpty())
         {
@@ -1478,7 +1104,7 @@
       }
       else
       {
-        totalCount = rootContainer.getEntryContainer(verifyConfig.getBaseDN()).getEntryCount(txn);
+        totalCount = rootContainer.getEntryContainer(verifyConfig.getBaseDN()).getNumberOfEntriesInBaseDN();
       }
     }
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Cursor.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Cursor.java
index 48e9444..87455d0 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Cursor.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Cursor.java
@@ -25,16 +25,14 @@
  */
 package org.opends.server.backends.pluggable.spi;
 
-import java.io.Closeable;
-
 import org.forgerock.opendj.ldap.ByteSequence;
 
 /**
- * Cursor that iterates through records in a tree.
+ * Sequential cursor extended with navigation methods.
  * @param <K> Type of the record's key
  * @param <V> Type of the record's value
  */
-public interface Cursor<K,V> extends Closeable
+public interface Cursor<K,V> extends SequentialCursor<K, V>
 {
   /**
    * Positions the cursor to the provided key if it exists in the tree.
@@ -74,32 +72,4 @@
    * @return {@code true} if the cursor could be positioned to the index, {@code false} otherwise
    */
   boolean positionToIndex(int index);
-
-  /**
-   * Moves this cursor to the next record in the tree.
-   *
-   * @return {@code true} if the cursor could move to the next record,
-   *         {@code false} if no next record exists
-   */
-  boolean next();
-
-  /**
-   * Returns the key of the record on which this cursor is currently positioned.
-   *
-   * @return the current record's key,
-   *         or {@code null} if this cursor is not positioned on any record.
-   */
-  K getKey();
-
-  /**
-   * Returns the value of the record on which this cursor is currently positioned.
-   *
-   * @return the current record's value,
-   *         or {@code null} if this cursor is not positioned on any record.
-   */
-  V getValue();
-
-  /** {@inheritDoc} */
-  @Override
-  void close();
 }
\ No newline at end of file
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/SequentialCursor.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/SequentialCursor.java
new file mode 100644
index 0000000..6bc2276
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/SequentialCursor.java
@@ -0,0 +1,74 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014-2015 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable.spi;
+
+import java.io.Closeable;
+import java.util.NoSuchElementException;
+
+/**
+ * Cursor extended with navigation methods.
+ * @param <K> Type of the record's key
+ * @param <V> Type of the record's value
+ */
+public interface SequentialCursor<K,V> extends Closeable
+{
+  /**
+   * Moves this cursor to the next record in the tree.
+   *
+   * @return {@code true} if the cursor has moved to the next record,
+   *         {@code false} if no next record exists leaving cursor
+   *         in undefined state.
+   */
+  boolean next();
+
+  /**
+   * Check whether this cursor is currently pointing to valid record.
+   *
+   * @return {@code true} if the cursor is pointing to a valid entry,
+   *         {@code false} if cursor is not pointing to a valid entry
+   */
+  boolean isDefined();
+
+  /**
+   * Returns the key of the record on which this cursor is currently positioned.
+   *
+   * @return the current record's key.
+   * @throws NoSuchElementException if the cursor is not defined.
+   */
+  K getKey() throws NoSuchElementException;
+
+  /**
+   * Returns the value of the record on which this cursor is currently positioned.
+   *
+   * @return the current record's value.
+   * @throws NoSuchElementException if the cursor is not defined.
+   */
+  V getValue() throws NoSuchElementException;
+
+  /** {@inheritDoc} */
+  @Override
+  void close();
+}
\ No newline at end of file
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
index 9838cb3..91ed7c8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.backends.task;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.util.ServerConstants.*;
@@ -345,12 +346,21 @@
     return ConditionResult.valueOf(ret != 0);
   }
 
-
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
+    checkNotNull(baseDN, "baseDN must not be null");
+    return numSubordinates(baseDN, true) + 1;
+  }
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-      throws DirectoryException
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
+    checkNotNull(parentDN, "parentDN must not be null");
+    return numSubordinates(parentDN, false);
+  }
+
+  private long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException
   {
     if (entryDN == null)
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java
index 6ce62a5..3d8bf6d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java
@@ -26,6 +26,7 @@
  */
 package org.opends.server.extensions;
 
+import static org.forgerock.util.Reject.*;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.extensions.ExtensionsConstants.*;
@@ -846,29 +847,31 @@
 
   /** {@inheritDoc} */
   @Override
-  public long numSubordinates(DN entryDN, boolean subtree)
-      throws DirectoryException
+  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
   {
-    ConfigEntry baseEntry = configEntries.get(entryDN);
+    checkNotNull(baseDN, "baseDN must not be null");
+    final ConfigEntry baseEntry = configEntries.get(baseDN);
     if (baseEntry == null)
     {
       return -1;
     }
 
-    if(!subtree)
+    long count = 1;
+    for (ConfigEntry child : baseEntry.getChildren().values())
     {
-      return baseEntry.getChildren().size();
+      count += getNumberOfEntriesInBaseDN(child.getDN());
+      count++;
     }
-    else
-    {
-      long count = 0;
-      for(ConfigEntry child : baseEntry.getChildren().values())
-      {
-        count += numSubordinates(child.getDN(), true);
-        count ++;
-      }
-      return count;
-    }
+    return count;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public long getNumberOfChildren(DN parentDN) throws DirectoryException
+  {
+    checkNotNull(parentDN, "parentDN must not be null");
+    final ConfigEntry baseEntry = configEntries.get(parentDN);
+    return baseEntry != null ? baseEntry.getChildren().size() : -1;
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java
index 6ca44c4..2d6f013 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java
@@ -78,7 +78,7 @@
 
     try
     {
-      long count = backend.numSubordinates(entry.getName(), false);
+      long count = backend.getNumberOfChildren(entry.getName());
       if(count >= 0)
       {
         return Attributes.create(rule.getAttributeType(), String.valueOf(count));
@@ -96,11 +96,11 @@
   @Override
   public boolean hasValue(Entry entry, VirtualAttributeRule rule)
   {
-    Backend backend = DirectoryServer.getBackend(entry.getName());
+    Backend<?> backend = DirectoryServer.getBackend(entry.getName());
 
     try
     {
-       return backend.numSubordinates(entry.getName(), false) >= 0;
+       return backend.getNumberOfChildren(entry.getName()) >= 0;
     }
     catch(DirectoryException de)
     {
@@ -116,7 +116,7 @@
     Backend<?> backend = DirectoryServer.getBackend(entry.getName());
     try
     {
-      long count = backend.numSubordinates(entry.getName(), false);
+      long count = backend.getNumberOfChildren(entry.getName());
       return count >= 0 && Long.parseLong(value.toString()) == count;
     }
     catch (NumberFormatException e)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/monitors/BackendMonitor.java b/opendj-server-legacy/src/main/java/org/opends/server/monitors/BackendMonitor.java
index aed7d94..dbd83e3 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/monitors/BackendMonitor.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/monitors/BackendMonitor.java
@@ -161,7 +161,7 @@
         long entryCount = -1;
         try
         {
-          entryCount = backend.numSubordinates(dn, true) + 1;
+          entryCount = backend.getNumberOfEntriesInBaseDN(dn);
         }
         catch (Exception ex)
         {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index ccd50bd..b0b14c9 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -3425,7 +3425,7 @@
       throw new DirectoryException(ResultCode.OTHER, message);
     }
 
-    long numberOfEntries = backend.numSubordinates(getBaseDN(), true) + 1;
+    long numberOfEntries = backend.getNumberOfEntriesInBaseDN(getBaseDN());
     long entryCount = Math.min(numberOfEntries, 1000);
     OutputStream os;
     ReplLDIFOutputStream ros = null;
@@ -4258,7 +4258,7 @@
       throw new DirectoryException(ResultCode.OTHER, msg);
     }
 
-    return backend.numSubordinates(getBaseDN(), true) + 1;
+    return backend.getNumberOfEntriesInBaseDN(getBaseDN());
   }
 
   /** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/DN.java b/opendj-server-legacy/src/main/java/org/opends/server/types/DN.java
index d604537..0b5b789 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/DN.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/DN.java
@@ -37,6 +37,7 @@
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.ByteSequenceReader;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ByteStringBuilder;
@@ -530,8 +531,6 @@
     }
   }
 
-
-
   /**
    * Decodes the provided ASN.1 octet string as a DN.
    *
@@ -543,7 +542,7 @@
    *                              decode the provided ASN.1 octet
    *                              string as a DN.
    */
-  public static DN decode(ByteString dnString)
+  public static DN decode(ByteSequence dnString)
          throws DirectoryException
   {
     // A null or empty DN is acceptable.
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/LDIFBackendTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/LDIFBackendTestCase.java
index f32c561..a8e49cd 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/LDIFBackendTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/LDIFBackendTestCase.java
@@ -647,31 +647,23 @@
     }
   }
 
-
-
   /**
    * Tests the {@code numSubordinates} method.
    *
    * @throws  Exception  If an unexpected problem occurs.
    */
   @Test
-  public void testNumSubordinates()
-         throws Exception
+  public void testNumSubordinates() throws Exception
   {
     Backend<?> b = getLDIFBackend();
 
-    assertEquals(b.numSubordinates(DN.valueOf("o=ldif"), false), 1);
-    assertEquals(b.numSubordinates(DN.valueOf("o=ldif"), true), 26);
-    assertEquals(b.numSubordinates(
-        DN.valueOf("uid=user.1,ou=People,o=ldif"), false), 0);
-    assertEquals(b.numSubordinates(
-        DN.valueOf("uid=user.1,ou=People,o=ldif"), true), 0);
-
+    assertEquals(b.getNumberOfChildren(DN.valueOf("o=ldif")), 1);
+    assertEquals(b.getNumberOfEntriesInBaseDN(DN.valueOf("o=ldif")), 27);
+    assertEquals(b.getNumberOfChildren(DN.valueOf("uid=user.1,ou=People,o=ldif")), 0);
     try
     {
-      b.numSubordinates(DN.valueOf("ou=nonexistent,o=ldif"), false);
-      fail("Expected an exception when calling numSubordinates on a " +
-           "non-existent entry");
+      b.getNumberOfChildren(DN.valueOf("ou=nonexistent,o=ldif"));
+      fail("Expected an exception when calling numSubordinates on a " + "non-existent entry");
     }
     catch (DirectoryException de)
     {
@@ -679,7 +671,10 @@
     }
   }
 
-
+  @Test(expectedExceptions=DirectoryException.class)
+  public void testCannotGetNumberOfEntriesForNotBaseDN() throws Exception {
+    assertEquals(getLDIFBackend().getNumberOfEntriesInBaseDN(DN.valueOf("uid=user.1,ou=People,o=ldif")), 0);
+  }
 
   /**
    * Tests LDIF export functionality.
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java
index 472ae0d..6f41e8d 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -640,25 +640,27 @@
   public void testNumSubordinates() throws Exception
   {
     DN dn = DN.valueOf("dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 1);
-    assertEquals(backend.numSubordinates(dn, true), 13);
+    assertEquals(backend.getNumberOfChildren(dn), 1);
+    assertEquals(backend.getNumberOfEntriesInBaseDN(dn), 14);
     dn = DN.valueOf("ou=People,dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 12);
-    assertEquals(backend.numSubordinates(dn, true), 12);
+    assertEquals(backend.getNumberOfChildren(dn), 12);
     dn = DN.valueOf("dc=com");
-    assertEquals(backend.numSubordinates(dn, false), -1);
-    assertEquals(backend.numSubordinates(dn, true), -1);
+    assertEquals(backend.getNumberOfChildren(dn), -1);
     dn = DN.valueOf("dc=test1,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 2);
-    assertEquals(backend.numSubordinates(dn, true), 2);
+    assertEquals(backend.getNumberOfChildren(dn), 2);
     dn = DN.valueOf("uid=user.10,ou=People,dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 0);
-    assertEquals(backend.numSubordinates(dn, true), 0);
+    assertEquals(backend.getNumberOfChildren(dn), 0);
     dn = DN.valueOf("uid=does not exist,ou=People,dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), -1);
-    assertEquals(backend.numSubordinates(dn, true), -1);
+    assertEquals(backend.getNumberOfChildren(dn), -1);
   }
 
+  @Test(expectedExceptions = DirectoryException.class)
+  public void testCannotGetNumberOfEntriesInNotBaseDN() throws Exception
+  {
+    backend.getNumberOfEntriesInBaseDN(DN.valueOf("ou=People,dc=test,dc=com"));
+  }
+
+
   @Test(dependsOnMethods = "testAdd")
   public void testSearchIndex() throws Exception {
     Set<String> attribs = new LinkedHashSet<String>();
@@ -710,7 +712,7 @@
     assertResultsCountIs(1, debugString);
   }
 
-  private void assertResultsCountIs(int expectedCount, String debugString)
+  private static void assertResultsCountIs(int expectedCount, String debugString)
   {
     int finalStartPos = debugString.indexOf("final=") + 13;
     int finalEndPos = debugString.indexOf("]", finalStartPos);
@@ -719,13 +721,13 @@
   }
 
   /** Returns the debug string from a search result. */
-  private String getDebugString(List<SearchResultEntry> result)
+  private static String getDebugString(List<SearchResultEntry> result)
   {
     return result.get(0).getAttribute("debugsearchindex").get(0).toString();
   }
 
   /** Returns the results of subtree search on provided connection with provided filter. */
-  private List<SearchResultEntry> doSubtreeSearch(String filter, Set<String> attribs) throws Exception
+  private static List<SearchResultEntry> doSubtreeSearch(String filter, Set<String> attribs) throws Exception
   {
     final SearchRequest request =
         newSearchRequest("dc=test,dc=com", SearchScope.WHOLE_SUBTREE, filter).addAttribute(attribs);
@@ -813,7 +815,7 @@
     }
   }
 
-  private List<AttributeIndexer> newAttributeIndexers(AttributeType attrType, MatchingRule matchingRule)
+  private static List<AttributeIndexer> newAttributeIndexers(AttributeType attrType, MatchingRule matchingRule)
   {
     List<AttributeIndexer> indexers = new ArrayList<AttributeIndexer>();
     for (org.forgerock.opendj.ldap.spi.Indexer indexer : matchingRule.getIndexers())
@@ -823,14 +825,14 @@
     return indexers;
   }
 
-  private IndexingOptions getOptions()
+  private static IndexingOptions getOptions()
   {
     final IndexingOptions options = mock(IndexingOptions.class);
     when(options.substringKeySize()).thenReturn(6);
     return options;
   }
 
-  private void assertIndexContainsID(List<? extends Indexer> indexers, Entry entry, Index index, EntryID entryID)
+  private static void assertIndexContainsID(List<? extends Indexer> indexers, Entry entry, Index index, EntryID entryID)
   {
     for (Indexer indexer : indexers)
     {
@@ -846,8 +848,8 @@
     }
   }
 
-  private void assertIndexContainsID(List<? extends Indexer> indexers, Entry entry,
-      Index index, EntryID entryID, ConditionResult expected)
+  private static void assertIndexContainsID(List<? extends Indexer> indexers, Entry entry, Index index,
+      EntryID entryID, ConditionResult expected)
   {
     for (Indexer indexer : indexers)
     {
@@ -858,8 +860,8 @@
     }
   }
 
-  private void assertIndexContainsID(Set<ByteString> addKeys, Index index,
-      EntryID entryID, ConditionResult expected)
+  private static void assertIndexContainsID(Set<ByteString> addKeys, Index index, EntryID entryID,
+      ConditionResult expected)
   {
     DatabaseEntry key = new DatabaseEntry();
     for (ByteString keyBytes : addKeys)
@@ -1298,7 +1300,7 @@
     assertEquals(resultCode, 0);
   }
 
-  private boolean findContainer(List<DatabaseContainer> databases, String lowercaseName)
+  private static boolean findContainer(List<DatabaseContainer> databases, String lowercaseName)
   {
     for (DatabaseContainer dc : databases)
     {
@@ -1334,25 +1336,20 @@
   public void testNumSubordinatesIndexEntryLimitExceeded() throws Exception
   {
     DN dn = DN.valueOf("dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 1);
-    assertEquals(backend.numSubordinates(dn, true), 14);
+    assertEquals(backend.getNumberOfChildren(dn), 1);
+    assertEquals(backend.getNumberOfEntriesInBaseDN(dn), 15);
 
     // 1 entry was deleted and 2 added for a total of 13
     dn = DN.valueOf("ou=People,dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 13);
-    assertEquals(backend.numSubordinates(dn, true), 13);
+    assertEquals(backend.getNumberOfChildren(dn), 13);
     dn = DN.valueOf("dc=com");
-    assertEquals(backend.numSubordinates(dn, false), -1);
-    assertEquals(backend.numSubordinates(dn, true), -1);
+    assertEquals(backend.getNumberOfChildren(dn), -1);
     dn = DN.valueOf("dc=test1,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 2);
-    assertEquals(backend.numSubordinates(dn, true), 2);
+    assertEquals(backend.getNumberOfChildren(dn), 2);
     dn = DN.valueOf("uid=user.10,ou=People,dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), 0);
-    assertEquals(backend.numSubordinates(dn, true), 0);
+    assertEquals(backend.getNumberOfChildren(dn), 0);
     dn = DN.valueOf("uid=does not exist,ou=People,dc=test,dc=com");
-    assertEquals(backend.numSubordinates(dn, false), -1);
-    assertEquals(backend.numSubordinates(dn, true), -1);
+    assertEquals(backend.getNumberOfChildren(dn), -1);
   }
 
 
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ControlsTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ControlsTestCase.java
index 061a550..52d2db4 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ControlsTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ControlsTestCase.java
@@ -135,7 +135,6 @@
     when(backendCfg.getBaseDN()).thenReturn(newSortedSet(baseDN));
     when(backendCfg.listBackendIndexes()).thenReturn(new String[0]);
     when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[] { SORT_ORDER_1, SORT_ORDER_2 });
-    when(backendCfg.isSubordinateIndexesEnabled()).thenReturn(true);
 
     when(backendCfg.getDBDirectory()).thenReturn(BACKEND_NAME);
     when(backendCfg.getDBDirectoryPermissions()).thenReturn("755");
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/DN2IDTest.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/DN2IDTest.java
new file mode 100644
index 0000000..d594d94
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/DN2IDTest.java
@@ -0,0 +1,296 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2015 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.PromiseImpl;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.admin.std.meta.BackendIndexCfgDefn.IndexType;
+import org.opends.server.admin.std.server.BackendIndexCfg;
+import org.opends.server.admin.std.server.PersistitBackendCfg;
+import org.opends.server.backends.persistit.PersistItStorage;
+import org.opends.server.backends.pluggable.spi.ReadOperation;
+import org.opends.server.backends.pluggable.spi.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.SequentialCursor;
+import org.opends.server.backends.pluggable.spi.TreeName;
+import org.opends.server.backends.pluggable.spi.WriteOperation;
+import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.MemoryQuota;
+import org.opends.server.core.ServerContext;
+import org.opends.server.extensions.DiskSpaceMonitor;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(groups = { "precommit", "pluggablebackend" }, sequential = true)
+public class DN2IDTest extends DirectoryServerTestCase
+{
+  private final TreeName dn2IDTreeName = new TreeName("base-dn", "index-id");
+  private DN baseDN;
+  private DN2ID dn2ID;
+  private PersistItStorage storage;
+
+  @BeforeClass
+  public void startFakeServer() throws Exception
+  {
+    TestCaseUtils.startFakeServer();
+  }
+
+  @AfterClass
+  public void stopFakeServer() throws Exception
+  {
+    TestCaseUtils.shutdownFakeServer();
+  }
+
+  @BeforeMethod
+  public void setUp() throws Exception
+  {
+    ServerContext serverContext = mock(ServerContext.class);
+    when(serverContext.getMemoryQuota()).thenReturn(new MemoryQuota());
+    when(serverContext.getDiskSpaceMonitor()).thenReturn(mock(DiskSpaceMonitor.class));
+
+    storage = new PersistItStorage(createBackendCfg(), serverContext);
+    try(final org.opends.server.backends.pluggable.spi.Importer importer = storage.startImport()) {
+      importer.createTree(dn2IDTreeName);
+    }
+
+    storage.open();
+
+    baseDN = dn("dc=example, dc=com");
+    dn2ID = new DN2ID(dn2IDTreeName, baseDN);
+  }
+
+  @AfterMethod
+  public void tearDown()
+  {
+    storage.close();
+    storage.removeStorageFiles();
+  }
+
+  private void populate() throws DirectoryException, Exception
+  {
+    final String[] dns =
+      {
+                                 "dc=example,dc=com",
+                      "ou=Devices,dc=example,dc=com",
+              "cn=dev0,ou=Devices,dc=example,dc=com",
+                       "ou=People,dc=example,dc=com",
+                "cn=foo,ou=People,dc=example,dc=com",
+             "cn=barbar,ou=People,dc=example,dc=com",
+             "cn=foofoo,ou=People,dc=example,dc=com",
+                "cn=bar,ou=People,dc=example,dc=com",
+        "cn=dev0,cn=bar,ou=People,dc=example,dc=com",
+        "cn=dev1,cn=bar,ou=People,dc=example,dc=com"
+      };
+
+    for (int i = 0; i < dns.length; i++)
+    {
+      put(dn(dns[i]), i + 1);
+    }
+  }
+
+  @Test
+  public void testCanAddDN() throws Exception
+  {
+    populate();
+
+    assertThat(get("dc=example,dc=com")).isEqualTo(id(1));
+    assertThat(get("ou=People,dc=example,dc=com")).isEqualTo(id(4));
+    assertThat(get("cn=dev1,cn=bar,ou=People,dc=example,dc=com")).isEqualTo(id(10));
+  }
+
+  @Test
+  public void testGetNonExistingDNReturnNull() throws Exception
+  {
+    assertThat(get("dc=non,dc=existing")).isNull();
+  }
+
+  @Test
+  public void testCanRemove() throws Exception
+  {
+    populate();
+
+    assertThat(get("ou=People,dc=example,dc=com")).isNotNull();
+    assertThat(remove("ou=People,dc=example,dc=com")).isTrue();
+    assertThat(get("ou=People,dc=example,dc=com")).isNull();
+  }
+
+  @Test
+  public void testRemoveNonExistingEntry() throws Exception
+  {
+    assertThat(remove("dc=non,dc=existing")).isFalse();
+  }
+
+  @Test
+  public void testTraverseChildren() throws Exception
+  {
+    populate();
+    assertThat(traverseChildren("ou=People,dc=example,dc=com"))
+      .containsExactly(
+                       get("cn=bar,ou=People,dc=example,dc=com"),
+                    get("cn=barbar,ou=People,dc=example,dc=com"),
+                       get("cn=foo,ou=People,dc=example,dc=com"),
+                    get("cn=foofoo,ou=People,dc=example,dc=com"));
+  }
+
+  @Test
+  public void testTraverseSubordinates() throws Exception
+  {
+    populate();
+    assertThat(traverseSubordinates("ou=People,dc=example,dc=com"))
+      .containsExactly(
+                      get("cn=bar,ou=People,dc=example,dc=com"),
+              get("cn=dev0,cn=bar,ou=People,dc=example,dc=com"),
+              get("cn=dev1,cn=bar,ou=People,dc=example,dc=com"),
+                   get("cn=barbar,ou=People,dc=example,dc=com"),
+                      get("cn=foo,ou=People,dc=example,dc=com"),
+                   get("cn=foofoo,ou=People,dc=example,dc=com"));
+  }
+
+  private EntryID get(final String dn) throws Exception
+  {
+    return storage.read(new ReadOperation<EntryID>()
+    {
+      @Override
+      public EntryID run(ReadableTransaction txn) throws Exception
+      {
+        return dn2ID.get(txn, dn(dn));
+      }
+    });
+  }
+
+  private List<EntryID> traverseChildren(final String dn) throws Exception
+  {
+    return storage.read(new ReadOperation<List<EntryID>>()
+    {
+      @Override
+      public List<EntryID> run(ReadableTransaction txn) throws Exception
+      {
+        try (final SequentialCursor<Void, EntryID> cursor = dn2ID.openChildrenCursor(txn, dn(dn)))
+        {
+          return getAllIDs(cursor);
+        }
+      }
+    });
+  }
+
+  private List<EntryID> traverseSubordinates(final String dn) throws Exception
+  {
+    return storage.read(new ReadOperation<List<EntryID>>()
+    {
+      @Override
+      public List<EntryID> run(ReadableTransaction txn) throws Exception
+      {
+        try (final SequentialCursor<Void, EntryID> cursor = dn2ID.openSubordinatesCursor(txn, dn(dn)))
+        {
+          return getAllIDs(cursor);
+        }
+      }
+    });
+  }
+
+  private static <K, V> List<V> getAllIDs(SequentialCursor<K, V> cursor) {
+    final List<V> values = new ArrayList<>();
+    while(cursor.next()) {
+      values.add(cursor.getValue());
+    }
+    return values;
+  }
+
+  private void put(final DN dn, final long id) throws Exception
+  {
+    storage.write(new WriteOperation()
+    {
+      @Override
+      public void run(WriteableTransaction txn) throws Exception
+      {
+        dn2ID.put(txn, dn, new EntryID(id));
+      }
+    });
+  }
+
+  private boolean remove(final String dn) throws Exception
+  {
+    final PromiseImpl<Boolean, NeverThrowsException> p = PromiseImpl.create();
+    storage.write(new WriteOperation()
+    {
+      @Override
+      public void run(WriteableTransaction txn) throws Exception
+      {
+        p.handleResult(dn2ID.remove(txn, dn(dn)));
+      }
+    });
+    return p.get(10, TimeUnit.SECONDS);
+  }
+
+  private static DN dn(String dn) throws DirectoryException
+  {
+    return DN.valueOf(dn);
+  }
+
+  private static EntryID id(long id)
+  {
+    return new EntryID(id);
+  }
+
+  private static PersistitBackendCfg createBackendCfg() throws ConfigException, DirectoryException
+  {
+    String homeDirName = "pdb_test";
+    PersistitBackendCfg backendCfg = mock(PersistitBackendCfg.class);
+
+    when(backendCfg.getBackendId()).thenReturn("persTest" + homeDirName);
+    when(backendCfg.getDBDirectory()).thenReturn(homeDirName);
+    when(backendCfg.getDBDirectoryPermissions()).thenReturn("755");
+    when(backendCfg.getDBCacheSize()).thenReturn(0L);
+    when(backendCfg.getDBCachePercent()).thenReturn(20);
+    when(backendCfg.getBaseDN()).thenReturn(TestCaseUtils.newSortedSet(DN.valueOf("dc=test,dc=com")));
+    when(backendCfg.dn()).thenReturn(DN.valueOf("dc=test,dc=com"));
+    when(backendCfg.listBackendIndexes()).thenReturn(new String[] { "sn" });
+    when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[0]);
+
+    BackendIndexCfg indexCfg = mock(BackendIndexCfg.class);
+    when(indexCfg.getIndexType()).thenReturn(TestCaseUtils.newSortedSet(IndexType.PRESENCE, IndexType.EQUALITY));
+    when(indexCfg.getAttribute()).thenReturn(DirectoryServer.getAttributeType("sn"));
+    when(backendCfg.getBackendIndex("sn")).thenReturn(indexCfg);
+
+    return backendCfg;
+  }
+
+}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/EntryIDSetTest.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/EntryIDSetTest.java
index 1b0f53d..a02bffe 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/EntryIDSetTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/EntryIDSetTest.java
@@ -39,8 +39,6 @@
 @Test(groups = { "precommit", "pluggablebackend", "unit" }, sequential=true)
 public class EntryIDSetTest extends DirectoryServerTestCase
 {
-  private static final int UNDEFINED_INITIAL_SIZE = 10;
-
   private final static ByteString KEY = ByteString.valueOf("test");
 
   @Test(expectedExceptions = NullPointerException.class)
@@ -183,9 +181,9 @@
     assertThat(codec.decode(KEY, string).isDefined()).isFalse();
     assertThat(codec.decode(KEY, string).size()).isEqualTo(Long.MAX_VALUE);
 
-    string = codec.encode(newUndefinedSetWithSize(ByteString.valueOf("none"), 1234));
+    string = codec.encode(newUndefinedSetWithKey(ByteString.valueOf("none")));
     assertThat(codec.decode(KEY, string).isDefined()).isFalse();
-    assertThat(codec.decode(KEY, string).size()).isEqualTo(1234);
+    assertThat(codec.decode(KEY, string).size()).isEqualTo(Long.MAX_VALUE);
   }
 
   @Test(enabled = false, dataProvider = "codec")
@@ -203,82 +201,61 @@
   @Test(expectedExceptions = NullPointerException.class)
   public void testUndefinedCannotCreateWithNull()
   {
-    newUndefinedSetWithSize(null, 1);
+    newUndefinedSetWithKey(null);
   }
 
   @Test
-  public void testUndefinedAdd()
+  public void testUndefinedAddDoesNothing()
   {
-    EntryIDSet undefined = newUndefinedWithInitialSize();
-
+    final EntryIDSet undefined = newUndefinedSet();
     assertThat(undefined.add(new EntryID(4))).isTrue();
-    assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE + 1);
+    assertThat(undefined.size()).isEqualTo(Long.MAX_VALUE);
   }
 
   @Test
-  public void testUndefinedAddAll()
+  public void testUndefinedAddAllDoesNothing()
   {
-    EntryIDSet undefined = newUndefinedWithInitialSize();
+    final EntryIDSet undefined = newUndefinedSet();
 
     undefined.addAll(newDefinedSet());
-    assertThat(newUndefinedWithInitialSize().size()).isEqualTo(UNDEFINED_INITIAL_SIZE);
+    assertThat(undefined.size()).isEqualTo(Long.MAX_VALUE);
 
     undefined.addAll(newDefinedSet(2, 4, 6));
-    assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE + 3);
+    assertThat(undefined.size()).isEqualTo(Long.MAX_VALUE);
   }
 
   @Test
-  public void testUndefinedRemove()
+  public void testUndefinedRemoveDoesNothing()
   {
-    EntryIDSet undefined = newUndefinedWithInitialSize();
-
+    final EntryIDSet undefined = newUndefinedSet();
     assertThat(undefined.remove(new EntryID(4))).isTrue();
-    assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE - 1);
+    assertThat(undefined.size()).isEqualTo(Long.MAX_VALUE);
   }
 
   @Test
-  public void testUndefinedRemoveUnderflow()
+  public void testUndefinedDeleteAllDoesNothing()
   {
-    EntryIDSet undefined = newUndefinedSetWithSize(ByteString.valueOf("test"), 0);
-
-    assertThat(undefined.remove(new EntryID(4))).isTrue();
-    assertThat(undefined.size()).isEqualTo(0);
-  }
-
-  @Test
-  public void testUndefinedDeleteAll()
-  {
-    EntryIDSet undefined = newUndefinedWithInitialSize();
-
+    final EntryIDSet undefined = newUndefinedSet();
     undefined.removeAll(newDefinedSet(20, 21, 22));
-    assertThat(undefined.size()).isEqualTo(UNDEFINED_INITIAL_SIZE - 3);
-  }
-
-  @Test
-  public void testUndefinedDeleteAllUnderflow()
-  {
-    EntryIDSet undefined = newUndefinedSetWithSize(ByteString.valueOf("test"), 0);
-
-    undefined.removeAll(newDefinedSet(20, 21, 22));
-    assertThat(undefined.size()).isEqualTo(0);
+    assertThat(undefined.size()).isEqualTo(Long.MAX_VALUE);
   }
 
   @Test
   public void testUndefinedContain()
   {
-    assertThat(newUndefinedWithInitialSize().contains(new EntryID(4))).isTrue();
+    assertThat(newUndefinedSet().contains(new EntryID(4))).isTrue();
   }
 
   @Test
   public void testUndefinedIterator()
   {
-    assertThat(newUndefinedWithInitialSize().iterator().hasNext()).isFalse();
+    assertThat(newUndefinedSet().iterator().hasNext()).isFalse();
   }
 
   @Test
   public void testUndefinedIteratorWithBegin()
   {
-    assertThat(newUndefinedWithInitialSize().iterator(new EntryID(8)).hasNext()).isFalse();
+    assertThat(newUndefinedSet().iterator(new EntryID(8)).hasNext()).isFalse();
   }
 
   @Test
@@ -304,9 +281,6 @@
     assertThat(newUndefinedSet().isDefined()).isFalse();
     assertThat(newUndefinedSetWithKey(KEY).isDefined()).isFalse();
     assertThat(newUndefinedSetWithKey(KEY).size()).isEqualTo(Long.MAX_VALUE);
-
-    assertThat(newUndefinedSetWithSize(KEY, 42).isDefined()).isFalse();
-    assertThat(newUndefinedSetWithSize(KEY, 42).size()).isEqualTo(42);
   }
 
   @Test
@@ -342,11 +316,6 @@
     assertIdsEquals(retained, 1, 3, 5, 7, 9);
   }
 
-  private static EntryIDSet newUndefinedWithInitialSize()
-  {
-    return newUndefinedSetWithSize(ByteString.valueOf("test"), UNDEFINED_INITIAL_SIZE);
-  }
-
   @DataProvider(name = "codecs")
   public static Object[][] codecs() {
      return new Object[][] { { CODEC_V1 }, { CODEC_V2 } };
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ID2CountTest.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ID2CountTest.java
new file mode 100644
index 0000000..827ea14
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/ID2CountTest.java
@@ -0,0 +1,253 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2015 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.PromiseImpl;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.admin.std.meta.BackendIndexCfgDefn.IndexType;
+import org.opends.server.admin.std.server.BackendIndexCfg;
+import org.opends.server.admin.std.server.PersistitBackendCfg;
+import org.opends.server.backends.persistit.PersistItStorage;
+import org.opends.server.backends.pluggable.spi.ReadOperation;
+import org.opends.server.backends.pluggable.spi.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.TreeName;
+import org.opends.server.backends.pluggable.spi.WriteOperation;
+import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.MemoryQuota;
+import org.opends.server.core.ServerContext;
+import org.opends.server.extensions.DiskSpaceMonitor;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(groups = { "precommit", "pluggablebackend" }, sequential = true)
+public class ID2CountTest extends DirectoryServerTestCase
+{
+  private final TreeName id2CountTreeName = new TreeName("base-dn", "index-id");
+  private ExecutorService parallelExecutor;
+  private ID2Count id2Count;
+  private PersistItStorage storage;
+
+  @BeforeClass
+  public void startFakeServer() throws Exception {
+    TestCaseUtils.startFakeServer();
+  }
+
+  @AfterClass
+  public void stopFakeServer() throws Exception {
+    TestCaseUtils.shutdownFakeServer();
+  }
+
+  @BeforeMethod
+  public void setUp() throws Exception
+  {
+    ServerContext serverContext = mock(ServerContext.class);
+    when(serverContext.getMemoryQuota()).thenReturn(new MemoryQuota());
+    when(serverContext.getDiskSpaceMonitor()).thenReturn(mock(DiskSpaceMonitor.class));
+
+    storage = new PersistItStorage(createBackendCfg(), serverContext);
+    org.opends.server.backends.pluggable.spi.Importer importer = storage.startImport();
+    importer.createTree(id2CountTreeName);
+    importer.close();
+
+    storage.open();
+
+    id2Count = new ID2Count(id2CountTreeName);
+
+    parallelExecutor = Executors.newFixedThreadPool(32);
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    storage.close();
+    storage.removeStorageFiles();
+  }
+
+  @Test
+  public void testConcurrentAddDelta() throws Exception
+  {
+    final long expected = stressCounter(8192, id(1), parallelExecutor);
+    waitExecutorTermination();
+
+    assertThat(getCounter(id(1))).isEqualTo(expected);
+    assertThat(getTotalCounter()).isEqualTo(expected);
+  }
+
+  @Test
+  public void testConcurrentTotalCounter() throws Exception
+  {
+    long totalExpected = 0;
+    for(int i = 0 ; i < 64 ; i++) {
+      totalExpected += stressCounter(128, id(i), parallelExecutor);
+    }
+    waitExecutorTermination();
+
+    assertThat(getTotalCounter()).isEqualTo(totalExpected);
+  }
+
+  @Test
+  public void testDeleteCounterDecrementTotalCounter() throws Exception
+  {
+    addDelta(id(0), 1024);
+    addDelta(id(1), 1024);
+    addDelta(id(2), 1024);
+    addDelta(id(3), 1024);
+    assertThat(getTotalCounter()).isEqualTo(4096);
+
+    assertThat(deleteCount(id(0))).isEqualTo(1024);
+    assertThat(getTotalCounter()).isEqualTo(3072);
+
+    assertThat(deleteCount(id(1))).isEqualTo(1024);
+    assertThat(deleteCount(id(2))).isEqualTo(1024);
+    assertThat(deleteCount(id(3))).isEqualTo(1024);
+    assertThat(getTotalCounter()).isEqualTo(0);
+  }
+
+  @Test
+  public void testGetCounterNonExistingKey() throws Exception
+  {
+      assertThat(getCounter(id(987654))).isEqualTo(0);
+  }
+
+  private void waitExecutorTermination() throws InterruptedException
+  {
+    parallelExecutor.shutdown();
+    parallelExecutor.awaitTermination(30, TimeUnit.SECONDS);
+  }
+
+  private long stressCounter(final int numIterations, final EntryID key, final ExecutorService exec)
+  {
+    final Random r = new Random();
+    long expected = 0;
+    for(int i = 0 ; i < numIterations ; i++) {
+      final long delta = r.nextLong();
+      expected += delta;
+
+      exec.submit(new Callable<Void>()
+      {
+        @Override
+        public Void call() throws Exception
+        {
+          addDelta(key, delta);
+          return null;
+        }
+      });
+    }
+    return expected;
+  }
+
+  private long deleteCount(final EntryID key) throws Exception {
+    final PromiseImpl<Long, NeverThrowsException> l = PromiseImpl.create();
+    storage.write(new WriteOperation()
+    {
+      @Override
+      public void run(WriteableTransaction txn) throws Exception
+      {
+        l.handleResult(id2Count.deleteCount(txn, key));
+      }
+    });
+    return l.get();
+  }
+
+  private void addDelta(final EntryID key, final long delta) throws Exception {
+    storage.write(new WriteOperation()
+    {
+      @Override
+      public void run(WriteableTransaction txn) throws Exception
+      {
+        id2Count.addDelta(txn, key, delta);
+      }
+    });
+  }
+
+  private long getCounter(final EntryID key) throws Exception {
+    return storage.read(new ReadOperation<Long>()
+    {
+      @Override
+      public Long run(ReadableTransaction txn) throws Exception
+      {
+        return id2Count.getCount(txn, key);
+      }
+    });
+  }
+
+  private long getTotalCounter() throws Exception {
+    return storage.read(new ReadOperation<Long>()
+    {
+      @Override
+      public Long run(ReadableTransaction txn) throws Exception
+      {
+        return id2Count.getTotalCount(txn);
+      }
+    });
+  }
+
+  public static EntryID id(long id) {
+    return new EntryID(id);
+  }
+
+  private PersistitBackendCfg createBackendCfg() throws ConfigException, DirectoryException
+  {
+    String homeDirName = "pdb_test";
+    PersistitBackendCfg backendCfg = mock(PersistitBackendCfg.class);
+
+    when(backendCfg.getBackendId()).thenReturn("persTest" + homeDirName);
+    when(backendCfg.getDBDirectory()).thenReturn(homeDirName);
+    when(backendCfg.getDBDirectoryPermissions()).thenReturn("755");
+    when(backendCfg.getDBCacheSize()).thenReturn(0L);
+    when(backendCfg.getDBCachePercent()).thenReturn(20);
+    when(backendCfg.getBaseDN()).thenReturn(TestCaseUtils.newSortedSet(DN.valueOf("dc=test,dc=com")));
+    when(backendCfg.dn()).thenReturn(DN.valueOf("dc=test,dc=com"));
+    when(backendCfg.listBackendIndexes()).thenReturn(new String[] { "sn" });
+    when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[0]);
+
+    BackendIndexCfg indexCfg = mock(BackendIndexCfg.class);
+    when(indexCfg.getIndexType()).thenReturn(TestCaseUtils.newSortedSet(IndexType.PRESENCE, IndexType.EQUALITY));
+    when(indexCfg.getAttribute()).thenReturn(DirectoryServer.getAttributeType("sn"));
+    when(backendCfg.getBackendIndex("sn")).thenReturn(indexCfg);
+
+    return backendCfg;
+  }
+
+}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
index f085aa5..0f0310a 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
@@ -137,7 +137,6 @@
     when(backendCfg.getBaseDN()).thenReturn(newSortedSet(testBaseDN));
     when(backendCfg.listBackendIndexes()).thenReturn(backendIndexes);
     when(backendCfg.listBackendVLVIndexes()).thenReturn(backendVlvIndexes);
-    when(backendCfg.isSubordinateIndexesEnabled()).thenReturn(true);
 
     BackendIndexCfg indexCfg = mock(BackendIndexCfg.class);
     when(indexCfg.getIndexType()).thenReturn(newSortedSet(IndexType.PRESENCE, IndexType.EQUALITY));
@@ -653,8 +652,8 @@
     assertEquals(backend.hasSubordinates(DN.valueOf("dc=a")), ConditionResult.UNDEFINED,
         "Subordinates query on unknown baseDN should return UNDEFINED.");
 
-    assertEquals(backend.numSubordinates(testBaseDN, false), 1);
-    assertEquals(backend.numSubordinates(testBaseDN, true), getTotalNumberOfLDIFEntries() - 1, "Wrong DIT count.");
+    assertEquals(backend.getNumberOfChildren(testBaseDN), 1);
+    assertEquals(backend.getNumberOfEntriesInBaseDN(testBaseDN), getTotalNumberOfLDIFEntries(), "Wrong DIT count.");
     assertEquals(backend.hasSubordinates(searchDN), ConditionResult.FALSE,
         "Leaf entry should not have any subordinates.");
   }
@@ -855,6 +854,11 @@
 
     backend.openBackend();
     assertEquals(backend.getEntryCount(), ldifNumberOfEntries, "Not enough entries in DIT.");
+    /** +1 for the testBaseDN itself */
+    assertEquals(backend.getNumberOfEntriesInBaseDN(testBaseDN), ldifNumberOfEntries, "Not enough entries in DIT.");
+    assertEquals(backend.getNumberOfChildren(testBaseDN), 1, "Not enough entries in DIT.");
+    /** -2 for baseDn and People entry */
+    assertEquals(backend.getNumberOfChildren(testBaseDN.child(DN.valueOf("ou=People"))), ldifNumberOfEntries - 2, "Not enough entries in DIT.");
   }
 
   @Test(dependsOnMethods = {"testImportLDIF"})
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/StateTest.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/StateTest.java
index f97d9a7..345194e 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/StateTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/StateTest.java
@@ -50,6 +50,7 @@
 import org.opends.server.extensions.DiskSpaceMonitor;
 import org.opends.server.types.DN;
 import org.opends.server.types.DirectoryException;
+import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -67,7 +68,12 @@
 
   @BeforeClass
   public void startServer() throws Exception {
-    TestCaseUtils.startServer();
+    TestCaseUtils.startFakeServer();
+  }
+
+  @AfterClass
+  public void stopServer() throws Exception {
+    TestCaseUtils.shutdownFakeServer();
   }
 
   @BeforeMethod
@@ -77,7 +83,7 @@
 
     ServerContext serverContext = mock(ServerContext.class);
     when(serverContext.getMemoryQuota()).thenReturn(new MemoryQuota());
-    when(serverContext.getDiskSpaceMonitor()).thenReturn(new DiskSpaceMonitor());
+    when(serverContext.getDiskSpaceMonitor()).thenReturn(mock(DiskSpaceMonitor.class));
 
     storage = new PersistItStorage(createBackendCfg(), serverContext);
     org.opends.server.backends.pluggable.spi.Importer importer = storage.startImport();
@@ -180,7 +186,6 @@
     when(backendCfg.getDBDirectoryPermissions()).thenReturn("755");
     when(backendCfg.getDBCacheSize()).thenReturn(0L);
     when(backendCfg.getDBCachePercent()).thenReturn(20);
-    when(backendCfg.isSubordinateIndexesEnabled()).thenReturn(true);
     when(backendCfg.getBaseDN()).thenReturn(TestCaseUtils.newSortedSet(DN.valueOf("dc=test,dc=com")));
     when(backendCfg.dn()).thenReturn(DN.valueOf("dc=test,dc=com"));
     when(backendCfg.listBackendIndexes()).thenReturn(new String[] { "sn" });
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/Utils.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/Utils.java
index 96de7a8..0ea72da 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/Utils.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/Utils.java
@@ -39,8 +39,15 @@
     assertThat(actual).containsAll(asList(expected));
   }
 
+  public static void assertIsEmpty(EntryIDSet actual)
+  {
+    assertIdsEquals(actual);
+  }
+
   public static void assertIdsEquals(EntryIDSet actual, long... expected)
   {
+    // needed is undefined EntryIDSet" => "needed since undefined EntryIDSet
+    assertThat(actual.isDefined());
     assertIdsEquals(actual.iterator(), expected);
   }
 
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/task/TaskBackendTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/task/TaskBackendTestCase.java
index 3a2e45d..c48ca05 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/task/TaskBackendTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/task/TaskBackendTestCase.java
@@ -493,14 +493,12 @@
 
     TaskBackend taskBackend =
       (TaskBackend) DirectoryServer.getBackend(DN.valueOf("cn=tasks"));
-    long tasksCountBefore = taskBackend.numSubordinates(DN.valueOf(
-      "cn=Scheduled Tasks,cn=tasks"), true);
+    long tasksCountBefore = taskBackend.getNumberOfEntriesInBaseDN(DN.valueOf("cn=Scheduled Tasks,cn=tasks"));
 
     assertTrue(addRecurringTask(taskID, taskSchedule));
 
     // Make sure recurring task iteration got scheduled.
-    long tasksCountAfter = taskBackend.numSubordinates(DN.valueOf(
-      "cn=Scheduled Tasks,cn=tasks"), true);
+    long tasksCountAfter = taskBackend.getNumberOfEntriesInBaseDN(DN.valueOf("cn=Scheduled Tasks,cn=tasks"));
     assertEquals(tasksCountAfter, tasksCountBefore + 1);
 
     // Perform a modification to update a non-state attribute.
@@ -519,8 +517,7 @@
     assertFalse(DirectoryServer.entryExists(DN.valueOf(taskDN)));
 
     // Make sure recurring task iteration got canceled and removed.
-    tasksCountAfter = taskBackend.numSubordinates(DN.valueOf(
-      "cn=Scheduled Tasks,cn=tasks"), true);
+    tasksCountAfter = taskBackend.getNumberOfEntriesInBaseDN(DN.valueOf("cn=Scheduled Tasks,cn=tasks"));
     assertEquals(tasksCountAfter, tasksCountBefore);
   }
 

--
Gitblit v1.10.0