Improve segmented journal actor metrics
[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
20 import java.nio.ByteBuffer;
21
22 import static com.google.common.base.MoreObjects.toStringHelper;
23 import static java.util.Objects.requireNonNull;
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 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>
49  * </ul>
50  * The remainder of the 64 segment header bytes are reserved for future metadata.
51  *
52  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
53  */
54 public final class JournalSegmentDescriptor {
55   public static final int BYTES = 64;
56
57   // Current segment version.
58   @VisibleForTesting
59   static final int VERSION = 1;
60
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
68
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
76
77   /**
78    * Returns a descriptor builder.
79    * <p>
80    * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
81    *
82    * @return The descriptor builder.
83    */
84   public static Builder builder() {
85     return new Builder(ByteBuffer.allocate(BYTES));
86   }
87
88   /**
89    * Returns a descriptor builder for the given descriptor buffer.
90    *
91    * @param buffer The descriptor buffer.
92    * @return The descriptor builder.
93    * @throws NullPointerException if {@code buffer} is null
94    */
95   public static Builder builder(ByteBuffer buffer) {
96     return new Builder(buffer);
97   }
98
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;
107
108   /**
109    * @throws NullPointerException if {@code buffer} is null
110    */
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;
120   }
121
122   /**
123    * Returns the segment version.
124    * <p>
125    * Versions are monotonically increasing starting at {@code 1}.
126    *
127    * @return The segment version.
128    */
129   public int version() {
130     return version;
131   }
132
133   /**
134    * Returns the segment identifier.
135    * <p>
136    * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
137    * contain in-sequence indexes.
138    *
139    * @return The segment identifier.
140    */
141   public long id() {
142     return id;
143   }
144
145   /**
146    * Returns the segment index.
147    * <p>
148    * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
149    * increasing thereafter.
150    *
151    * @return The segment index.
152    */
153   public long index() {
154     return index;
155   }
156
157   /**
158    * Returns the maximum count of the segment.
159    *
160    * @return The maximum allowed count of the segment.
161    */
162   public int maxSegmentSize() {
163     return maxSegmentSize;
164   }
165
166   /**
167    * Returns the maximum number of entries allowed in the segment.
168    *
169    * @return The maximum number of entries allowed in the segment.
170    */
171   public int maxEntries() {
172     return maxEntries;
173   }
174
175   /**
176    * Returns last time the segment was updated.
177    * <p>
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}.
181    *
182    * @return The last time the segment was updated in terms of milliseconds since the epoch.
183    */
184   public long updated() {
185     return updated;
186   }
187
188   /**
189    * Writes an update to the descriptor.
190    */
191   public void update(long timestamp) {
192     if (!locked) {
193       buffer.putLong(UPDATED_POSITION, timestamp);
194       this.updated = timestamp;
195     }
196   }
197
198   /**
199    * Copies the segment to a new buffer.
200    */
201   JournalSegmentDescriptor copyTo(ByteBuffer buffer) {
202     buffer.putInt(version);
203     buffer.putLong(id);
204     buffer.putLong(index);
205     buffer.putInt(maxSegmentSize);
206     buffer.putInt(maxEntries);
207     buffer.putLong(updated);
208     buffer.put(locked ? (byte) 1 : (byte) 0);
209     return this;
210   }
211
212   @Override
213   public String toString() {
214     return toStringHelper(this)
215         .add("version", version)
216         .add("id", id)
217         .add("index", index)
218         .add("updated", updated)
219         .toString();
220   }
221
222   /**
223    * Segment descriptor builder.
224    */
225   public static class Builder {
226     private final ByteBuffer buffer;
227
228     private Builder(ByteBuffer buffer) {
229       this.buffer = requireNonNull(buffer, "buffer cannot be null");
230       buffer.putInt(VERSION_POSITION, VERSION);
231     }
232
233     /**
234      * Sets the segment identifier.
235      *
236      * @param id The segment identifier.
237      * @return The segment descriptor builder.
238      */
239     public Builder withId(long id) {
240       buffer.putLong(ID_POSITION, id);
241       return this;
242     }
243
244     /**
245      * Sets the segment index.
246      *
247      * @param index The segment starting index.
248      * @return The segment descriptor builder.
249      */
250     public Builder withIndex(long index) {
251       buffer.putLong(INDEX_POSITION, index);
252       return this;
253     }
254
255     /**
256      * Sets maximum count of the segment.
257      *
258      * @param maxSegmentSize The maximum count of the segment.
259      * @return The segment descriptor builder.
260      */
261     public Builder withMaxSegmentSize(int maxSegmentSize) {
262       buffer.putInt(MAX_SIZE_POSITION, maxSegmentSize);
263       return this;
264     }
265
266     /**
267      * Sets the maximum number of entries in the segment.
268      *
269      * @param maxEntries The maximum number of entries in the segment.
270      * @return The segment descriptor builder.
271      * @deprecated since 3.0.2
272      */
273     @Deprecated
274     public Builder withMaxEntries(int maxEntries) {
275       buffer.putInt(MAX_ENTRIES_POSITION, maxEntries);
276       return this;
277     }
278
279     /**
280      * Builds the segment descriptor.
281      *
282      * @return The built segment descriptor.
283      */
284     public JournalSegmentDescriptor build() {
285       buffer.rewind();
286       return new JournalSegmentDescriptor(buffer);
287     }
288   }
289 }