1919
2020package org .elasticsearch .index .seqno ;
2121
22+ import com .carrotsearch .hppc .LongObjectHashMap ;
2223import org .apache .lucene .util .FixedBitSet ;
2324import org .elasticsearch .common .SuppressForbidden ;
24- import org .elasticsearch .common .settings .Setting ;
25- import org .elasticsearch .index .IndexSettings ;
26-
27- import java .util .LinkedList ;
2825
2926/**
3027 * This class generates sequences numbers and keeps track of the so-called "local checkpoint" which is the highest number for which all
3330public class LocalCheckpointTracker {
3431
3532 /**
36- * We keep a bit for each sequence number that is still pending. To optimize allocation, we do so in multiple arrays allocating them on
37- * demand and cleaning up while completed. This constant controls the size of the arrays .
33+ * We keep a bit for each sequence number that is still pending. To optimize allocation, we do so in multiple sets allocating them on
34+ * demand and cleaning up while completed. This constant controls the size of the sets .
3835 */
39- static final int BIT_ARRAYS_SIZE = 1024 ;
36+ static final int BIT_SET_SIZE = 1024 ;
4037
4138 /**
42- * An ordered list of bit arrays representing pending sequence numbers. The list is "anchored" in {@link #firstProcessedSeqNo} which
43- * marks the sequence number the fist bit in the first array corresponds to .
39+ * A collection of bit sets representing pending sequence numbers. Each sequence number is mapped to a bit set by dividing by the
40+ * bit set size .
4441 */
45- final LinkedList <FixedBitSet > processedSeqNo = new LinkedList <>();
46-
47- /**
48- * The sequence number that the first bit in the first array corresponds to.
49- */
50- long firstProcessedSeqNo ;
42+ final LongObjectHashMap <FixedBitSet > processedSeqNo = new LongObjectHashMap <>();
5143
5244 /**
5345 * The current local checkpoint, i.e., all sequence numbers no more than this number have been completed.
@@ -77,7 +69,6 @@ public LocalCheckpointTracker(final long maxSeqNo, final long localCheckpoint) {
7769 throw new IllegalArgumentException (
7870 "max seq. no. must be non-negative or [" + SequenceNumbers .NO_OPS_PERFORMED + "] but was [" + maxSeqNo + "]" );
7971 }
80- firstProcessedSeqNo = localCheckpoint == SequenceNumbers .NO_OPS_PERFORMED ? 0 : localCheckpoint + 1 ;
8172 nextSeqNo = maxSeqNo == SequenceNumbers .NO_OPS_PERFORMED ? 0 : maxSeqNo + 1 ;
8273 checkpoint = localCheckpoint ;
8374 }
@@ -122,7 +113,6 @@ synchronized void resetCheckpoint(final long checkpoint) {
122113 assert checkpoint != SequenceNumbers .UNASSIGNED_SEQ_NO ;
123114 assert checkpoint <= this .checkpoint ;
124115 processedSeqNo .clear ();
125- firstProcessedSeqNo = checkpoint + 1 ;
126116 this .checkpoint = checkpoint ;
127117 }
128118
@@ -175,24 +165,28 @@ synchronized void waitForOpsToComplete(final long seqNo) throws InterruptedExcep
175165 @ SuppressForbidden (reason = "Object#notifyAll" )
176166 private void updateCheckpoint () {
177167 assert Thread .holdsLock (this );
178- assert checkpoint < firstProcessedSeqNo + BIT_ARRAYS_SIZE - 1 :
179- "checkpoint should be below the end of the first bit set (o.w. current bit set is completed and shouldn't be there)" ;
180- assert getBitSetForSeqNo (checkpoint + 1 ) == processedSeqNo .getFirst () :
181- "checkpoint + 1 doesn't point to the first bit set (o.w. current bit set is completed and shouldn't be there)" ;
182168 assert getBitSetForSeqNo (checkpoint + 1 ).get (seqNoToBitSetOffset (checkpoint + 1 )) :
183169 "updateCheckpoint is called but the bit following the checkpoint is not set" ;
184170 try {
185171 // keep it simple for now, get the checkpoint one by one; in the future we can optimize and read words
186- FixedBitSet current = processedSeqNo .getFirst ();
172+ long bitSetKey = getBitSetKey (checkpoint );
173+ FixedBitSet current = processedSeqNo .get (bitSetKey );
174+ if (current == null ) {
175+ // the bit set corresponding to the checkpoint has already been removed, set ourselves up for the next bit set
176+ assert checkpoint % BIT_SET_SIZE == BIT_SET_SIZE - 1 ;
177+ current = processedSeqNo .get (++bitSetKey );
178+ }
187179 do {
188180 checkpoint ++;
189- // the checkpoint always falls in the first bit set or just before. If it falls
190- // on the last bit of the current bit set, we can clean it.
191- if (checkpoint == firstProcessedSeqNo + BIT_ARRAYS_SIZE - 1 ) {
192- processedSeqNo .removeFirst ();
193- firstProcessedSeqNo += BIT_ARRAYS_SIZE ;
194- assert checkpoint - firstProcessedSeqNo < BIT_ARRAYS_SIZE ;
195- current = processedSeqNo .peekFirst ();
181+ /*
182+ * The checkpoint always falls in the current bit set or we have already cleaned it; if it falls on the last bit of the
183+ * current bit set, we can clean it.
184+ */
185+ if (checkpoint == lastSeqNoInBitSet (bitSetKey )) {
186+ assert current != null ;
187+ final FixedBitSet removed = processedSeqNo .remove (bitSetKey );
188+ assert removed == current ;
189+ current = processedSeqNo .get (++bitSetKey );
196190 }
197191 } while (current != null && current .get (seqNoToBitSetOffset (checkpoint + 1 )));
198192 } finally {
@@ -201,37 +195,45 @@ assert getBitSetForSeqNo(checkpoint + 1).get(seqNoToBitSetOffset(checkpoint + 1)
201195 }
202196 }
203197
198+ private long lastSeqNoInBitSet (final long bitSetKey ) {
199+ return (1 + bitSetKey ) * BIT_SET_SIZE - 1 ;
200+ }
201+
204202 /**
205- * Return the bit array for the provided sequence number, possibly allocating a new array if needed.
203+ * Return the bit set for the provided sequence number, possibly allocating a new set if needed.
206204 *
207- * @param seqNo the sequence number to obtain the bit array for
208- * @return the bit array corresponding to the provided sequence number
205+ * @param seqNo the sequence number to obtain the bit set for
206+ * @return the bit set corresponding to the provided sequence number
209207 */
208+ private long getBitSetKey (final long seqNo ) {
209+ assert Thread .holdsLock (this );
210+ return seqNo / BIT_SET_SIZE ;
211+ }
212+
210213 private FixedBitSet getBitSetForSeqNo (final long seqNo ) {
211214 assert Thread .holdsLock (this );
212- assert seqNo >= firstProcessedSeqNo : " seqNo: " + seqNo + " firstProcessedSeqNo: " + firstProcessedSeqNo ;
213- final long bitSetOffset = ( seqNo - firstProcessedSeqNo ) / BIT_ARRAYS_SIZE ;
214- if ( bitSetOffset > Integer . MAX_VALUE ) {
215- throw new IndexOutOfBoundsException (
216- "sequence number too high; got [" + seqNo + "], firstProcessedSeqNo [" + firstProcessedSeqNo + "]" );
217- }
218- while ( bitSetOffset >= processedSeqNo . size ()) {
219- processedSeqNo .add ( new FixedBitSet ( BIT_ARRAYS_SIZE ) );
215+ final long bitSetKey = getBitSetKey ( seqNo ) ;
216+ final int index = processedSeqNo . indexOf ( bitSetKey ) ;
217+ final FixedBitSet bitSet ;
218+ if ( processedSeqNo . indexExists ( index )) {
219+ bitSet = processedSeqNo . indexGet ( index );
220+ } else {
221+ bitSet = new FixedBitSet ( BIT_SET_SIZE );
222+ processedSeqNo .indexInsert ( index , bitSetKey , bitSet );
220223 }
221- return processedSeqNo . get (( int ) bitSetOffset ) ;
224+ return bitSet ;
222225 }
223226
224227 /**
225- * Obtain the position in the bit array corresponding to the provided sequence number. The bit array corresponding to the sequence
226- * number can be obtained via {@link #getBitSetForSeqNo(long)}.
228+ * Obtain the position in the bit set corresponding to the provided sequence number. The bit set corresponding to the sequence number
229+ * can be obtained via {@link #getBitSetForSeqNo(long)}.
227230 *
228231 * @param seqNo the sequence number to obtain the position for
229- * @return the position in the bit array corresponding to the provided sequence number
232+ * @return the position in the bit set corresponding to the provided sequence number
230233 */
231234 private int seqNoToBitSetOffset (final long seqNo ) {
232235 assert Thread .holdsLock (this );
233- assert seqNo >= firstProcessedSeqNo ;
234- return ((int ) (seqNo - firstProcessedSeqNo )) % BIT_ARRAYS_SIZE ;
236+ return Math .toIntExact (seqNo % BIT_SET_SIZE );
235237 }
236238
237239}
0 commit comments