2 * Copyright 2015-2022 Open Networking Foundation and others. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package io.atomix.storage.journal;
18 import com.google.common.annotations.VisibleForTesting;
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.nio.file.StandardOpenOption;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
28 * Stores information about a {@link JournalSegment} of the log.
30 * The segment descriptor manages metadata related to a single segment of the log. Descriptors are stored within the
31 * first {@code 64} bytes of each segment in the following order:
33 * <li>{@code id} (64-bit signed integer) - A unique segment identifier. This is a monotonically increasing number
34 * within each log. Segments with in-sequence identifiers should contain in-sequence indexes.</li>
35 * <li>{@code index} (64-bit signed integer) - The effective first index of the segment. This indicates the index at
36 * which the first entry should be written to the segment. Indexes are monotonically increasing thereafter.</li>
37 * <li>{@code version} (64-bit signed integer) - The version of the segment. Versions are monotonically increasing
38 * starting at {@code 1}. Versions will only be incremented whenever the segment is rewritten to another
39 * memory/disk space, e.g. after log compaction.</li>
40 * <li>{@code maxSegmentSize} (32-bit unsigned integer) - The maximum number of bytes allowed in the segment.</li>
41 * <li>{@code maxEntries} (32-bit signed integer) - The total number of expected entries in the segment. This is the
42 * final number of entries allowed within the segment both before and after compaction. This entry count is used
43 * to determine the count of internal indexing and deduplication facilities.</li>
44 * <li>{@code updated} (64-bit signed integer) - The last update to the segment in terms of milliseconds since the
46 * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment
47 * have been committed, the {@code updated} time should be set to the current time. Log compaction should not
48 * result in a change to {@code updated}.</li>
49 * <li>{@code locked} (8-bit boolean) - A boolean indicating whether the segment is locked. Segments will be locked
50 * once all entries have been committed to the segment. The lock state of each segment is used to determine log
51 * compaction and recovery behavior.</li>
53 * The remainder of the 64 segment header bytes are reserved for future metadata.
55 * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
57 public record JournalSegmentDescriptor(
65 public static final int BYTES = 64;
67 // Current segment version.
69 static final int VERSION = 1;
72 * Read a JournalSegmentDescriptor from a {@link Path}.
74 * @param path path to read from
75 * @return A {@link JournalSegmentDescriptor}
76 * @throws IOException if an I/O error occurs or there is not enough data
78 public static @NonNull JournalSegmentDescriptor readFrom(final Path path) throws IOException {
80 try (var is = Files.newInputStream(path, StandardOpenOption.READ)) {
81 bytes = is.readNBytes(BYTES);
84 if (bytes.length != BYTES) {
85 throw new IOException("Need " + BYTES + " bytes, only " + bytes.length + " available");
88 final var buffer = ByteBuffer.wrap(bytes);
89 return new JournalSegmentDescriptor(
100 * Returns the segment version.
102 * Versions are monotonically increasing starting at {@code 1}.
104 * @return The segment version.
106 public int version() {
111 * Returns the segment identifier.
113 * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
114 * contain in-sequence indexes.
116 * @return The segment identifier.
123 * Returns the segment index.
125 * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
126 * increasing thereafter.
128 * @return The segment index.
130 public long index() {
135 * Returns the maximum count of the segment.
137 * @return The maximum allowed count of the segment.
139 public int maxSegmentSize() {
140 return maxSegmentSize;
144 * Returns the maximum number of entries allowed in the segment.
146 * @return The maximum number of entries allowed in the segment.
148 public int maxEntries() {
153 * Returns last time the segment was updated.
155 * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
156 * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
157 * change to {@code updated}.
159 * @return The last time the segment was updated in terms of milliseconds since the epoch.
161 public long updated() {
166 * Returns this segment as an array of bytes
170 byte @NonNull [] toArray() {
171 final var bytes = new byte[BYTES];
172 ByteBuffer.wrap(bytes)
176 .putInt(maxSegmentSize)
179 .put(locked ? (byte) 1 : (byte) 0);
184 * Returns a descriptor builder.
186 * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
188 * @return The descriptor builder.
190 public static Builder builder() {
191 return builder(VERSION);
195 * Returns a descriptor builder for the given descriptor buffer.
197 * @param version version to build
198 * @return The descriptor builder.
199 * @throws NullPointerException if {@code buffer} is null
201 public static Builder builder(final int version) {
202 return new Builder(version);
206 * Segment descriptor builder.
208 public static final class Builder {
209 private final int version;
213 private Integer maxSegmentSize;
214 private Integer maxEntries;
215 private Long updated;
217 Builder(final int version) {
218 this.version = version;
222 * Sets the segment identifier.
224 * @param id The segment identifier.
225 * @return The segment descriptor builder.
227 public Builder withId(final long id) {
233 * Sets the segment index.
235 * @param index The segment starting index.
236 * @return The segment descriptor builder.
238 public Builder withIndex(final long index) {
244 * Sets maximum count of the segment.
246 * @param maxSegmentSize The maximum count of the segment.
247 * @return The segment descriptor builder.
249 public Builder withMaxSegmentSize(final int maxSegmentSize) {
250 this.maxSegmentSize = maxSegmentSize;
255 * Sets the maximum number of entries in the segment.
257 * @param maxEntries The maximum number of entries in the segment.
258 * @return The segment descriptor builder.
259 * @deprecated since 3.0.2
262 public Builder withMaxEntries(final int maxEntries) {
263 this.maxEntries = maxEntries;
268 * Sets updated timestamp;
270 * @param updated Epoch milliseconds
271 * @return The segment descriptor builder.
273 public Builder withUpdated(final long updated) {
274 this.updated = updated;
279 * Builds the segment descriptor.
281 * @return The built segment descriptor.
283 public JournalSegmentDescriptor build() {
284 return new JournalSegmentDescriptor(version,
286 checkSet(index, "index"),
287 checkSet(maxSegmentSize, "maxSegmentSize"),
288 checkSet(maxEntries, "maxEntries"),
289 checkSet(updated, "updated"),
293 private static <T> @NonNull T checkSet(final @Nullable T obj, final String name) {
297 throw new IllegalArgumentException(name + " not set");