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

mreutegg 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 d521fd7c4d OAK-11808: Segment graph may need to be recomputed (#2393)
d521fd7c4d is described below

commit d521fd7c4db66b3617fbd4073693134b850a94ad
Author: Lucas Weitzendorf <[email protected]>
AuthorDate: Mon Aug 11 08:14:10 2025 +0200

    OAK-11808: Segment graph may need to be recomputed (#2393)
    
    * OAK-11808: SegmentGraph class
    
    * address review comments
    
    * address further comments
    
    ---------
    
    Co-authored-by: Lucas Weitzendorf <[email protected]>
---
 .../segment/aws/tool/AwsSegmentStoreMigrator.java  |   8 +-
 .../oak/segment/aws/tool/SegmentCopyTestBase.java  |   9 +-
 .../segment/azure/tool/SegmentStoreMigrator.java   |  22 +-
 .../segment/azure/tool/SegmentCopyTestBase.java    |  11 +-
 .../remote/AbstractRemoteSegmentArchiveReader.java |  25 +--
 .../oak/segment/remote/package-info.java           |   2 +-
 .../oak/segment/file/tar/GraphLoader.java          | 116 ----------
 .../oak/segment/file/tar/SegmentGraph.java         | 243 +++++++++++++++++++++
 .../oak/segment/file/tar/SegmentTarReader.java     |  36 +--
 .../oak/segment/file/tar/TarConstants.java         |  19 --
 .../jackrabbit/oak/segment/file/tar/TarFiles.java  |  26 +--
 .../jackrabbit/oak/segment/file/tar/TarReader.java |  98 ++-------
 .../jackrabbit/oak/segment/file/tar/TarWriter.java |  72 +-----
 .../spi/persistence/SegmentArchiveReader.java      |  17 +-
 .../CachingSegmentArchiveReader.java               |  11 +-
 .../persistence/persistentcache/package-info.java  |   2 +-
 .../split/UnclosedSegmentArchiveReader.java        |   8 +-
 .../oak/segment/file/tar/TarFileTest.java          |   8 +-
 18 files changed, 328 insertions(+), 405 deletions(-)

diff --git 
a/oak-segment-aws/src/main/java/org/apache/jackrabbit/oak/segment/aws/tool/AwsSegmentStoreMigrator.java
 
b/oak-segment-aws/src/main/java/org/apache/jackrabbit/oak/segment/aws/tool/AwsSegmentStoreMigrator.java
index 2bcc900d53..4f37a60d07 100644
--- 
a/oak-segment-aws/src/main/java/org/apache/jackrabbit/oak/segment/aws/tool/AwsSegmentStoreMigrator.java
+++ 
b/oak-segment-aws/src/main/java/org/apache/jackrabbit/oak/segment/aws/tool/AwsSegmentStoreMigrator.java
@@ -38,6 +38,7 @@ import org.apache.jackrabbit.oak.commons.Buffer;
 import org.apache.jackrabbit.oak.segment.aws.AwsContext;
 import org.apache.jackrabbit.oak.segment.aws.AwsPersistence;
 import 
org.apache.jackrabbit.oak.segment.aws.tool.AwsToolUtils.SegmentStoreType;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.apache.jackrabbit.oak.segment.file.tar.TarPersistence;
 import org.apache.jackrabbit.oak.segment.remote.RemoteUtilities;
 import org.apache.jackrabbit.oak.segment.spi.RepositoryNotReachableException;
@@ -212,11 +213,8 @@ public class AwsSegmentStoreMigrator implements Closeable  
{
     }
 
     private void migrateGraph(SegmentArchiveReader reader, 
SegmentArchiveWriter writer) throws IOException {
-        if (reader.hasGraph()) {
-            Buffer graph = reader.getGraph();
-            byte[] array = fetchByteArray(graph);
-            writer.writeGraph(array);
-        }
+        SegmentGraph graph = reader.getGraph();
+        writer.writeGraph(graph.write());
     }
 
     private static <T> T runWithRetry(Producer<T> producer, int maxAttempts, 
int intervalSec) throws IOException {
diff --git 
a/oak-segment-aws/src/test/java/org/apache/jackrabbit/oak/segment/aws/tool/SegmentCopyTestBase.java
 
b/oak-segment-aws/src/test/java/org/apache/jackrabbit/oak/segment/aws/tool/SegmentCopyTestBase.java
index 4ad310443f..d5b9a65732 100644
--- 
a/oak-segment-aws/src/test/java/org/apache/jackrabbit/oak/segment/aws/tool/SegmentCopyTestBase.java
+++ 
b/oak-segment-aws/src/test/java/org/apache/jackrabbit/oak/segment/aws/tool/SegmentCopyTestBase.java
@@ -37,6 +37,7 @@ import org.apache.jackrabbit.oak.segment.aws.AwsContext;
 import org.apache.jackrabbit.oak.segment.aws.AwsPersistence;
 import 
org.apache.jackrabbit.oak.segment.aws.tool.AwsToolUtils.SegmentStoreType;
 import org.apache.jackrabbit.oak.segment.file.FileStore;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitor;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitorAdapter;
 import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
@@ -176,11 +177,9 @@ public abstract class SegmentCopyTestBase {
             Buffer destBinRefBuffer = destArchiveReader.getBinaryReferences();
             assertEquals(srcBinRefBuffer, destBinRefBuffer);
 
-            assertEquals(srcArchiveReader.hasGraph(), 
destArchiveReader.hasGraph());
-
-            Buffer srcGraphBuffer = srcArchiveReader.getGraph();
-            Buffer destGraphBuffer = destArchiveReader.getGraph();
-            assertEquals(srcGraphBuffer, destGraphBuffer);
+            SegmentGraph srcGraph = srcArchiveReader.getGraph();
+            SegmentGraph destGraph = destArchiveReader.getGraph();
+            assertEquals(srcGraph, destGraph);
         }
     }
 
diff --git 
a/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentStoreMigrator.java
 
b/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentStoreMigrator.java
index 6fabbfe71e..cdd2ac1af7 100644
--- 
a/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentStoreMigrator.java
+++ 
b/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentStoreMigrator.java
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.oak.commons.Buffer;
 import org.apache.jackrabbit.oak.segment.azure.AzurePersistence;
 import org.apache.jackrabbit.oak.segment.azure.tool.ToolUtils.SegmentStoreType;
 import org.apache.jackrabbit.oak.segment.azure.util.Retrier;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.apache.jackrabbit.oak.segment.file.tar.TarPersistence;
 import org.apache.jackrabbit.oak.segment.remote.RemoteUtilities;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitorAdapter;
@@ -213,25 +214,14 @@ public class SegmentStoreMigrator implements Closeable  {
     private void migrateBinaryRef(SegmentArchiveReader reader, 
SegmentArchiveWriter writer) throws IOException, ExecutionException, 
InterruptedException {
         Future<Buffer> future = executor.submit(() -> 
RETRIER.execute(reader::getBinaryReferences));
         Buffer binaryReferences = future.get();
-        if (binaryReferences != null) {
-            byte[] array = fetchByteArray(binaryReferences);
-            RETRIER.execute(() -> writer.writeBinaryReferences(array));
-        }
+        byte[] array = fetchByteArray(binaryReferences);
+        RETRIER.execute(() -> writer.writeBinaryReferences(array));
     }
 
     private void migrateGraph(SegmentArchiveReader reader, 
SegmentArchiveWriter writer) throws IOException, ExecutionException, 
InterruptedException {
-        Future<Buffer> future = executor.submit(() -> RETRIER.execute(() -> {
-            if (reader.hasGraph()) {
-                return reader.getGraph();
-            } else {
-                return null;
-            }
-        }));
-        Buffer graph = future.get();
-        if (graph != null) {
-            byte[] array = fetchByteArray(graph);
-            RETRIER.execute(() -> writer.writeGraph(array));
-        }
+        Future<SegmentGraph> future = executor.submit(() -> 
RETRIER.execute(reader::getGraph));
+        SegmentGraph graph = future.get();
+        RETRIER.execute(() -> writer.writeGraph(graph.write()));
     }
 
     @Override
diff --git 
a/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentCopyTestBase.java
 
b/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentCopyTestBase.java
index f259939e53..c7f7f07606 100644
--- 
a/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentCopyTestBase.java
+++ 
b/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/tool/SegmentCopyTestBase.java
@@ -54,6 +54,7 @@ import 
org.apache.jackrabbit.oak.segment.azure.tool.SegmentCopy;
 import org.apache.jackrabbit.oak.segment.azure.tool.ToolUtils.SegmentStoreType;
 import 
org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.CompactorType;
 import org.apache.jackrabbit.oak.segment.file.FileStore;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitor;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitorAdapter;
 import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
@@ -195,12 +196,10 @@ public abstract class SegmentCopyTestBase {
             Buffer srcBinRefBuffer = srcArchiveReader.getBinaryReferences();
             Buffer destBinRefBuffer = destArchiveReader.getBinaryReferences();
             assertEquals(srcBinRefBuffer, destBinRefBuffer);
-
-            assertEquals(srcArchiveReader.hasGraph(), 
destArchiveReader.hasGraph());
-
-            Buffer srcGraphBuffer = srcArchiveReader.getGraph();
-            Buffer destGraphBuffer = destArchiveReader.getGraph();
-            assertEquals(srcGraphBuffer, destGraphBuffer);
+            
+            SegmentGraph srcGraph = srcArchiveReader.getGraph();
+            SegmentGraph destGraph = destArchiveReader.getGraph();
+            assertEquals(srcGraph, destGraph);
         }
     }
 
diff --git 
a/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/AbstractRemoteSegmentArchiveReader.java
 
b/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/AbstractRemoteSegmentArchiveReader.java
index a7f838d758..fe99490e18 100644
--- 
a/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/AbstractRemoteSegmentArchiveReader.java
+++ 
b/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/AbstractRemoteSegmentArchiveReader.java
@@ -21,9 +21,12 @@ import static 
org.apache.jackrabbit.oak.segment.remote.RemoteUtilities.OFF_HEAP;
 
 import org.apache.jackrabbit.oak.commons.Buffer;
 import org.apache.jackrabbit.oak.commons.time.Stopwatch;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveEntry;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveReader;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.io.IOException;
@@ -39,8 +42,6 @@ public abstract class AbstractRemoteSegmentArchiveReader 
implements SegmentArchi
 
     protected final Map<UUID, RemoteSegmentArchiveEntry> index = new 
LinkedHashMap<>();
 
-    protected Boolean hasGraph;
-
     public AbstractRemoteSegmentArchiveReader(IOMonitor ioMonitor) throws 
IOException {
         this.ioMonitor = ioMonitor;
     }
@@ -78,24 +79,16 @@ public abstract class AbstractRemoteSegmentArchiveReader 
implements SegmentArchi
     }
 
     @Override
-    public Buffer getGraph() throws IOException {
-        Buffer graph = doReadDataFile(".gph");
-        hasGraph = graph != null;
-        return graph;
-    }
-
-    @Override
-    public boolean hasGraph() {
-        if (hasGraph == null) {
-            try {
-                getGraph();
-            } catch (IOException ignore) { }
+    public @NotNull SegmentGraph getGraph() throws IOException {
+        Buffer buffer = doReadDataFile(".gph");
+        if (buffer != null) {
+            return SegmentGraph.parse(buffer);
         }
-        return hasGraph != null ? hasGraph : false;
+        return SegmentGraph.compute(this);
     }
 
     @Override
-    public Buffer getBinaryReferences() throws IOException {
+    public @Nullable Buffer getBinaryReferences() throws IOException {
         return doReadDataFile(".brf");
     }
 
diff --git 
a/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/package-info.java
 
b/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/package-info.java
index c72df5f1f7..fd64aa03ce 100644
--- 
a/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/package-info.java
+++ 
b/oak-segment-remote/src/main/java/org/apache/jackrabbit/oak/segment/remote/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 @Internal(since = "1.0.0")
-@Version("1.4.0")
+@Version("2.0.0")
 package org.apache.jackrabbit.oak.segment.remote;
 
 import org.apache.jackrabbit.oak.commons.annotations.Internal;
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GraphLoader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GraphLoader.java
deleted file mode 100644
index 8151920004..0000000000
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/GraphLoader.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.jackrabbit.oak.segment.file.tar;
-
-import static 
org.apache.jackrabbit.oak.segment.file.tar.TarConstants.GRAPH_MAGIC;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.zip.CRC32;
-
-import org.apache.jackrabbit.oak.commons.Buffer;
-import org.apache.jackrabbit.oak.commons.collections.MapUtils;
-import org.apache.jackrabbit.oak.segment.util.ReaderAtEnd;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class GraphLoader {
-
-    private static final Logger log = 
LoggerFactory.getLogger(GraphLoader.class);
-
-    private static final int FOOTER_SIZE = 16;
-
-    private GraphLoader() {
-    }
-
-    /**
-     * Loads the optional pre-compiled graph entry from the given tar file.
-     *
-     * @return the graph or {@code null} if one was not found
-     * @throws IOException if the tar file could not be read
-     */
-    public static Buffer loadGraph(ReaderAtEnd readerAtEnd) throws IOException 
{
-        Buffer meta = readerAtEnd.readAtEnd(FOOTER_SIZE, FOOTER_SIZE);
-
-        int crc32 = meta.getInt();
-        int count = meta.getInt();
-        int bytes = meta.getInt();
-        int magic = meta.getInt();
-
-        if (magic != GRAPH_MAGIC) {
-            log.warn("Invalid graph magic number");
-            return null;
-        }
-
-        if (count < 0) {
-            log.warn("Invalid number of entries");
-            return null;
-        }
-
-        if (bytes < 4 + count * 34) {
-            log.warn("Invalid entry size");
-            return null;
-        }
-
-        Buffer graph = readerAtEnd.readAtEnd(bytes, bytes);
-
-        byte[] b = new byte[bytes - FOOTER_SIZE];
-
-        graph.mark();
-        graph.get(b);
-        graph.reset();
-
-        CRC32 checksum = new CRC32();
-        checksum.update(b);
-
-        if (crc32 != (int) checksum.getValue()) {
-            log.warn("Invalid graph checksum in tar file");
-            return null;
-        }
-
-        return graph;
-    }
-
-    public static Map<UUID, List<UUID>> parseGraph(Buffer buffer) {
-        int nEntries = buffer.getInt(buffer.limit() - 12);
-
-        Map<UUID, List<UUID>> graph = MapUtils.newHashMap(nEntries);
-
-        for (int i = 0; i < nEntries; i++) {
-            long msb = buffer.getLong();
-            long lsb = buffer.getLong();
-            int nVertices = buffer.getInt();
-
-            List<UUID> vertices = new ArrayList<>(nVertices);
-
-            for (int j = 0; j < nVertices; j++) {
-                long vMsb = buffer.getLong();
-                long vLsb = buffer.getLong();
-                vertices.add(new UUID(vMsb, vLsb));
-            }
-
-            graph.put(new UUID(msb, lsb), vertices);
-        }
-
-        return graph;
-    }
-}
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentGraph.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentGraph.java
new file mode 100644
index 0000000000..353eaf52f5
--- /dev/null
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentGraph.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.segment.file.tar;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import org.apache.jackrabbit.oak.commons.Buffer;
+import org.apache.jackrabbit.oak.segment.SegmentId;
+import org.apache.jackrabbit.oak.segment.data.SegmentData;
+import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveEntry;
+import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveReader;
+import org.apache.jackrabbit.oak.segment.util.ReaderAtEnd;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class SegmentGraph {
+
+    private static final Logger log = 
LoggerFactory.getLogger(SegmentGraph.class);
+
+    private static final int FOOTER_SIZE = 16;
+
+    /**
+     * Magic byte sequence at the end of the graph block.
+     * <p>
+     * The file is read from the end (the tar file is read from the end: the
+     * last entry is the index, then the graph). File format:
+     * <ul>
+     * <li>0 padding to make the footer end at a 512 byte boundary</li>
+     * <li>The list of UUIDs (segments included the graph; this includes
+     * segments in this tar file, and referenced segments in tar files with a
+     * lower sequence number). 16 bytes each.</li>
+     * <li>The graph data. The index of the source segment UUID (in the above
+     * list, 4 bytes), then the list of referenced segments (the indexes of
+     * those; 4 bytes each). Then the list is terminated by -1.</li>
+     * <li>The last part is the footer, which contains metadata of the graph
+     * (size, checksum, the number of UUIDs).</li>
+     * </ul>
+     */
+    private static final int MAGIC = ('\n' << 24) + ('0' << 16) + ('G' << 8) + 
'\n';
+
+    private final @NotNull Map<UUID, Set<UUID>> edgeMap;
+
+    public SegmentGraph() {
+        this.edgeMap = new HashMap<>();
+    }
+
+    private SegmentGraph(@NotNull Map<UUID, Set<UUID>> edgeMap) {
+        this.edgeMap = edgeMap;
+    }
+
+    public void addEdge(@NotNull UUID from, @NotNull UUID to) {
+        edgeMap.computeIfAbsent(from, k -> new HashSet<>()).add(to);
+    }
+
+    public @NotNull Map<UUID, Set<UUID>> getEdges() {
+        return Collections.unmodifiableMap(edgeMap);
+    }
+
+    public @NotNull Set<UUID> getEdges(@NotNull UUID from) {
+        Set<UUID> set = edgeMap.getOrDefault(from, Collections.emptySet());
+        return Collections.unmodifiableSet(set);
+    }
+
+    /**
+     * Loads the optional pre-compiled graph entry from the given tar file.
+     *
+     * @return the graph or {@code null} if one was not found
+     * @throws IOException if the tar file could not be read
+     */
+    public static @Nullable SegmentGraph load(ReaderAtEnd readerAtEnd) throws 
IOException {
+        Buffer meta = readerAtEnd.readAtEnd(FOOTER_SIZE, FOOTER_SIZE);
+
+        int crc32 = meta.getInt();
+        int count = meta.getInt();
+        int bytes = meta.getInt();
+        int magic = meta.getInt();
+
+        if (magic != MAGIC) {
+            log.warn("Invalid graph magic number");
+            return null;
+        }
+
+        if (count < 0) {
+            log.warn("Invalid number of entries");
+            return null;
+        }
+
+        if (bytes < 4 + count * 34) {
+            log.warn("Invalid entry size");
+            return null;
+        }
+
+        Buffer buffer = readerAtEnd.readAtEnd(bytes, bytes);
+        byte[] b = new byte[bytes - FOOTER_SIZE];
+
+        buffer.mark();
+        buffer.get(b);
+        buffer.reset();
+
+        CRC32 checksum = new CRC32();
+        checksum.update(b);
+
+        if (crc32 != (int) checksum.getValue()) {
+            log.warn("Invalid graph checksum in tar file");
+            return null;
+        }
+
+        return SegmentGraph.parse(buffer);
+    }
+
+    /**
+     * Computes the graph from a segment archive
+     *
+     * @return the computed segment graph.
+     */
+    public static @NotNull SegmentGraph compute(SegmentArchiveReader 
archiveReader) throws IOException {
+        SegmentGraph graph = new SegmentGraph();
+        for (SegmentArchiveEntry entry : archiveReader.listSegments()) {
+            if (!SegmentId.isDataSegmentId(entry.getLsb())) {
+                continue;
+            }
+            UUID from = new UUID(entry.getMsb(), entry.getLsb());
+            Buffer buffer = archiveReader.readSegment(entry.getMsb(), 
entry.getLsb());
+            SegmentData data = SegmentData.newSegmentData(buffer);
+            for (int i = 0; i < data.getSegmentReferencesCount(); i++) {
+                UUID to = new UUID(data.getSegmentReferenceMsb(i), 
data.getSegmentReferenceLsb(i));
+                graph.addEdge(from, to);
+            }
+        }
+        return graph;
+    }
+
+    public static @NotNull SegmentGraph parse(@NotNull Buffer buffer) {
+        int nEntries = buffer.getInt(buffer.limit() - 12);
+        Map<UUID, Set<UUID>> edgeMap = new HashMap<>(nEntries);
+
+        for (int i = 0; i < nEntries; i++) {
+            long msb = buffer.getLong();
+            long lsb = buffer.getLong();
+            int nVertices = buffer.getInt();
+
+            Set<UUID> vertices = new HashSet<>(nVertices);
+
+            for (int j = 0; j < nVertices; j++) {
+                long vMsb = buffer.getLong();
+                long vLsb = buffer.getLong();
+                vertices.add(new UUID(vMsb, vLsb));
+            }
+
+            edgeMap.put(new UUID(msb, lsb), vertices);
+        }
+
+        return new SegmentGraph(edgeMap);
+    }
+
+    public byte[] write() {
+        int graphSize = size();
+        Buffer buffer = Buffer.allocate(graphSize);
+
+        for (Map.Entry<UUID, Set<UUID>> entry : edgeMap.entrySet()) {
+            UUID from = entry.getKey();
+            buffer.putLong(from.getMostSignificantBits());
+            buffer.putLong(from.getLeastSignificantBits());
+
+            Set<UUID> adj = entry.getValue();
+            buffer.putInt(adj.size());
+            for (UUID to : adj) {
+                buffer.putLong(to.getMostSignificantBits());
+                buffer.putLong(to.getLeastSignificantBits());
+            }
+        }
+
+        CRC32 checksum = new CRC32();
+        checksum.update(buffer.array(), 0, buffer.position());
+
+        buffer.putInt((int) checksum.getValue());
+        buffer.putInt(edgeMap.size());
+        buffer.putInt(graphSize);
+        buffer.putInt(MAGIC);
+
+        return buffer.array();
+    }
+
+    public int size() {
+        // The following information is stored in the footer as meta 
information about the entry.
+        // 4 bytes to store a magic number identifying this entry as 
containing references to binary values.
+        // 4 bytes to store the CRC32 checksum of the data in this entry.
+        // 4 bytes to store the length of this entry, without including the 
optional padding.
+        // 4 bytes to store the number of entries in the graph map.
+        int graphSize = FOOTER_SIZE;
+        // The following information is stored as part of the main content of
+        // this entry, after the optional padding.
+        for (Map.Entry<UUID, Set<UUID>> entry : edgeMap.entrySet()) {
+            // 16 bytes to store the key of the map.
+            graphSize += 16;
+            // 4 bytes for the number of entries in the adjacency list.
+            graphSize += 4;
+            // 16 bytes for every element in the adjacency list.
+            graphSize += 16 * entry.getValue().size();
+        }
+        return graphSize;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof SegmentGraph) {
+            return edgeMap.equals(((SegmentGraph) other).edgeMap);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return edgeMap.hashCode();
+    }
+}
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentTarReader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentTarReader.java
index afc924e49b..3e22e34d51 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentTarReader.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/SegmentTarReader.java
@@ -41,6 +41,7 @@ import 
org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveEntry;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveReader;
 import org.apache.jackrabbit.oak.segment.util.ReaderAtEnd;
+import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,8 +61,6 @@ public class SegmentTarReader implements SegmentArchiveReader 
{
 
     private final Index index;
 
-    private volatile Boolean hasGraph;
-
     public SegmentTarReader(File file, FileAccess access, Index index, 
IOMonitor ioMonitor) {
         this.access = access;
         this.file = file;
@@ -129,25 +128,18 @@ public class SegmentTarReader implements 
SegmentArchiveReader {
     }
 
     @Override
-    public Buffer getGraph() throws IOException {
+    public @NotNull SegmentGraph getGraph() throws IOException {
         int end = access.length() - 2 * BLOCK_SIZE - getIndexEntrySize();
-        Buffer graph = GraphLoader.loadGraph((whence, amount) -> 
access.read(end - whence, amount));
-        hasGraph = graph != null;
-        return graph;
-    }
-
-    @Override
-    public boolean hasGraph() {
-        if (hasGraph == null) {
-            try {
-                getGraph();
-            } catch (IOException ignore) { }
+        SegmentGraph graph = SegmentGraph.load((whence, amount) -> 
access.read(end - whence, amount));
+        if (graph == null) {
+            log.warn("Recomputing missing graph for {}.", name);
+            graph = SegmentGraph.compute(this);
         }
-        return hasGraph;
+        return graph;
     }
 
     @Override
-    public Buffer getBinaryReferences() throws IOException {
+    public @NotNull Buffer getBinaryReferences() throws IOException {
         try {
             int end = access.length() - 2 * BLOCK_SIZE - getIndexEntrySize() - 
getGraphEntrySize();
             return 
BinaryReferencesIndexLoader.loadBinaryReferencesIndex((whence, amount) -> 
access.read(end - whence, amount));
@@ -162,7 +154,7 @@ public class SegmentTarReader implements 
SegmentArchiveReader {
     }
 
     @Override
-    public String getName() {
+    public @NotNull String getName() {
         return name;
     }
 
@@ -181,20 +173,12 @@ public class SegmentTarReader implements 
SegmentArchiveReader {
     }
 
     private int getGraphEntrySize() {
-        Buffer buffer;
-
         try {
-            buffer = getGraph();
+            return getEntrySize(getGraph().size());
         } catch (IOException e) {
             log.warn("Exception while loading pre-compiled tar graph", e);
             return 0;
         }
-
-        if (buffer == null) {
-            return 0;
-        }
-
-        return getEntrySize(buffer.getInt(buffer.limit() - 8));
     }
 
     @Override
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarConstants.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarConstants.java
index 55e4087ce7..e75c56d3bb 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarConstants.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarConstants.java
@@ -25,25 +25,6 @@ public class TarConstants {
 
     static final String FILE_NAME_FORMAT = "data%05d%s.tar";
 
-    /**
-     * Magic byte sequence at the end of the graph block.
-     * <p>
-     * The file is read from the end (the tar file is read from the end: the
-     * last entry is the index, then the graph). File format:
-     * <ul>
-     * <li>0 padding to make the footer end at a 512 byte boundary</li>
-     * <li>The list of UUIDs (segments included the graph; this includes
-     * segments in this tar file, and referenced segments in tar files with a
-     * lower sequence number). 16 bytes each.</li>
-     * <li>The graph data. The index of the source segment UUID (in the above
-     * list, 4 bytes), then the list of referenced segments (the indexes of
-     * those; 4 bytes each). Then the list is terminated by -1.</li>
-     * <li>The last part is the footer, which contains metadata of the graph
-     * (size, checksum, the number of UUIDs).</li>
-     * </ul>
-     */
-    public static final int GRAPH_MAGIC = ('\n' << 24) + ('0' << 16) + ('G' << 
8) + '\n';
-
     /**
      * The tar file block size.
      */
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
index 111a2e2f28..63936be274 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.segment.file.tar;
 
+import static java.util.Collections.emptyMap;
 import static 
org.apache.jackrabbit.oak.commons.conditions.Validate.checkArgument;
 import static java.util.Objects.requireNonNull;
 
@@ -26,6 +27,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -881,29 +883,15 @@ public class TarFiles implements Closeable {
             lock.readLock().unlock();
         }
 
-        Set<UUID> index = null;
-        Map<UUID, List<UUID>> graph = null;
-
         for (TarReader reader : iterable(head)) {
             if (fileName.equals(reader.getFileName())) {
-                index = reader.getUUIDs();
-                graph = reader.getGraph();
-                break;
-            }
-        }
-
-        Map<UUID, Set<UUID>> result = new HashMap<>();
-        if (index != null) {
-            for (UUID uuid : index) {
-                result.put(uuid, emptySet());
-            }
-        }
-        if (graph != null) {
-            for (Entry<UUID, List<UUID>> entry : graph.entrySet()) {
-                result.put(entry.getKey(), new HashSet<>(entry.getValue()));
+                Map<UUID, Set<UUID>> result = new HashMap<>();
+                reader.getUUIDs().forEach((uuid -> result.put(uuid, 
emptySet())));
+                result.putAll(reader.getGraph().getEdges());
+                return result;
             }
         }
-        return result;
+        return emptyMap();
     }
 
     public Map<String, Set<UUID>> getIndices() {
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
index c1cd30e4cd..c17a4caa32 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
@@ -52,6 +52,7 @@ import 
org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveEntry;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveManager;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveReader;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -279,8 +280,6 @@ public class TarReader implements Closeable {
 
     private final Set<UUID> segmentUUIDs;
 
-    private volatile boolean hasGraph;
-
     private TarReader(SegmentArchiveManager archiveManager, 
SegmentArchiveReader archive) {
         this.archiveManager = archiveManager;
         this.archive = archive;
@@ -342,24 +341,6 @@ public class TarReader implements Closeable {
         return entryList.toArray(new SegmentArchiveEntry[entryList.size()]);
     }
 
-    /**
-     * Read the references of an entry in this TAR file.
-     *
-     * @param id    The identifier of the entry.
-     * @param graph The content of the graph of this TAR file.
-     * @return The references of the provided TAR entry.
-     */
-    @NotNull
-    private static List<UUID> getReferences(UUID id, Map<UUID, List<UUID>> 
graph) {
-        List<UUID> references = graph.get(id);
-
-        if (references == null) {
-            return Collections.emptyList();
-        }
-
-        return references;
-    }
-
     /**
      * Collect the references of those BLOBs that are reachable from the 
entries
      * in this TAR file.
@@ -379,7 +360,6 @@ public class TarReader implements Closeable {
      */
     void collectBlobReferences(@NotNull Consumer<String> collector, 
Predicate<GCGeneration> skipGeneration) {
         BinaryReferencesIndex references = getBinaryReferences();
-
         if (references == null) {
             return;
         }
@@ -426,12 +406,12 @@ public class TarReader implements Closeable {
      * @param context     An instance of {@link CleanupContext}.
      */
     void mark(Set<UUID> references, Set<UUID> reclaimable, CleanupContext 
context) throws IOException {
-        Map<UUID, List<UUID>> graph = getGraph();
+        SegmentGraph graph = getGraph();
         SegmentArchiveEntry[] entries = getEntries();
         for (int i = entries.length - 1; i >= 0; i--) {
             // A bulk segments is *always* written before any data segment 
referencing it.
             // Backward iteration ensures we see all references to bulk 
segments before
-            // we see the bulk segment itself. Therefore we can remove a bulk 
reference
+            // we see the bulk segment itself. Therefore, we can remove a bulk 
reference
             // from the bulkRefs set once we encounter it, which save us some 
memory and
             // CPU on subsequent look-ups.
             SegmentArchiveEntry entry = entries[i];
@@ -440,7 +420,7 @@ public class TarReader implements Closeable {
             if (context.shouldReclaim(id, generation, references.remove(id))) {
                 reclaimable.add(id);
             } else {
-                for (UUID refId : getReferences(id, graph)) {
+                for (UUID refId : graph.getEdges(id)) {
                     if (context.shouldFollow(id, refId)) {
                         references.add(refId);
                     }
@@ -511,18 +491,12 @@ public class TarReader implements Closeable {
             log.debug("None of the entries of {} are referenceable.", name);
             return null;
         }
-        if (afterSize >= beforeSize * 3 / 4 && hasGraph()) {
-            // the space savings are not worth it at less than 25%,
-            // unless this tar file lacks a pre-compiled segment graph
-            // in which case we'll always generate a new tar file with
-            // the graph to speed up future garbage collection runs.
+        if (afterSize >= beforeSize * 3 / 4) {
+            // the space savings are not worth it at less than 25%
             log.debug("Not enough space savings. ({}/{}). Skipping clean up of 
{}",
                     archive.length() - afterSize, archive.length(), name);
             return this;
         }
-        if (!hasGraph()) {
-            log.warn("Recovering {}, which is missing its graph.", name);
-        }
 
         int pos = name.length() - "a.tar".length();
         char generation = name.charAt(pos);
@@ -548,33 +522,20 @@ public class TarReader implements Closeable {
         }
 
         // Reconstruct the graph index for non-cleaned segments.
-
-        Map<UUID, List<UUID>> graph = getGraph();
-
-        for (Entry<UUID, List<UUID>> e : graph.entrySet()) {
-            if (cleaned.contains(e.getKey())) {
-                continue;
-            }
-
-            Set<UUID> vertices = new HashSet<>();
-
-            for (UUID vertex : e.getValue()) {
-                if (cleaned.contains(vertex)) {
-                    continue;
+        SegmentGraph graph = getGraph();
+        for (Entry<UUID, Set<UUID>> e : graph.getEdges().entrySet()) {
+            UUID from = e.getKey();
+            if (!cleaned.contains(from)) {
+                for (UUID to : e.getValue()) {
+                    if (!cleaned.contains(to)) {
+                        writer.addGraphEdge(from, to);
+                    }
                 }
-
-                vertices.add(vertex);
-            }
-
-            for (UUID vertex : vertices) {
-                writer.addGraphEdge(e.getKey(), vertex);
             }
         }
 
         // Reconstruct the binary reference index for non-cleaned segments.
-
         BinaryReferencesIndex references = getBinaryReferences();
-
         if (references != null) {
             references.forEach((gen, full, compacted, id, reference) -> {
                 if (cleaned.contains(id)) {
@@ -602,22 +563,12 @@ public class TarReader implements Closeable {
     }
 
     /**
-     * Loads and parses the optional pre-compiled graph entry from the given 
tar
-     * file.
+     * Loads and parses the pre-compiled graph entry from the tar file if it 
exists, computes it otherwise.
      *
-     * @return The parsed graph, or {@code null} if one was not found.
+     * @return A {@link SegmentGraph} instance
      */
-    Map<UUID, List<UUID>> getGraph() throws IOException {
-        Buffer buffer = archive.getGraph();
-        if (buffer == null) {
-            return null;
-        } else {
-            return GraphLoader.parseGraph(buffer);
-        }
-    }
-
-    private boolean hasGraph() {
-        return archive.hasGraph();
+    @NotNull SegmentGraph getGraph() throws IOException {
+        return archive.getGraph();
     }
 
     /**
@@ -631,23 +582,20 @@ public class TarReader implements Closeable {
      *
      * @return An instance of {@link Map}.
      */
-    BinaryReferencesIndex getBinaryReferences() {
-        BinaryReferencesIndex index = null;
+    @Nullable BinaryReferencesIndex getBinaryReferences() {
         try {
-
             Buffer binaryReferences = archive.getBinaryReferences();
-            if (binaryReferences == null && archive.isRemote()) {
-
+            if (binaryReferences == null) {
                 // This can happen because segment files and binary references 
files are flushed one after another in
                 // {@link TarWriter#flush}
-                log.info("The remote archive directory {} still does not have 
file with binary references written.", archive.getName());
+                log.info("The archive directory {} still does not have file 
with binary references written.", archive.getName());
                 return null;
             }
-            index = 
BinaryReferencesIndexLoader.parseBinaryReferencesIndex(binaryReferences);
+            return 
BinaryReferencesIndexLoader.parseBinaryReferencesIndex(binaryReferences);
         } catch (InvalidBinaryReferencesIndexException | IOException e) {
             log.warn("Exception while loading binary reference", e);
+            return null;
         }
-        return index;
     }
 
     /**
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarWriter.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarWriter.java
index 750b7ec2ee..43516b3379 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarWriter.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarWriter.java
@@ -23,18 +23,11 @@ import static java.util.Objects.requireNonNull;
 
 import static java.lang.String.format;
 import static 
org.apache.jackrabbit.oak.segment.file.tar.TarConstants.FILE_NAME_FORMAT;
-import static 
org.apache.jackrabbit.oak.segment.file.tar.TarConstants.GRAPH_MAGIC;
 import static 
org.apache.jackrabbit.oak.segment.file.tar.binaries.BinaryReferencesIndexWriter.newBinaryReferencesIndexWriter;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
 import java.util.UUID;
-import java.util.zip.CRC32;
 
 import org.apache.jackrabbit.oak.commons.Buffer;
 import org.apache.jackrabbit.oak.commons.conditions.Validate;
@@ -72,7 +65,7 @@ class TarWriter implements Closeable {
     /**
      * Graph of references between segments.
      */
-    private final Map<UUID, Set<UUID>> graph = new HashMap<>();
+    private final SegmentGraph graph = new SegmentGraph();
 
     private final SegmentArchiveManager archiveManager;
 
@@ -172,7 +165,7 @@ class TarWriter implements Closeable {
     }
 
     void addGraphEdge(UUID from, UUID to) {
-        graph.computeIfAbsent(from, k -> new HashSet<>()).add(to);
+        graph.addEdge(from, to);
     }
 
     /**
@@ -260,66 +253,7 @@ class TarWriter implements Closeable {
     }
 
     private void writeGraph() throws IOException {
-        int graphSize = 0;
-
-        // The following information are stored in the footer as meta-
-        // information about the entry.
-
-        // 4 bytes to store a magic number identifying this entry as containing
-        // references to binary values.
-        graphSize += 4;
-
-        // 4 bytes to store the CRC32 checksum of the data in this entry.
-        graphSize += 4;
-
-        // 4 bytes to store the length of this entry, without including the
-        // optional padding.
-        graphSize += 4;
-
-        // 4 bytes to store the number of entries in the graph map.
-        graphSize += 4;
-
-        // The following information are stored as part of the main content of
-        // this entry, after the optional padding.
-
-        for (Entry<UUID, Set<UUID>> entry : graph.entrySet()) {
-            // 16 bytes to store the key of the map.
-            graphSize += 16;
-
-            // 4 bytes for the number of entries in the adjacency list.
-            graphSize += 4;
-
-            // 16 bytes for every element in the adjacency list.
-            graphSize += 16 * entry.getValue().size();
-        }
-
-        Buffer buffer = Buffer.allocate(graphSize);
-
-        for (Entry<UUID, Set<UUID>> entry : graph.entrySet()) {
-            UUID from = entry.getKey();
-
-            buffer.putLong(from.getMostSignificantBits());
-            buffer.putLong(from.getLeastSignificantBits());
-
-            Set<UUID> adj = entry.getValue();
-
-            buffer.putInt(adj.size());
-
-            for (UUID to : adj) {
-                buffer.putLong(to.getMostSignificantBits());
-                buffer.putLong(to.getLeastSignificantBits());
-            }
-        }
-
-        CRC32 checksum = new CRC32();
-        checksum.update(buffer.array(), 0, buffer.position());
-
-        buffer.putInt((int) checksum.getValue());
-        buffer.putInt(graph.size());
-        buffer.putInt(graphSize);
-        buffer.putInt(GRAPH_MAGIC);
-
-        archive.writeGraph(buffer.array());
+        archive.writeGraph(graph.write());
     }
 
     synchronized long fileLength() {
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/SegmentArchiveReader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/SegmentArchiveReader.java
index 2e99a5970e..3ac897e65b 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/SegmentArchiveReader.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/SegmentArchiveReader.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.List;
 
 import org.apache.jackrabbit.oak.commons.Buffer;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -61,25 +62,17 @@ public interface SegmentArchiveReader extends Closeable {
     /**
      * Load the segment graph.
      *
-     * @return byte buffer representing the graph or null if the graph hasn't 
been
-     * persisted.
+     * @return segment graph instance
      */
-    @Nullable
-    Buffer getGraph() throws IOException;
-
-    /**
-     * Check if the segment graph has been persisted for this archive.
-     *
-     * @return {@code true} if the graph exists, false otherwise
-     */
-    boolean hasGraph();
+    @NotNull
+    SegmentGraph getGraph() throws IOException;
 
     /**
      * Load binary references.
      *
      * @return byte buffer representing the binary references structure.
      */
-    @NotNull
+    @Nullable
     Buffer getBinaryReferences() throws IOException;
 
     /**
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/CachingSegmentArchiveReader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/CachingSegmentArchiveReader.java
index 9c682d6b3a..df5aadb3ea 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/CachingSegmentArchiveReader.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/CachingSegmentArchiveReader.java
@@ -19,6 +19,7 @@
 package org.apache.jackrabbit.oak.segment.spi.persistence.persistentcache;
 
 import org.apache.jackrabbit.oak.commons.Buffer;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveEntry;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveReader;
 import org.jetbrains.annotations.NotNull;
@@ -63,18 +64,12 @@ public class CachingSegmentArchiveReader implements 
SegmentArchiveReader {
     }
 
     @Override
-    @Nullable
-    public Buffer getGraph() throws IOException {
+    public @NotNull SegmentGraph getGraph() throws IOException {
         return delegate.getGraph();
     }
 
     @Override
-    public boolean hasGraph() {
-        return delegate.hasGraph();
-    }
-
-    @Override
-    @NotNull
+    @Nullable
     public Buffer getBinaryReferences() throws IOException {
         return delegate.getBinaryReferences();
     }
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/package-info.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/package-info.java
index 493125d01b..db4c23c990 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/package-info.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/persistentcache/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 @Internal(since = "1.0.0")
-@Version("4.1.0")
+@Version("5.0.0")
 package org.apache.jackrabbit.oak.segment.spi.persistence.persistentcache;
 
 import org.apache.jackrabbit.oak.commons.annotations.Internal;
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/split/UnclosedSegmentArchiveReader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/split/UnclosedSegmentArchiveReader.java
index 37c74024b6..d6902c757d 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/split/UnclosedSegmentArchiveReader.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/spi/persistence/split/UnclosedSegmentArchiveReader.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.util.List;
 
 import org.apache.jackrabbit.oak.commons.Buffer;
+import org.apache.jackrabbit.oak.segment.file.tar.SegmentGraph;
 import 
org.apache.jackrabbit.oak.segment.file.tar.binaries.BinaryReferencesIndexWriter;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveEntry;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveReader;
@@ -52,15 +53,10 @@ class UnclosedSegmentArchiveReader implements 
SegmentArchiveReader {
     }
 
     @Override
-    public @Nullable Buffer getGraph() throws IOException {
+    public @NotNull SegmentGraph getGraph() throws IOException {
         return delegate.getGraph();
     }
 
-    @Override
-    public boolean hasGraph() {
-        return delegate.hasGraph();
-    }
-
     @Override
     public @NotNull Buffer getBinaryReferences() throws IOException {
         Buffer buffer = delegate.getBinaryReferences();
diff --git 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFileTest.java
 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFileTest.java
index f2350c35c5..972d462659 100644
--- 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFileTest.java
+++ 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFileTest.java
@@ -255,12 +255,10 @@ public class TarFileTest {
         Set<UUID> sweep = newSet(new UUID(1, 2), new UUID(2, 3));
 
         try (TarReader reader = TarReader.open("data00000a.tar", 
archiveManager)) {
-            try (TarReader swept = reader.sweep(sweep, new HashSet<UUID>())) {
+            try (TarReader swept = reader.sweep(sweep, new HashSet<>())) {
                 assertNotNull(swept);
-
-                Map<UUID, List<UUID>> graph = new HashMap<>();
-                graph.put(new UUID(2, 1), List.of(new UUID(2, 2)));
-
+                SegmentGraph graph = new SegmentGraph();
+                graph.addEdge(new UUID(2, 1), new UUID(2, 2));
                 assertEquals(graph, swept.getGraph());
             }
         }

Reply via email to