You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by am...@apache.org on 2022/12/02 05:47:37 UTC

[jackrabbit-oak] branch trunk updated: OAK-9975: [DSGC] Report cummulative size of referenced blobs during Mark phase (#771)

This is an automated email from the ASF dual-hosted git repository.

amitj pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 146ea2e604 OAK-9975: [DSGC] Report cummulative size of referenced blobs during Mark phase (#771)
146ea2e604 is described below

commit 146ea2e604927215d4cdb9137e60c752bd8c3393
Author: Amit Jain <am...@apache.org>
AuthorDate: Fri Dec 2 11:17:30 2022 +0530

    OAK-9975: [DSGC] Report cummulative size of referenced blobs during Mark phase (#771)
    
    - Update size metrics in checkConsistency & mark
---
 .../plugins/blob/MarkSweepGarbageCollector.java    |  86 +++++++++++++++--
 .../oak/plugins/blob/OperationStatsCollector.java  |  20 ++++
 .../oak/plugins/blob/OperationsStatsMBean.java     |  12 +++
 .../jackrabbit/oak/plugins/blob/BlobGCTest.java    | 102 ++++++++++++++++-----
 4 files changed, 190 insertions(+), 30 deletions(-)

diff --git a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/MarkSweepGarbageCollector.java b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/MarkSweepGarbageCollector.java
index 085f156c46..d1361e0b52 100644
--- a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/MarkSweepGarbageCollector.java
+++ b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/MarkSweepGarbageCollector.java
@@ -405,13 +405,35 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
 
         // Mark all used references
         iterateNodeTree(fs, false);
-
+        
+        // Get size
+        getBlobReferencesSize(fs, stats);
+        
         // Move the marked references file to the data store meta area if applicable
         GarbageCollectionType.get(blobStore).addMarked(blobStore, fs, repoId, uniqueSuffix);
 
         LOG.debug("Ending mark phase of the garbage collector");
     }
 
+    private static void getBlobReferencesSize(GarbageCollectorFileState fs, GarbageCollectionOperationStats stats)
+        throws IOException {
+        try (LineIterator lineIterator = new LineIterator(new FileReader(fs.getMarkedRefs()))) {
+            lineIterator.forEachRemaining(line -> {
+                String id = line.split(DELIM)[0];
+                long length = DataStoreBlobStore.BlobId.of(id).getLength();
+                LOG.info("Blob {} has size {}", id, length);
+
+                stats.getCollector().updateNumBlobReferences(1);
+
+                if (length != -1) {
+                    stats.getCollector().updateBlobReferencesSize(length);
+                }
+            });
+        }
+        LOG.info("Blob references found : {} with size : {}", stats.getNumBlobReferences(),
+            stats.getBlobReferencesSize());
+    }
+
     /**
      * Difference phase where the GC candidates are identified.
      *
@@ -526,7 +548,7 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
         }
 
         if (checkConsistencyAfterGc) {
-            BlobCollectionType.get(blobStore).checkConsistencyAfterGC(blobStore, fs, consistencyStatsCollector, new File(root));
+            BlobCollectionType.get(blobStore).checkConsistencyAfterGC(blobStore, fs, consistencyStats);
         }
         BlobCollectionType.get(blobStore).handleRemoves(blobStore, fs.getGarbage(), fs.getMarkedRefs());
 
@@ -689,6 +711,9 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
 
             // Mark all used blob references
             iterateNodeTree(fs, true);
+            // Move the marked references file to the data store meta area if applicable
+            String uniqueSuffix = UUID.randomUUID().toString();
+            GarbageCollectionType.get(blobStore).addMarked(blobStore, fs, repoId, uniqueSuffix);
             consistencyStatsCollector.updateMarkDuration(sw.elapsed(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
 
             try {
@@ -703,6 +728,21 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
                 // Retrieve all other marked present in the datastore
                 List<DataRecord> refFiles =
                     ((SharedDataStore) blobStore).getAllMetadataRecords(SharedStoreRecordType.REFERENCES.getType());
+
+                // Get all the repositories registered
+                List<DataRecord> repoFiles =
+                    ((SharedDataStore) blobStore).getAllMetadataRecords(SharedStoreRecordType.REPOSITORY.getType());
+                LOG.info("Repositories registered {}", repoFiles);
+
+                // Retrieve repos for which reference files have not been created
+                Set<String> unAvailRepos =
+                    SharedDataStoreUtils.refsNotAvailableFromRepos(repoFiles, refFiles);
+                LOG.info("Repositories with unavailable references {}", unAvailRepos);
+                
+                if (!unAvailRepos.isEmpty()) {
+                    throw new NotAllRepositoryMarkedException("Not all repositories have marked references available");
+                }
+                
                 if (refFiles.size() > 0) {
                     File temp = new File(root, repoId + UUID.randomUUID().toString());
                     copyFile(fs.getMarkedRefs(), temp);
@@ -718,6 +758,9 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
                 }
             }
 
+            // Get size
+            getBlobReferencesSize(fs, consistencyStats);
+
             LOG.trace("Starting difference phase of the consistency check");
             FileLineDifferenceIterator iter = new FileLineDifferenceIterator(
                 fs.getAvailableRefs(),
@@ -1076,8 +1119,8 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
         }
 
         void checkConsistencyAfterGC(GarbageCollectableBlobStore blobStore, GarbageCollectorFileState fs,
-            OperationStatsCollector consistencyStatsCollector, File root) throws IOException {
-            consistencyStatsCollector.start();
+            GarbageCollectionOperationStats stats) throws IOException {
+            stats.getCollector().start();
             Stopwatch sw = Stopwatch.createStarted();
 
             try {
@@ -1111,12 +1154,15 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
                     }
                     LOG.warn(
                         "Consistency check failure in the the blob store after GC : {}", blobStore);
-                    consistencyStatsCollector.finishFailure();
-                    consistencyStatsCollector.updateNumDeleted(candidates);
+                    stats.getCollector().finishFailure();
+                    stats.getCollector().updateNumDeleted(candidates);
                 }
+                
+                // Update the size of the referenced blobs
+                getBlobReferencesSize(fs, stats);
             } finally {
                 sw.stop();
-                consistencyStatsCollector.updateDuration(sw.elapsed(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
+                stats.getCollector().updateDuration(sw.elapsed(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
             }
         }
 
@@ -1156,6 +1202,10 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
         static final String NUM_BLOBS_DELETED = "NUM_BLOBS_DELETED";
         static final String TOTAL_SIZE_DELETED = "TOTAL_SIZE_DELETED";
         static final String NUM_CANDIDATES = "NUM_CANDIDATES";
+        
+        static final String NUM_BLOB_REFERENCES = "NUM_BLOB_REFERENCES";
+
+        static final String BLOB_REFERENCES_SIZE = "BLOB_REFERENCES_SIZE";
 
         private final String typeName;
 
@@ -1164,6 +1214,8 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
         private CounterStats numDeletedCounter;
         private CounterStats totalSizeDeletedCounter;
         private CounterStats numCandidatesCounter;
+        private CounterStats numBlobReferencesCounter;
+        private CounterStats blobReferencesSizeCounter;
         private TimerStats duration;
         private final TimerStats markDuration;
         private final TimerStats sweepDuration;
@@ -1177,6 +1229,8 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
             this.numDeletedCounter = sp.getCounterStats(getMetricName(NUM_BLOBS_DELETED), StatsOptions.METRICS_ONLY);
             this.totalSizeDeletedCounter = sp.getCounterStats(getMetricName(TOTAL_SIZE_DELETED), StatsOptions.METRICS_ONLY);
             this.numCandidatesCounter = sp.getCounterStats(getMetricName(NUM_CANDIDATES), StatsOptions.METRICS_ONLY);
+            this.numBlobReferencesCounter = sp.getCounterStats(getMetricName(NUM_BLOB_REFERENCES), StatsOptions.METRICS_ONLY);
+            this.blobReferencesSizeCounter = sp.getCounterStats(getMetricName(BLOB_REFERENCES_SIZE), StatsOptions.METRICS_ONLY);
             this.duration = sp.getTimer(getMetricName(DURATION), StatsOptions.METRICS_ONLY);
             this.markDuration = sp.getTimer(getMetricName(MARK_DURATION), StatsOptions.METRICS_ONLY);
             this.sweepDuration = sp.getTimer(getMetricName(SWEEP_DURATION), StatsOptions.METRICS_ONLY);
@@ -1207,6 +1261,16 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
                     totalSizeDeletedCounter.inc(size);
                 }
 
+                @Override
+                public void updateNumBlobReferences(long num) {
+                    numBlobReferencesCounter.inc(num);
+                }
+
+                @Override
+                public void updateBlobReferencesSize(long size) {
+                    blobReferencesSizeCounter.inc(size);
+                }
+                
                 @Override
                 public void updateDuration(long time, TimeUnit timeUnit) {
                     duration.update(time, timeUnit);
@@ -1262,6 +1326,14 @@ public class MarkSweepGarbageCollector implements BlobGarbageCollector {
         @Override public long sizeDeleted() {
             return totalSizeDeletedCounter.getCount();
         }
+
+        @Override public long getNumBlobReferences() {
+            return numBlobReferencesCounter.getCount();
+        }
+
+        @Override public long getBlobReferencesSize() {
+            return blobReferencesSizeCounter.getCount();
+        }
     }
 
 
diff --git a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationStatsCollector.java b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationStatsCollector.java
index 677c0b8413..1998bbac2a 100644
--- a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationStatsCollector.java
+++ b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationStatsCollector.java
@@ -41,6 +41,14 @@ public interface OperationStatsCollector {
         @Override public void updateTotalSizeDeleted(long size) {
         }
 
+        @Override
+        public void updateNumBlobReferences(long num) {
+        }
+
+        @Override
+        public void updateBlobReferencesSize(long size) {
+        }
+
         @Override public void updateDuration(long time, TimeUnit timeUnit) {
         }
 
@@ -79,6 +87,18 @@ public interface OperationStatsCollector {
      */
     void updateTotalSizeDeleted(long size);
 
+    /**
+     * Update the number of blob references
+     * @param num
+     */
+    public void updateNumBlobReferences(long num);
+
+    /**
+     * Update the size of blob references
+     * @param size
+     */
+    public void updateBlobReferencesSize(long size);
+    
     /**
      * Increment the duration timer
      *
diff --git a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationsStatsMBean.java b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationsStatsMBean.java
index 5d6796bbbc..2669dabd00 100644
--- a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationsStatsMBean.java
+++ b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/OperationsStatsMBean.java
@@ -65,4 +65,16 @@ public interface OperationsStatsMBean {
      * @return
      */
     long sizeDeleted();
+
+    /**
+     * Returns the number of blob references
+     * @return
+     */
+    long getNumBlobReferences();
+
+    /**
+     * Returns the cumulative size of the blob references
+     * @return
+     */
+    long getBlobReferencesSize();
 }
diff --git a/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/BlobGCTest.java b/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/BlobGCTest.java
index 2dc54a3ebd..41127c2fe9 100644
--- a/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/BlobGCTest.java
+++ b/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/BlobGCTest.java
@@ -19,10 +19,12 @@
 
 package org.apache.jackrabbit.oak.plugins.blob;
 
+import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.BLOB_REFERENCES_SIZE;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.CONSISTENCY_NAME;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.FINISH_FAILURE;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.NAME;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.NUM_BLOBS_DELETED;
+import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.NUM_BLOB_REFERENCES;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.NUM_CANDIDATES;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.START;
 import static org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.GarbageCollectionOperationStats.TOTAL_SIZE_DELETED;
@@ -61,6 +63,7 @@ import org.apache.jackrabbit.oak.api.blob.BlobAccessProvider;
 import org.apache.jackrabbit.oak.api.blob.BlobUpload;
 import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
 import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
+import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector.NotAllRepositoryMarkedException;
 import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore;
 import org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils;
 import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
@@ -79,6 +82,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.mockito.internal.util.collections.Iterables;
 import org.slf4j.Logger;
@@ -92,6 +96,9 @@ public class BlobGCTest {
 
     @Rule
     public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+    
+    @Rule
+    public ExpectedException expectedEx = ExpectedException.none();
 
     protected Whiteboard wb;
 
@@ -150,7 +157,9 @@ public class BlobGCTest {
         protected ThreadPoolExecutor executor;
         protected DefaultStatisticsProvider statsProvider;
         protected long startReferenceTime;
-
+        
+        protected int blobSize = 100;
+        
         public Cluster(File root, GarbageCollectableBlobStore blobStore, NodeStore nodeStore, int seed) throws Exception {
             this.root = root;
             this.nodeStore = nodeStore;
@@ -167,9 +176,8 @@ public class BlobGCTest {
             log.info("Reference time {}", startReferenceTime);
             scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
             executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
-            statsProvider = new DefaultStatisticsProvider(scheduledExecutor);
 
-            blobStoreState = setUp(nodeStore, blobStore, 10, 5, 100, seed);
+            blobStoreState = setUp(nodeStore, blobStore, 10, 5, blobSize, seed);
         }
 
         public void setRepoId(String id) {
@@ -182,6 +190,7 @@ public class BlobGCTest {
 
         public MarkSweepGarbageCollector getCollector(long blobGcMaxAgeInSecs, boolean checkConsistency,
             boolean sweepIfRefsPastRetention) throws Exception {
+            statsProvider = new DefaultStatisticsProvider(scheduledExecutor);
 
             collector =
                 new MarkSweepGarbageCollector(referenceRetriever, blobStore, executor, root.getAbsolutePath(), 2048,
@@ -216,7 +225,8 @@ public class BlobGCTest {
 
         assertTrue(Sets.symmetricDifference(totalPresent, existingAfterGC).isEmpty());
         assertStats(secondCluster.statsProvider, 1, 0, totalAdded.size() - totalPresent.size(),
-            totalAdded.size() - totalPresent.size(), NAME);
+            totalAdded.size() - totalPresent.size(), secondCluster.blobStoreState.blobsPresent.size(), 
+            cluster.blobSize, NAME);
     }
 
     @Test
@@ -234,7 +244,8 @@ public class BlobGCTest {
         Set<String> existingAfterGC = executeGarbageCollection(secondCluster, secondCluster.getCollector(0), false);
 
         assertEquals(totalAdded, existingAfterGC);
-        assertStats(secondCluster.statsProvider, 1, 1, 0, 0, NAME);
+        assertStats(secondCluster.statsProvider, 1, 1, 0, 0, secondCluster.blobStoreState.blobsPresent.size(),
+            secondCluster.blobSize, NAME);
     }
 
     @Test
@@ -284,8 +295,9 @@ public class BlobGCTest {
         Set<String> existingAfterGC = executeGarbageCollection(secondCluster, secondCluster.getCollector(5, false, true), false);
 
         assertTrue(Sets.symmetricDifference(totalPresent, existingAfterGC).isEmpty());
-        assertStats(secondCluster.statsProvider, 2, 0, totalAdded.size() - totalPresent.size(),
-            totalAdded.size() - totalPresent.size(), NAME);
+        assertStats(secondCluster.statsProvider, 1, 0, totalAdded.size() - totalPresent.size(),
+            totalAdded.size() - totalPresent.size(), secondCluster.blobStoreState.blobsPresent.size(),
+            secondCluster.blobSize, NAME);
     }
 
     @Test
@@ -313,7 +325,8 @@ public class BlobGCTest {
         Set<String> existingAfterGC = executeGarbageCollection(secondCluster, secondCluster.getCollector(6, false, true), false);
 
         assertTrue(Sets.symmetricDifference(totalAdded, existingAfterGC).isEmpty());
-        assertStats(secondCluster.statsProvider, 2, 1, 0,0, NAME);
+        assertStats(secondCluster.statsProvider, 1, 1, 0,0, secondCluster.blobStoreState.blobsPresent.size(),
+            secondCluster.blobSize, NAME);
     }
 
     @Test
@@ -324,20 +337,24 @@ public class BlobGCTest {
         assertTrue(Sets.symmetricDifference(cluster.blobStoreState.blobsPresent, existingAfterGC).isEmpty());
         assertStats(cluster.statsProvider, 1, 0,
             cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
-            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(), NAME);
+            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, NAME);
     }
 
     @Test
     public void gcWithConsistencyCheck() throws Exception {
         log.info("Starting gcWithConsistencyCheck()");
-        ((MemoryBlobStoreNodeStore) cluster.nodeStore).getReferencedBlobs().add("SPURIOUS");
+        ((MemoryBlobStoreNodeStore) cluster.nodeStore).getReferencedBlobs().add("SPURIOUS#100");
 
         MarkSweepGarbageCollector collector = cluster.getCollector(0, true, false);
         Set<String> existingAfterGC = executeGarbageCollection(cluster, collector, false);
         assertFalse(Sets.symmetricDifference(cluster.blobStoreState.blobsPresent, existingAfterGC).isEmpty());
         assertStats(cluster.statsProvider, 1, 0,
             cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size() + 1,
-            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size() + 1, NAME);
+            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size() + 1,
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, NAME);
+        assertStats(cluster.statsProvider, 1, 1, 1, 0,
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, CONSISTENCY_NAME);
         assertStatsBean(collector.getConsistencyOperationStats(), 1, 1, 1);
     }
 
@@ -350,7 +367,8 @@ public class BlobGCTest {
         assertTrue(Sets.symmetricDifference(cluster.blobStoreState.blobsPresent, existingAfterGC).isEmpty());
         assertStats(cluster.statsProvider, 1, 0,
             cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
-            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(), NAME);
+            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, NAME);
     }
 
     @Test
@@ -362,7 +380,8 @@ public class BlobGCTest {
         assertTrue(Sets.symmetricDifference(cluster.blobStoreState.blobsPresent, existingAfterGC).isEmpty());
         assertStats(cluster.statsProvider, 1, 0,
             cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
-            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(), NAME);
+            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, NAME);
     }
 
     @Test
@@ -377,7 +396,8 @@ public class BlobGCTest {
                 false);
         assertTrue(Sets.symmetricDifference(cluster.blobStoreState.blobsAdded, existingAfterGC).isEmpty());
         assertStats(cluster.statsProvider, 1, 0, 0,
-            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(), NAME);
+            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, NAME);
     }
 
     @Test
@@ -391,7 +411,8 @@ public class BlobGCTest {
         long missing = collector.checkConsistency();
 
         assertEquals(0, missing);
-        assertStats(cluster.statsProvider, 1, 0, 0, 0, CONSISTENCY_NAME);
+        assertStats(cluster.statsProvider, 1, 0, 0, 0, cluster.blobStoreState.blobsPresent.size(), cluster.blobSize,
+            CONSISTENCY_NAME);
         assertStatsBean(collector.getConsistencyOperationStats(), 1, 0, 0);
     }
 
@@ -409,7 +430,8 @@ public class BlobGCTest {
         long missing = collector.checkConsistency();
 
         assertEquals(1, missing);
-        assertStats(cluster.statsProvider, 1, 1, 1, 0, CONSISTENCY_NAME);
+        assertStats(cluster.statsProvider, 1, 1, 1, 0, cluster.blobStoreState.blobsPresent.size(), cluster.blobSize,
+            CONSISTENCY_NAME);
         assertStatsBean(collector.getConsistencyOperationStats(), 1, 1, 1);
     }
 
@@ -421,14 +443,16 @@ public class BlobGCTest {
         MemoryBlobStoreNodeStore secondClusterNodeStore = new MemoryBlobStoreNodeStore(cluster.blobStore, true);
         Cluster secondCluster = new Cluster(folder.newFolder(), cluster.blobStore, secondClusterNodeStore, 100);
         closer.register(secondCluster);
-
+        
+        int totalPresent = secondCluster.blobStoreState.blobsPresent.size() + cluster.blobStoreState.blobsPresent.size();
         secondCluster.blobStoreState.blobsPresent.add(Iterables.firstOf(cluster.blobStoreState.blobsPresent));
         // Execute mark on the default cluster
         executeGarbageCollection(cluster, cluster.getCollector(0), true);
         MarkSweepGarbageCollector globalCollector = secondCluster.getCollector(0, true, false);
         long missing = globalCollector.checkConsistency();
         assertEquals(0, missing);
-        assertStats(secondCluster.statsProvider, 1, 0, 0, 0, CONSISTENCY_NAME);
+        assertStats(secondCluster.statsProvider, 1, 0, 0, 0, totalPresent,
+            cluster.blobSize, CONSISTENCY_NAME);
         assertStatsBean(globalCollector.getConsistencyOperationStats(), 1, 0, 0);
     }
 
@@ -450,7 +474,10 @@ public class BlobGCTest {
         MarkSweepGarbageCollector globalCollector = secondCluster.getCollector(0, true, false);
         long missing = globalCollector.checkConsistency();
         assertEquals(1, missing);
-        assertStats(secondCluster.statsProvider, 1, 1, 1, 0, CONSISTENCY_NAME);
+        int totalPresent =
+            secondCluster.blobStoreState.blobsPresent.size() + cluster.blobStoreState.blobsPresent.size();
+        assertStats(secondCluster.statsProvider, 1, 1, 1, 0, totalPresent,
+            cluster.blobSize, CONSISTENCY_NAME);
         assertStatsBean(globalCollector.getConsistencyOperationStats(), 1, 1, 1);
     }
 
@@ -472,10 +499,28 @@ public class BlobGCTest {
         MarkSweepGarbageCollector globalCollector = secondCluster.getCollector(0, true, false);
         long missing = globalCollector.checkConsistency();
         assertEquals(1, missing);
-        assertStats(secondCluster.statsProvider, 1, 1, 1, 0, CONSISTENCY_NAME);
+        int totalPresent =
+            secondCluster.blobStoreState.blobsPresent.size() + cluster.blobStoreState.blobsPresent.size();
+        assertStats(secondCluster.statsProvider, 1, 1, 1, 0, totalPresent,
+            cluster.blobSize, CONSISTENCY_NAME);
         assertStatsBean(globalCollector.getConsistencyOperationStats(), 1, 1, 1);
     }
 
+    @Test
+    public void checkConsistencyFailureNotAllMarked() throws Exception {
+        log.info("Staring checkConsistencyFailureNotAllMarked()");
+        expectedEx.expect(NotAllRepositoryMarkedException.class);
+
+        // Setup a different cluster/repository sharing the blob store
+        MemoryBlobStoreNodeStore secondClusterNodeStore = new MemoryBlobStoreNodeStore(cluster.blobStore);
+        Cluster secondCluster = new Cluster(folder.newFolder(), cluster.blobStore, secondClusterNodeStore, 100);
+        closer.register(secondCluster);
+
+        // Execute mark on the default cluster
+        MarkSweepGarbageCollector globalCollector = cluster.getCollector(0, true, false);
+        globalCollector.checkConsistency();
+    }
+
     @Test
     public void gcCheckDeletedSize() throws Exception {
         log.info("Starting gcCheckDeletedSize()");
@@ -496,7 +541,8 @@ public class BlobGCTest {
         assertTrue(customLogs.getLogs().get(0).contains(String.valueOf(deletedSize)));
         assertStats(cluster.statsProvider, 1, 0,
             cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
-            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(), NAME);
+            cluster.blobStoreState.blobsAdded.size() - cluster.blobStoreState.blobsPresent.size(),
+            cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, NAME);
         assertEquals(deletedSize, getStatCount(cluster.statsProvider, NAME, TOTAL_SIZE_DELETED));
         customLogs.finished();
         assertTrue(Sets.symmetricDifference(cluster.blobStoreState.blobsPresent, existingAfterGC).isEmpty());
@@ -509,7 +555,12 @@ public class BlobGCTest {
         Set<String> existingAfterGC =
             executeGarbageCollection(cluster, cluster.getCollector(0),true);
         assertTrue(Sets.symmetricDifference(cluster.blobStoreState.blobsAdded, existingAfterGC).isEmpty());
-        assertStats(cluster.statsProvider, 1, 0, 0, 0, NAME);
+        assertStats(cluster.statsProvider, 1, 0, 0, 0, cluster.blobStoreState.blobsPresent.size(), cluster.blobSize, 
+            NAME);
+        assertEquals(cluster.blobStoreState.blobsPresent.size(), getStatCount(cluster.statsProvider, NAME,
+            NUM_BLOB_REFERENCES));
+        assertEquals(cluster.blobStoreState.blobsPresent.size() * cluster.blobSize, getStatCount(cluster.statsProvider, NAME,
+            BLOB_REFERENCES_SIZE));
     }
 
     protected Set<String> executeGarbageCollection(Cluster cluster, MarkSweepGarbageCollector collector, boolean markOnly)
@@ -524,12 +575,17 @@ public class BlobGCTest {
     }
 
     private void assertStats(StatisticsProvider statsProvider, int start, int failure, long deleted, long candidates,
-        String typeName) {
+        long blobsPresent, long blobsPresentSize, String typeName) {
 
         assertEquals("Start counter mismatch", start, getStatCount(statsProvider, typeName, START));
         assertEquals("Finish error mismatch", failure, getStatCount(statsProvider, typeName, FINISH_FAILURE));
         assertEquals("Num deleted mismatch", deleted, getStatCount(statsProvider, typeName, NUM_BLOBS_DELETED));
         assertEquals("Num candidates mismatch", candidates, getStatCount(statsProvider, typeName, NUM_CANDIDATES));
+        assertEquals("Num references mismatch", blobsPresent, getStatCount(statsProvider, typeName,
+            NUM_BLOB_REFERENCES));
+        assertEquals("Blob reference size mismatch", blobsPresent * blobsPresentSize, getStatCount(statsProvider,
+            typeName,
+            BLOB_REFERENCES_SIZE));
     }