Retain RandomAccessFile in JournalSegmentFile
[controller.git] / atomix-storage / src / main / java / io / atomix / storage / journal / JournalSegmentDescriptor.java
1 /*
2  * Copyright 2015-2022 Open Networking Foundation and others.  All rights reserved.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package io.atomix.storage.journal;
17
18 import com.google.common.annotations.VisibleForTesting;
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.nio.channels.ReadableByteChannel;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24
25 /**
26  * Stores information about a {@link JournalSegment} of the log.
27  * <p>
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:
30  * <ul>
31  *   <li>{@code id} (64-bit signed integer) - A unique segment identifier. This is a monotonically increasing number
32  *       within 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
34  *       which 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
37  *       memory/disk 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
40  *       final number of entries allowed within the segment both before and after compaction. This entry count is used
41  *       to determine 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
43  *       epoch.
44  *       When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment
45  *       have been committed, the {@code updated} time should be set to the current time. Log compaction should not
46  *       result in a change to {@code updated}.</li>
47  *   <li>{@code locked} (8-bit boolean) - A boolean indicating whether the segment is locked. Segments will be locked
48  *       once all entries have been committed to the segment. The lock state of each segment is used to determine log
49  *       compaction and recovery behavior.</li>
50  * </ul>
51  * The remainder of the 64 segment header bytes are reserved for future metadata.
52  *
53  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
54  */
55 public record JournalSegmentDescriptor(
56         int version,
57         long id,
58         long index,
59         int maxSegmentSize,
60         int maxEntries,
61         long updated,
62         boolean locked) {
63     public static final int BYTES = 64;
64
65     // Current segment version.
66     @VisibleForTesting
67     static final int VERSION = 1;
68
69     /**
70      * Read a JournalSegmentDescriptor from a {@link ReadableByteChannel}.
71      *
72      * @param channel channel to read from
73      * @return A {@link JournalSegmentDescriptor}
74      * @throws IOException if an I/O error occurs or there is not enough data
75      */
76     public static @NonNull JournalSegmentDescriptor readFrom(final ReadableByteChannel channel) throws IOException {
77         final var buffer = ByteBuffer.allocate(BYTES);
78         final var read = channel.read(buffer);
79         if (read != BYTES) {
80             throw new IOException("Need " + BYTES + " bytes, only " + read + " available");
81         }
82
83         buffer.flip();
84         return new JournalSegmentDescriptor(
85             buffer.getInt(),
86             buffer.getLong(),
87             buffer.getLong(),
88             buffer.getInt(),
89             buffer.getInt(),
90             buffer.getLong(),
91             buffer.get() == 1);
92     }
93
94     /**
95      * Returns the segment version.
96      * <p>
97      * Versions are monotonically increasing starting at {@code 1}.
98      *
99      * @return The segment version.
100      */
101     public int version() {
102         return version;
103     }
104
105     /**
106      * Returns the segment identifier.
107      * <p>
108      * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
109      * contain in-sequence indexes.
110      *
111      * @return The segment identifier.
112      */
113     public long id() {
114         return id;
115     }
116
117     /**
118      * Returns the segment index.
119      * <p>
120      * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
121      * increasing thereafter.
122      *
123      * @return The segment index.
124      */
125     public long index() {
126         return index;
127     }
128
129     /**
130      * Returns the maximum count of the segment.
131      *
132      * @return The maximum allowed count of the segment.
133      */
134     public int maxSegmentSize() {
135         return maxSegmentSize;
136     }
137
138     /**
139      * Returns the maximum number of entries allowed in the segment.
140      *
141      * @return The maximum number of entries allowed in the segment.
142      */
143     public int maxEntries() {
144         return maxEntries;
145     }
146
147     /**
148      * Returns last time the segment was updated.
149      * <p>
150      * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
151      * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
152      * change to {@code updated}.
153      *
154      * @return The last time the segment was updated in terms of milliseconds since the epoch.
155      */
156     public long updated() {
157         return updated;
158     }
159
160     /**
161      * Returns this segment as an array of bytes
162      *
163      * @return bytes
164      */
165     byte @NonNull [] toArray() {
166         final var bytes = new byte[BYTES];
167         ByteBuffer.wrap(bytes)
168             .putInt(version)
169             .putLong(id)
170             .putLong(index)
171             .putInt(maxSegmentSize)
172             .putInt(maxEntries)
173             .putLong(updated)
174             .put(locked ? (byte) 1 : (byte) 0);
175         return bytes;
176     }
177
178     /**
179      * Returns a descriptor builder.
180      * <p>
181      * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
182      *
183      * @return The descriptor builder.
184      */
185     public static Builder builder() {
186         return builder(VERSION);
187     }
188
189     /**
190      * Returns a descriptor builder for the given descriptor buffer.
191      *
192      * @param version version to build
193      * @return The descriptor builder.
194      * @throws NullPointerException if {@code buffer} is null
195      */
196     public static Builder builder(final int version) {
197         return new Builder(version);
198     }
199
200     /**
201      * Segment descriptor builder.
202      */
203     public static final class Builder {
204         private final int version;
205
206         private Long id;
207         private Long index;
208         private Integer maxSegmentSize;
209         private Integer maxEntries;
210         private Long updated;
211
212         Builder(final int version) {
213             this.version = version;
214         }
215
216         /**
217          * Sets the segment identifier.
218          *
219          * @param id The segment identifier.
220          * @return The segment descriptor builder.
221          */
222         public Builder withId(final long id) {
223             this.id = id;
224             return this;
225         }
226
227         /**
228          * Sets the segment index.
229          *
230          * @param index The segment starting index.
231          * @return The segment descriptor builder.
232          */
233         public Builder withIndex(final long index) {
234             this.index = index;
235             return this;
236         }
237
238         /**
239          * Sets maximum count of the segment.
240          *
241          * @param maxSegmentSize The maximum count of the segment.
242          * @return The segment descriptor builder.
243          */
244         public Builder withMaxSegmentSize(final int maxSegmentSize) {
245             this.maxSegmentSize = maxSegmentSize;
246             return this;
247         }
248
249         /**
250          * Sets the maximum number of entries in the segment.
251          *
252          * @param maxEntries The maximum number of entries in the segment.
253          * @return The segment descriptor builder.
254          * @deprecated since 3.0.2
255          */
256         @Deprecated
257         public Builder withMaxEntries(final int maxEntries) {
258             this.maxEntries = maxEntries;
259             return this;
260         }
261
262         /**
263          * Sets updated timestamp;
264          *
265          * @param updated Epoch milliseconds
266          * @return The segment descriptor builder.
267          */
268         public Builder withUpdated(final long updated) {
269             this.updated = updated;
270             return this;
271         }
272
273         /**
274          * Builds the segment descriptor.
275          *
276          * @return The built segment descriptor.
277          */
278         public JournalSegmentDescriptor build() {
279             return new JournalSegmentDescriptor(version,
280                 checkSet(id, "id"),
281                 checkSet(index, "index"),
282                 checkSet(maxSegmentSize, "maxSegmentSize"),
283                 checkSet(maxEntries, "maxEntries"),
284                 checkSet(updated, "updated"),
285                 false);
286         }
287
288         private static <T> @NonNull T checkSet(final @Nullable T obj, final String name) {
289             if (obj != null) {
290                 return obj;
291             }
292             throw new IllegalArgumentException(name + " not set");
293         }
294     }
295 }