View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.procedure2.store;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.Arrays;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.TreeMap;
28  
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos;
32  
33  /**
34   * Keeps track of live procedures.
35   *
36   * It can be used by the ProcedureStore to identify which procedures are already
37   * deleted/completed to avoid the deserialization step on restart.
38   */
39  @InterfaceAudience.Private
40  @InterfaceStability.Evolving
41  public class ProcedureStoreTracker {
42    private final TreeMap<Long, BitSetNode> map = new TreeMap<Long, BitSetNode>();
43  
44    private boolean keepDeletes = false;
45    private boolean partial = false;
46  
47    private long minUpdatedProcId = Long.MAX_VALUE;
48    private long maxUpdatedProcId = Long.MIN_VALUE;
49  
50    public enum DeleteState { YES, NO, MAYBE }
51  
52    public static class BitSetNode {
53      private final static long WORD_MASK = 0xffffffffffffffffL;
54      private final static int ADDRESS_BITS_PER_WORD = 6;
55      private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
56      private final static int MAX_NODE_SIZE = 1 << ADDRESS_BITS_PER_WORD;
57  
58      private final boolean partial;
59      private long[] updated;
60      private long[] deleted;
61      private long start;
62  
63      public void dump() {
64        System.out.printf("%06d:%06d min=%d max=%d%n", getStart(), getEnd(),
65          getMinProcId(), getMaxProcId());
66        System.out.println("Update:");
67        for (int i = 0; i < updated.length; ++i) {
68          for (int j = 0; j < BITS_PER_WORD; ++j) {
69            System.out.print((updated[i] & (1L << j)) != 0 ? "1" : "0");
70          }
71          System.out.println(" " + i);
72        }
73        System.out.println();
74        System.out.println("Delete:");
75        for (int i = 0; i < deleted.length; ++i) {
76          for (int j = 0; j < BITS_PER_WORD; ++j) {
77            System.out.print((deleted[i] & (1L << j)) != 0 ? "1" : "0");
78          }
79          System.out.println(" " + i);
80        }
81        System.out.println();
82      }
83  
84      public BitSetNode(final long procId, final boolean partial) {
85        start = alignDown(procId);
86  
87        int count = 1;
88        updated = new long[count];
89        deleted = new long[count];
90        for (int i = 0; i < count; ++i) {
91          updated[i] = 0;
92          deleted[i] = partial ? 0 : WORD_MASK;
93        }
94  
95        this.partial = partial;
96        updateState(procId, false);
97      }
98  
99      protected BitSetNode(final long start, final long[] updated, final long[] deleted) {
100       this.start = start;
101       this.updated = updated;
102       this.deleted = deleted;
103       this.partial = false;
104     }
105 
106     public void update(final long procId) {
107       updateState(procId, false);
108     }
109 
110     public void delete(final long procId) {
111       updateState(procId, true);
112     }
113 
114     public Long getStart() {
115       return start;
116     }
117 
118     public Long getEnd() {
119       return start + (updated.length << ADDRESS_BITS_PER_WORD) - 1;
120     }
121 
122     public boolean contains(final long procId) {
123       return start <= procId && procId <= getEnd();
124     }
125 
126     public DeleteState isDeleted(final long procId) {
127       int bitmapIndex = getBitmapIndex(procId);
128       int wordIndex = bitmapIndex >> ADDRESS_BITS_PER_WORD;
129       if (wordIndex >= deleted.length) {
130         return DeleteState.MAYBE;
131       }
132       return (deleted[wordIndex] & (1L << bitmapIndex)) != 0 ? DeleteState.YES : DeleteState.NO;
133     }
134 
135     private boolean isUpdated(final long procId) {
136       int bitmapIndex = getBitmapIndex(procId);
137       int wordIndex = bitmapIndex >> ADDRESS_BITS_PER_WORD;
138       if (wordIndex >= updated.length) {
139         return false;
140       }
141       return (updated[wordIndex] & (1L << bitmapIndex)) != 0;
142     }
143 
144     public boolean isUpdated() {
145       // TODO: cache the value
146       for (int i = 0; i < updated.length; ++i) {
147         if ((updated[i] | deleted[i]) != WORD_MASK) {
148           return false;
149         }
150       }
151       return true;
152     }
153 
154     public boolean isEmpty() {
155       // TODO: cache the value
156       for (int i = 0; i < deleted.length; ++i) {
157         if (deleted[i] != WORD_MASK) {
158           return false;
159         }
160       }
161       return true;
162     }
163 
164     public void resetUpdates() {
165       for (int i = 0; i < updated.length; ++i) {
166         updated[i] = 0;
167       }
168     }
169 
170     public void undeleteAll() {
171       for (int i = 0; i < updated.length; ++i) {
172         deleted[i] = 0;
173       }
174     }
175 
176     public void unsetPartialFlag() {
177       for (int i = 0; i < updated.length; ++i) {
178         for (int j = 0; j < BITS_PER_WORD; ++j) {
179           if ((updated[i] & (1L << j)) == 0) {
180             deleted[i] |= (1L << j);
181           }
182         }
183       }
184     }
185 
186     public ProcedureProtos.ProcedureStoreTracker.TrackerNode convert() {
187       ProcedureProtos.ProcedureStoreTracker.TrackerNode.Builder builder =
188         ProcedureProtos.ProcedureStoreTracker.TrackerNode.newBuilder();
189       builder.setStartId(start);
190       for (int i = 0; i < updated.length; ++i) {
191         builder.addUpdated(updated[i]);
192         builder.addDeleted(deleted[i]);
193       }
194       return builder.build();
195     }
196 
197     public static BitSetNode convert(ProcedureProtos.ProcedureStoreTracker.TrackerNode data) {
198       long start = data.getStartId();
199       int size = data.getUpdatedCount();
200       long[] updated = new long[size];
201       long[] deleted = new long[size];
202       for (int i = 0; i < size; ++i) {
203         updated[i] = data.getUpdated(i);
204         deleted[i] = data.getDeleted(i);
205       }
206       return new BitSetNode(start, updated, deleted);
207     }
208 
209     // ========================================================================
210     //  Grow/Merge Helpers
211     // ========================================================================
212     public boolean canGrow(final long procId) {
213       return Math.abs(procId - start) < MAX_NODE_SIZE;
214     }
215 
216     public boolean canMerge(final BitSetNode rightNode) {
217       assert start < rightNode.getEnd();
218       return (rightNode.getEnd() - start) < MAX_NODE_SIZE;
219     }
220 
221     public void grow(final long procId) {
222       int delta, offset;
223 
224       if (procId < start) {
225         // add to head
226         long newStart = alignDown(procId);
227         delta = (int)(start - newStart) >> ADDRESS_BITS_PER_WORD;
228         offset = delta;
229         start = newStart;
230       } else {
231         // Add to tail
232         long newEnd = alignUp(procId + 1);
233         delta = (int)(newEnd - getEnd()) >> ADDRESS_BITS_PER_WORD;
234         offset = 0;
235       }
236 
237       long[] newBitmap;
238       int oldSize = updated.length;
239 
240       newBitmap = new long[oldSize + delta];
241       for (int i = 0; i < newBitmap.length; ++i) {
242         newBitmap[i] = 0;
243       }
244       System.arraycopy(updated, 0, newBitmap, offset, oldSize);
245       updated = newBitmap;
246 
247       newBitmap = new long[deleted.length + delta];
248       for (int i = 0; i < newBitmap.length; ++i) {
249         newBitmap[i] = partial ? 0 : WORD_MASK;
250       }
251       System.arraycopy(deleted, 0, newBitmap, offset, oldSize);
252       deleted = newBitmap;
253     }
254 
255     public void merge(final BitSetNode rightNode) {
256       int delta = (int)(rightNode.getEnd() - getEnd()) >> ADDRESS_BITS_PER_WORD;
257 
258       long[] newBitmap;
259       int oldSize = updated.length;
260       int newSize = (delta - rightNode.updated.length);
261       int offset = oldSize + newSize;
262 
263       newBitmap = new long[oldSize + delta];
264       System.arraycopy(updated, 0, newBitmap, 0, oldSize);
265       System.arraycopy(rightNode.updated, 0, newBitmap, offset, rightNode.updated.length);
266       updated = newBitmap;
267 
268       newBitmap = new long[oldSize + delta];
269       System.arraycopy(deleted, 0, newBitmap, 0, oldSize);
270       System.arraycopy(rightNode.deleted, 0, newBitmap, offset, rightNode.deleted.length);
271       deleted = newBitmap;
272 
273       for (int i = 0; i < newSize; ++i) {
274         updated[offset + i] = 0;
275         deleted[offset + i] = partial ? 0 : WORD_MASK;
276       }
277     }
278 
279     @Override
280     public String toString() {
281       return "BitSetNode(" + getStart() + "-" + getEnd() + ")";
282     }
283 
284     // ========================================================================
285     //  Min/Max Helpers
286     // ========================================================================
287     public long getMinProcId() {
288       long minProcId = start;
289       for (int i = 0; i < deleted.length; ++i) {
290         if (deleted[i] == 0) {
291           return(minProcId);
292         }
293 
294         if (deleted[i] != WORD_MASK) {
295           for (int j = 0; j < BITS_PER_WORD; ++j) {
296             if ((deleted[i] & (1L << j)) != 0) {
297               return minProcId + j;
298             }
299           }
300         }
301 
302         minProcId += BITS_PER_WORD;
303       }
304       return minProcId;
305     }
306 
307     public long getMaxProcId() {
308       long maxProcId = getEnd();
309       for (int i = deleted.length - 1; i >= 0; --i) {
310         if (deleted[i] == 0) {
311           return maxProcId;
312         }
313 
314         if (deleted[i] != WORD_MASK) {
315           for (int j = BITS_PER_WORD - 1; j >= 0; --j) {
316             if ((deleted[i] & (1L << j)) == 0) {
317               return maxProcId - (BITS_PER_WORD - 1 - j);
318             }
319           }
320         }
321         maxProcId -= BITS_PER_WORD;
322       }
323       return maxProcId;
324     }
325 
326     // ========================================================================
327     //  Bitmap Helpers
328     // ========================================================================
329     private int getBitmapIndex(final long procId) {
330       return (int)(procId - start);
331     }
332 
333     private void updateState(final long procId, final boolean isDeleted) {
334       int bitmapIndex = getBitmapIndex(procId);
335       int wordIndex = bitmapIndex >> ADDRESS_BITS_PER_WORD;
336       long value = (1L << bitmapIndex);
337 
338       if (isDeleted) {
339         updated[wordIndex] |= value;
340         deleted[wordIndex] |= value;
341       } else {
342         updated[wordIndex] |= value;
343         deleted[wordIndex] &= ~value;
344       }
345     }
346 
347     // ========================================================================
348     //  Helpers
349     // ========================================================================
350     private static long alignUp(final long x) {
351       return (x + (BITS_PER_WORD - 1)) & -BITS_PER_WORD;
352     }
353 
354     private static long alignDown(final long x) {
355       return x & -BITS_PER_WORD;
356     }
357   }
358 
359   public void insert(long procId) {
360     BitSetNode node = getOrCreateNode(procId);
361     node.update(procId);
362     trackProcIds(procId);
363   }
364 
365   public void insert(final long procId, final long[] subProcIds) {
366     update(procId);
367     for (int i = 0; i < subProcIds.length; ++i) {
368       insert(subProcIds[i]);
369     }
370   }
371 
372   public void update(long procId) {
373     Map.Entry<Long, BitSetNode> entry = map.floorEntry(procId);
374     assert entry != null : "expected node to update procId=" + procId;
375 
376     BitSetNode node = entry.getValue();
377     assert node.contains(procId);
378     node.update(procId);
379     trackProcIds(procId);
380   }
381 
382   public void delete(long procId) {
383     Map.Entry<Long, BitSetNode> entry = map.floorEntry(procId);
384     assert entry != null : "expected node to delete procId=" + procId;
385 
386     BitSetNode node = entry.getValue();
387     assert node.contains(procId) : "expected procId in the node";
388     node.delete(procId);
389 
390     if (!keepDeletes && node.isEmpty()) {
391       // TODO: RESET if (map.size() == 1)
392       map.remove(entry.getKey());
393     }
394 
395     trackProcIds(procId);
396   }
397 
398   public void delete(long[] procIds) {
399     // TODO: optimize
400     Arrays.sort(procIds);
401     for (int i = 0; i < procIds.length; ++i) {
402       delete(procIds[i]);
403     }
404   }
405 
406   private void trackProcIds(long procId) {
407     minUpdatedProcId = Math.min(minUpdatedProcId, procId);
408     maxUpdatedProcId = Math.max(maxUpdatedProcId, procId);
409   }
410 
411   public long getUpdatedMinProcId() {
412     return minUpdatedProcId;
413   }
414 
415   public long getUpdatedMaxProcId() {
416     return maxUpdatedProcId;
417   }
418 
419   @InterfaceAudience.Private
420   public void setDeleted(final long procId, final boolean isDeleted) {
421     BitSetNode node = getOrCreateNode(procId);
422     assert node.contains(procId) : "expected procId=" + procId + " in the node=" + node;
423     node.updateState(procId, isDeleted);
424   }
425 
426   public void reset() {
427     this.keepDeletes = false;
428     this.partial = false;
429     this.map.clear();
430     resetUpdates();
431   }
432 
433   public DeleteState isDeleted(long procId) {
434     Map.Entry<Long, BitSetNode> entry = map.floorEntry(procId);
435     if (entry != null && entry.getValue().contains(procId)) {
436       BitSetNode node = entry.getValue();
437       DeleteState state = node.isDeleted(procId);
438       return partial && !node.isUpdated(procId) ? DeleteState.MAYBE : state;
439     }
440     return partial ? DeleteState.MAYBE : DeleteState.YES;
441   }
442 
443   public long getMinProcId() {
444     // TODO: Cache?
445     Map.Entry<Long, BitSetNode> entry = map.firstEntry();
446     return entry == null ? 0 : entry.getValue().getMinProcId();
447   }
448 
449   public void setKeepDeletes(boolean keepDeletes) {
450     this.keepDeletes = keepDeletes;
451     if (!keepDeletes) {
452       Iterator<Map.Entry<Long, BitSetNode>> it = map.entrySet().iterator();
453       while (it.hasNext()) {
454         Map.Entry<Long, BitSetNode> entry = it.next();
455         if (entry.getValue().isEmpty()) {
456           it.remove();
457         }
458       }
459     }
460   }
461 
462   public void setPartialFlag(boolean isPartial) {
463     if (this.partial && !isPartial) {
464       for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
465         entry.getValue().unsetPartialFlag();
466       }
467     }
468     this.partial = isPartial;
469   }
470 
471   public boolean isEmpty() {
472     for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
473       if (entry.getValue().isEmpty() == false) {
474         return false;
475       }
476     }
477     return true;
478   }
479 
480   public boolean isUpdated() {
481     for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
482       if (entry.getValue().isUpdated() == false) {
483         return false;
484       }
485     }
486     return true;
487   }
488 
489   public boolean isTracking(long minId, long maxId) {
490     // TODO: we can make it more precise, instead of looking just at the block
491     return map.floorEntry(minId) != null || map.floorEntry(maxId) != null;
492   }
493 
494   public void resetUpdates() {
495     for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
496       entry.getValue().resetUpdates();
497     }
498     minUpdatedProcId = Long.MAX_VALUE;
499     maxUpdatedProcId = Long.MIN_VALUE;
500   }
501 
502   public void undeleteAll() {
503     for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
504       entry.getValue().undeleteAll();
505     }
506   }
507 
508   private BitSetNode getOrCreateNode(final long procId) {
509     // can procId fit in the left node?
510     BitSetNode leftNode = null;
511     boolean leftCanGrow = false;
512     Map.Entry<Long, BitSetNode> leftEntry = map.floorEntry(procId);
513     if (leftEntry != null) {
514       leftNode = leftEntry.getValue();
515       if (leftNode.contains(procId)) {
516         return leftNode;
517       }
518       leftCanGrow = leftNode.canGrow(procId);
519     }
520 
521     BitSetNode rightNode = null;
522     boolean rightCanGrow = false;
523     Map.Entry<Long, BitSetNode> rightEntry = map.ceilingEntry(procId);
524     if (rightEntry != null) {
525       rightNode = rightEntry.getValue();
526       rightCanGrow = rightNode.canGrow(procId);
527       if (leftNode != null) {
528         if (leftNode.canMerge(rightNode)) {
529           // merge left and right node
530           return mergeNodes(leftNode, rightNode);
531         }
532 
533         if (leftCanGrow && rightCanGrow) {
534           if ((procId - leftNode.getEnd()) <= (rightNode.getStart() - procId)) {
535             // grow the left node
536             return growNode(leftNode, procId);
537           }
538           // grow the right node
539           return growNode(rightNode, procId);
540         }
541       }
542     }
543 
544     // grow the left node
545     if (leftCanGrow) {
546       return growNode(leftNode, procId);
547     }
548 
549     // grow the right node
550     if (rightCanGrow) {
551       return growNode(rightNode, procId);
552     }
553 
554     // add new node
555     BitSetNode node = new BitSetNode(procId, partial);
556     map.put(node.getStart(), node);
557     return node;
558   }
559 
560   private BitSetNode growNode(BitSetNode node, long procId) {
561     map.remove(node.getStart());
562     node.grow(procId);
563     map.put(node.getStart(), node);
564     return node;
565   }
566 
567   private BitSetNode mergeNodes(BitSetNode leftNode, BitSetNode rightNode) {
568     assert leftNode.getStart() < rightNode.getStart();
569     leftNode.merge(rightNode);
570     map.remove(rightNode.getStart());
571     return leftNode;
572   }
573 
574   public void dump() {
575     System.out.println("map " + map.size());
576     System.out.println("isUpdated " + isUpdated());
577     System.out.println("isEmpty " + isEmpty());
578     for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
579       entry.getValue().dump();
580     }
581   }
582 
583   public void writeTo(final OutputStream stream) throws IOException {
584     ProcedureProtos.ProcedureStoreTracker.Builder builder =
585         ProcedureProtos.ProcedureStoreTracker.newBuilder();
586     for (Map.Entry<Long, BitSetNode> entry : map.entrySet()) {
587       builder.addNode(entry.getValue().convert());
588     }
589     builder.build().writeDelimitedTo(stream);
590   }
591 
592   public void readFrom(final InputStream stream) throws IOException {
593     reset();
594     final ProcedureProtos.ProcedureStoreTracker data =
595         ProcedureProtos.ProcedureStoreTracker.parseDelimitedFrom(stream);
596     for (ProcedureProtos.ProcedureStoreTracker.TrackerNode protoNode: data.getNodeList()) {
597       final BitSetNode node = BitSetNode.convert(protoNode);
598       map.put(node.getStart(), node);
599     }
600   }
601 }