2 * Copyright 2015-present Open Networking Foundation
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;
20 import java.nio.ByteBuffer;
22 import static com.google.common.base.MoreObjects.toStringHelper;
23 import static java.util.Objects.requireNonNull;
26 * Stores information about a {@link JournalSegment} of the log.
28 * The segment descriptor manages metadata related to a single segment of the log. Descriptors are stored within the
29 * first {@code 64} bytes of each segment in the following order:
31 * <li>{@code id} (64-bit signed integer) - A unique segment identifier. This is a monotonically increasing number within
32 * each log. Segments with in-sequence identifiers should contain in-sequence indexes.</li>
33 * <li>{@code index} (64-bit signed integer) - The effective first index of the segment. This indicates the index at which
34 * the first entry should be written to the segment. Indexes are monotonically increasing thereafter.</li>
35 * <li>{@code version} (64-bit signed integer) - The version of the segment. Versions are monotonically increasing
36 * starting at {@code 1}. Versions will only be incremented whenever the segment is rewritten to another memory/disk
37 * space, e.g. after log compaction.</li>
38 * <li>{@code maxSegmentSize} (32-bit unsigned integer) - The maximum number of bytes allowed in the segment.</li>
39 * <li>{@code maxEntries} (32-bit signed integer) - The total number of expected entries in the segment. This is the final
40 * number of entries allowed within the segment both before and after compaction. This entry count is used to determine
41 * the count of internal indexing and deduplication facilities.</li>
42 * <li>{@code updated} (64-bit signed integer) - The last update to the segment in terms of milliseconds since the epoch.
43 * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
44 * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
45 * change to {@code updated}.</li>
46 * <li>{@code locked} (8-bit boolean) - A boolean indicating whether the segment is locked. Segments will be locked once
47 * all entries have been committed to the segment. The lock state of each segment is used to determine log compaction
48 * and recovery behavior.</li>
50 * The remainder of the 64 segment header bytes are reserved for future metadata.
52 * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
54 public final class JournalSegmentDescriptor {
55 public static final int BYTES = 64;
57 // Current segment version.
59 static final int VERSION = 1;
61 // The lengths of each field in the header.
62 private static final int VERSION_LENGTH = Integer.BYTES; // 32-bit signed integer
63 private static final int ID_LENGTH = Long.BYTES; // 64-bit signed integer
64 private static final int INDEX_LENGTH = Long.BYTES; // 64-bit signed integer
65 private static final int MAX_SIZE_LENGTH = Integer.BYTES; // 32-bit signed integer
66 private static final int MAX_ENTRIES_LENGTH = Integer.BYTES; // 32-bit signed integer
67 private static final int UPDATED_LENGTH = Long.BYTES; // 64-bit signed integer
69 // The positions of each field in the header.
70 private static final int VERSION_POSITION = 0; // 0
71 private static final int ID_POSITION = VERSION_POSITION + VERSION_LENGTH; // 4
72 private static final int INDEX_POSITION = ID_POSITION + ID_LENGTH; // 12
73 private static final int MAX_SIZE_POSITION = INDEX_POSITION + INDEX_LENGTH; // 20
74 private static final int MAX_ENTRIES_POSITION = MAX_SIZE_POSITION + MAX_SIZE_LENGTH; // 24
75 private static final int UPDATED_POSITION = MAX_ENTRIES_POSITION + MAX_ENTRIES_LENGTH; // 28
78 * Returns a descriptor builder.
80 * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
82 * @return The descriptor builder.
84 public static Builder builder() {
85 return new Builder(ByteBuffer.allocate(BYTES));
89 * Returns a descriptor builder for the given descriptor buffer.
91 * @param buffer The descriptor buffer.
92 * @return The descriptor builder.
93 * @throws NullPointerException if {@code buffer} is null
95 public static Builder builder(ByteBuffer buffer) {
96 return new Builder(buffer);
99 private final ByteBuffer buffer;
100 private final int version;
101 private final long id;
102 private final long index;
103 private final int maxSegmentSize;
104 private final int maxEntries;
105 private volatile long updated;
106 private volatile boolean locked;
109 * @throws NullPointerException if {@code buffer} is null
111 public JournalSegmentDescriptor(ByteBuffer buffer) {
112 this.buffer = buffer;
113 this.version = buffer.getInt();
114 this.id = buffer.getLong();
115 this.index = buffer.getLong();
116 this.maxSegmentSize = buffer.getInt();
117 this.maxEntries = buffer.getInt();
118 this.updated = buffer.getLong();
119 this.locked = buffer.get() == 1;
123 * Returns the segment version.
125 * Versions are monotonically increasing starting at {@code 1}.
127 * @return The segment version.
129 public int version() {
134 * Returns the segment identifier.
136 * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
137 * contain in-sequence indexes.
139 * @return The segment identifier.
146 * Returns the segment index.
148 * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
149 * increasing thereafter.
151 * @return The segment index.
153 public long index() {
158 * Returns the maximum count of the segment.
160 * @return The maximum allowed count of the segment.
162 public int maxSegmentSize() {
163 return maxSegmentSize;
167 * Returns the maximum number of entries allowed in the segment.
169 * @return The maximum number of entries allowed in the segment.
171 public int maxEntries() {
176 * Returns last time the segment was updated.
178 * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
179 * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
180 * change to {@code updated}.
182 * @return The last time the segment was updated in terms of milliseconds since the epoch.
184 public long updated() {
189 * Writes an update to the descriptor.
191 public void update(long timestamp) {
193 buffer.putLong(UPDATED_POSITION, timestamp);
194 this.updated = timestamp;
199 * Copies the segment to a new buffer.
201 JournalSegmentDescriptor copyTo(ByteBuffer buffer) {
202 buffer.putInt(version);
204 buffer.putLong(index);
205 buffer.putInt(maxSegmentSize);
206 buffer.putInt(maxEntries);
207 buffer.putLong(updated);
208 buffer.put(locked ? (byte) 1 : (byte) 0);
213 public String toString() {
214 return toStringHelper(this)
215 .add("version", version)
218 .add("updated", updated)
223 * Segment descriptor builder.
225 public static class Builder {
226 private final ByteBuffer buffer;
228 private Builder(ByteBuffer buffer) {
229 this.buffer = requireNonNull(buffer, "buffer cannot be null");
230 buffer.putInt(VERSION_POSITION, VERSION);
234 * Sets the segment identifier.
236 * @param id The segment identifier.
237 * @return The segment descriptor builder.
239 public Builder withId(long id) {
240 buffer.putLong(ID_POSITION, id);
245 * Sets the segment index.
247 * @param index The segment starting index.
248 * @return The segment descriptor builder.
250 public Builder withIndex(long index) {
251 buffer.putLong(INDEX_POSITION, index);
256 * Sets maximum count of the segment.
258 * @param maxSegmentSize The maximum count of the segment.
259 * @return The segment descriptor builder.
261 public Builder withMaxSegmentSize(int maxSegmentSize) {
262 buffer.putInt(MAX_SIZE_POSITION, maxSegmentSize);
267 * Sets the maximum number of entries in the segment.
269 * @param maxEntries The maximum number of entries in the segment.
270 * @return The segment descriptor builder.
271 * @deprecated since 3.0.2
274 public Builder withMaxEntries(int maxEntries) {
275 buffer.putInt(MAX_ENTRIES_POSITION, maxEntries);
280 * Builds the segment descriptor.
282 * @return The built segment descriptor.
284 public JournalSegmentDescriptor build() {
286 return new JournalSegmentDescriptor(buffer);