Refactor JournalSegmentDescriptor
[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.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;
26
27 /**
28  * Stores information about a {@link JournalSegment} of the log.
29  * <p>
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:
32  * <ul>
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
45  *       epoch.
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>
52  * </ul>
53  * The remainder of the 64 segment header bytes are reserved for future metadata.
54  *
55  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
56  */
57 public record JournalSegmentDescriptor(
58         int version,
59         long id,
60         long index,
61         int maxSegmentSize,
62         int maxEntries,
63         long updated,
64         boolean locked) {
65     public static final int BYTES = 64;
66
67     // Current segment version.
68     @VisibleForTesting
69     static final int VERSION = 1;
70
71     /**
72      * Read a JournalSegmentDescriptor from a {@link Path}.
73      *
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
77      */
78     public static @NonNull JournalSegmentDescriptor readFrom(final Path path) throws IOException {
79         final byte[] bytes;
80         try (var is = Files.newInputStream(path, StandardOpenOption.READ)) {
81             bytes = is.readNBytes(BYTES);
82         }
83
84         if (bytes.length != BYTES) {
85             throw new IOException("Need " + BYTES + " bytes, only " + bytes.length + " available");
86         }
87
88         final var buffer = ByteBuffer.wrap(bytes);
89         return new JournalSegmentDescriptor(
90             buffer.getInt(),
91             buffer.getLong(),
92             buffer.getLong(),
93             buffer.getInt(),
94             buffer.getInt(),
95             buffer.getLong(),
96             buffer.get() == 1);
97     }
98
99     /**
100      * Returns the segment version.
101      * <p>
102      * Versions are monotonically increasing starting at {@code 1}.
103      *
104      * @return The segment version.
105      */
106     public int version() {
107         return version;
108     }
109
110     /**
111      * Returns the segment identifier.
112      * <p>
113      * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
114      * contain in-sequence indexes.
115      *
116      * @return The segment identifier.
117      */
118     public long id() {
119         return id;
120     }
121
122     /**
123      * Returns the segment index.
124      * <p>
125      * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
126      * increasing thereafter.
127      *
128      * @return The segment index.
129      */
130     public long index() {
131         return index;
132     }
133
134     /**
135      * Returns the maximum count of the segment.
136      *
137      * @return The maximum allowed count of the segment.
138      */
139     public int maxSegmentSize() {
140         return maxSegmentSize;
141     }
142
143     /**
144      * Returns the maximum number of entries allowed in the segment.
145      *
146      * @return The maximum number of entries allowed in the segment.
147      */
148     public int maxEntries() {
149         return maxEntries;
150     }
151
152     /**
153      * Returns last time the segment was updated.
154      * <p>
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}.
158      *
159      * @return The last time the segment was updated in terms of milliseconds since the epoch.
160      */
161     public long updated() {
162         return updated;
163     }
164
165     /**
166      * Returns this segment as an array of bytes
167      *
168      * @return bytes
169      */
170     byte @NonNull [] toArray() {
171         final var bytes = new byte[BYTES];
172         ByteBuffer.wrap(bytes)
173             .putInt(version)
174             .putLong(id)
175             .putLong(index)
176             .putInt(maxSegmentSize)
177             .putInt(maxEntries)
178             .putLong(updated)
179             .put(locked ? (byte) 1 : (byte) 0);
180         return bytes;
181     }
182
183     /**
184      * Returns a descriptor builder.
185      * <p>
186      * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
187      *
188      * @return The descriptor builder.
189      */
190     public static Builder builder() {
191         return builder(VERSION);
192     }
193
194     /**
195      * Returns a descriptor builder for the given descriptor buffer.
196      *
197      * @param version version to build
198      * @return The descriptor builder.
199      * @throws NullPointerException if {@code buffer} is null
200      */
201     public static Builder builder(final int version) {
202         return new Builder(version);
203     }
204
205     /**
206      * Segment descriptor builder.
207      */
208     public static final class Builder {
209         private final int version;
210
211         private Long id;
212         private Long index;
213         private Integer maxSegmentSize;
214         private Integer maxEntries;
215         private Long updated;
216
217         Builder(final int version) {
218             this.version = version;
219         }
220
221         /**
222          * Sets the segment identifier.
223          *
224          * @param id The segment identifier.
225          * @return The segment descriptor builder.
226          */
227         public Builder withId(final long id) {
228             this.id = id;
229             return this;
230         }
231
232         /**
233          * Sets the segment index.
234          *
235          * @param index The segment starting index.
236          * @return The segment descriptor builder.
237          */
238         public Builder withIndex(final long index) {
239             this.index = index;
240             return this;
241         }
242
243         /**
244          * Sets maximum count of the segment.
245          *
246          * @param maxSegmentSize The maximum count of the segment.
247          * @return The segment descriptor builder.
248          */
249         public Builder withMaxSegmentSize(final int maxSegmentSize) {
250             this.maxSegmentSize = maxSegmentSize;
251             return this;
252         }
253
254         /**
255          * Sets the maximum number of entries in the segment.
256          *
257          * @param maxEntries The maximum number of entries in the segment.
258          * @return The segment descriptor builder.
259          * @deprecated since 3.0.2
260          */
261         @Deprecated
262         public Builder withMaxEntries(final int maxEntries) {
263             this.maxEntries = maxEntries;
264             return this;
265         }
266
267         /**
268          * Sets updated timestamp;
269          *
270          * @param updated Epoch milliseconds
271          * @return The segment descriptor builder.
272          */
273         public Builder withUpdated(final long updated) {
274             this.updated = updated;
275             return this;
276         }
277
278         /**
279          * Builds the segment descriptor.
280          *
281          * @return The built segment descriptor.
282          */
283         public JournalSegmentDescriptor build() {
284             return new JournalSegmentDescriptor(version,
285                 checkSet(id, "id"),
286                 checkSet(index, "index"),
287                 checkSet(maxSegmentSize, "maxSegmentSize"),
288                 checkSet(maxEntries, "maxEntries"),
289                 checkSet(updated, "updated"),
290                 false);
291         }
292
293         private static <T> @NonNull T checkSet(final @Nullable T obj, final String name) {
294             if (obj != null) {
295                 return obj;
296             }
297             throw new IllegalArgumentException(name + " not set");
298         }
299     }
300 }