1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.regionserver;
20
21 import static org.apache.hadoop.util.StringUtils.humanReadableInt;
22
23 import java.io.IOException;
24 import java.lang.Thread.UncaughtExceptionHandler;
25 import java.lang.management.MemoryUsage;
26 import java.util.ArrayList;
27 import java.util.ConcurrentModificationException;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.Set;
34 import java.util.SortedMap;
35 import java.util.concurrent.BlockingQueue;
36 import java.util.concurrent.DelayQueue;
37 import java.util.concurrent.Delayed;
38 import java.util.concurrent.ThreadFactory;
39 import java.util.concurrent.TimeUnit;
40 import java.util.concurrent.atomic.AtomicBoolean;
41 import java.util.concurrent.locks.ReentrantReadWriteLock;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45 import org.apache.hadoop.conf.Configuration;
46 import org.apache.hadoop.hbase.DroppedSnapshotException;
47 import org.apache.hadoop.hbase.HConstants;
48 import org.apache.hadoop.hbase.RemoteExceptionHandler;
49 import org.apache.hadoop.hbase.classification.InterfaceAudience;
50 import org.apache.hadoop.hbase.client.RegionReplicaUtil;
51 import org.apache.hadoop.hbase.io.util.HeapMemorySizeUtil;
52 import org.apache.hadoop.hbase.regionserver.Region.FlushResult;
53 import org.apache.hadoop.hbase.util.Bytes;
54 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
55 import org.apache.hadoop.hbase.util.HasThread;
56 import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
57 import org.apache.hadoop.hbase.util.Threads;
58 import org.apache.hadoop.util.StringUtils;
59 import org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
60 import org.apache.htrace.Trace;
61 import org.apache.htrace.TraceScope;
62 import org.apache.hadoop.hbase.util.Counter;
63
64 import com.google.common.base.Preconditions;
65
66
67
68
69
70
71
72
73
74
75 @InterfaceAudience.Private
76 class MemStoreFlusher implements FlushRequester {
77 private static final Log LOG = LogFactory.getLog(MemStoreFlusher.class);
78
79 private Configuration conf;
80
81
82 private final BlockingQueue<FlushQueueEntry> flushQueue =
83 new DelayQueue<FlushQueueEntry>();
84 private final Map<Region, FlushRegionEntry> regionsInQueue =
85 new HashMap<Region, FlushRegionEntry>();
86 private AtomicBoolean wakeupPending = new AtomicBoolean();
87
88 private final long threadWakeFrequency;
89 private final HRegionServer server;
90 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
91 private final Object blockSignal = new Object();
92
93 protected long globalMemStoreLimit;
94 protected float globalMemStoreLimitLowMarkPercent;
95 protected long globalMemStoreLimitLowMark;
96
97 private long blockingWaitTime;
98 private final Counter updatesBlockedMsHighWater = new Counter();
99
100 private final FlushHandler[] flushHandlers;
101 private List<FlushRequestListener> flushRequestListeners = new ArrayList<FlushRequestListener>(1);
102
103
104
105
106
107 public MemStoreFlusher(final Configuration conf,
108 final HRegionServer server) {
109 super();
110 this.conf = conf;
111 this.server = server;
112 this.threadWakeFrequency =
113 conf.getLong(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000);
114 long max = -1L;
115 final MemoryUsage usage = HeapMemorySizeUtil.safeGetHeapMemoryUsage();
116 if (usage != null) {
117 max = usage.getMax();
118 }
119 float globalMemStorePercent = HeapMemorySizeUtil.getGlobalMemStorePercent(conf, true);
120 this.globalMemStoreLimit = (long) (max * globalMemStorePercent);
121 this.globalMemStoreLimitLowMarkPercent =
122 HeapMemorySizeUtil.getGlobalMemStoreLowerMark(conf, globalMemStorePercent);
123 this.globalMemStoreLimitLowMark =
124 (long) (this.globalMemStoreLimit * this.globalMemStoreLimitLowMarkPercent);
125
126 this.blockingWaitTime = conf.getInt("hbase.hstore.blockingWaitTime",
127 90000);
128 int handlerCount = conf.getInt("hbase.hstore.flusher.count", 2);
129 this.flushHandlers = new FlushHandler[handlerCount];
130 LOG.info("globalMemStoreLimit="
131 + TraditionalBinaryPrefix.long2String(this.globalMemStoreLimit, "", 1)
132 + ", globalMemStoreLimitLowMark="
133 + TraditionalBinaryPrefix.long2String(this.globalMemStoreLimitLowMark, "", 1)
134 + ", maxHeap=" + TraditionalBinaryPrefix.long2String(max, "", 1));
135 }
136
137 public Counter getUpdatesBlockedMsHighWater() {
138 return this.updatesBlockedMsHighWater;
139 }
140
141
142
143
144
145
146
147 private boolean flushOneForGlobalPressure() {
148 SortedMap<Long, Region> regionsBySize = server.getCopyOfOnlineRegionsSortedBySize();
149 Set<Region> excludedRegions = new HashSet<Region>();
150
151 double secondaryMultiplier
152 = ServerRegionReplicaUtil.getRegionReplicaStoreFileRefreshMultiplier(conf);
153
154 boolean flushedOne = false;
155 while (!flushedOne) {
156
157
158 Region bestFlushableRegion = getBiggestMemstoreRegion(regionsBySize, excludedRegions, true);
159
160 Region bestAnyRegion = getBiggestMemstoreRegion(
161 regionsBySize, excludedRegions, false);
162
163 Region bestRegionReplica = getBiggestMemstoreOfRegionReplica(regionsBySize,
164 excludedRegions);
165
166 if (bestAnyRegion == null && bestRegionReplica == null) {
167 LOG.error("Above memory mark but there are no flushable regions!");
168 return false;
169 }
170
171 Region regionToFlush;
172 if (bestFlushableRegion != null &&
173 bestAnyRegion.getMemstoreSize() > 2 * bestFlushableRegion.getMemstoreSize()) {
174
175
176
177
178 if (LOG.isDebugEnabled()) {
179 LOG.debug("Under global heap pressure: " + "Region "
180 + bestAnyRegion.getRegionInfo().getRegionNameAsString()
181 + " has too many " + "store files, but is "
182 + TraditionalBinaryPrefix.long2String(bestAnyRegion.getMemstoreSize(), "", 1)
183 + " vs best flushable region's "
184 + TraditionalBinaryPrefix.long2String(bestFlushableRegion.getMemstoreSize(), "", 1)
185 + ". Choosing the bigger.");
186 }
187 regionToFlush = bestAnyRegion;
188 } else {
189 if (bestFlushableRegion == null) {
190 regionToFlush = bestAnyRegion;
191 } else {
192 regionToFlush = bestFlushableRegion;
193 }
194 }
195
196 Preconditions.checkState(
197 (regionToFlush != null && regionToFlush.getMemstoreSize() > 0) ||
198 (bestRegionReplica != null && bestRegionReplica.getMemstoreSize() > 0));
199
200 if (regionToFlush == null ||
201 (bestRegionReplica != null &&
202 ServerRegionReplicaUtil.isRegionReplicaStoreFileRefreshEnabled(conf) &&
203 (bestRegionReplica.getMemstoreSize()
204 > secondaryMultiplier * regionToFlush.getMemstoreSize()))) {
205 LOG.info("Refreshing storefiles of region " + bestRegionReplica +
206 " due to global heap pressure. memstore size=" + StringUtils.humanReadableInt(
207 server.getRegionServerAccounting().getGlobalMemstoreSize()));
208 flushedOne = refreshStoreFilesAndReclaimMemory(bestRegionReplica);
209 if (!flushedOne) {
210 LOG.info("Excluding secondary region " + bestRegionReplica +
211 " - trying to find a different region to refresh files.");
212 excludedRegions.add(bestRegionReplica);
213 }
214 } else {
215 LOG.info("Flush of region " + regionToFlush + " due to global heap pressure. "
216 + "Total Memstore size="
217 + humanReadableInt(server.getRegionServerAccounting().getGlobalMemstoreSize())
218 + ", Region memstore size="
219 + humanReadableInt(regionToFlush.getMemstoreSize()));
220 flushedOne = flushRegion(regionToFlush, true, false);
221
222 if (!flushedOne) {
223 LOG.info("Excluding unflushable region " + regionToFlush +
224 " - trying to find a different region to flush.");
225 excludedRegions.add(regionToFlush);
226 }
227 }
228 }
229 return true;
230 }
231
232 private class FlushHandler extends HasThread {
233
234 private FlushHandler(String name) {
235 super(name);
236 }
237
238 @Override
239 public void run() {
240 while (!server.isStopped()) {
241 FlushQueueEntry fqe = null;
242 try {
243 wakeupPending.set(false);
244 fqe = flushQueue.poll(threadWakeFrequency, TimeUnit.MILLISECONDS);
245 if (fqe == null || fqe instanceof WakeupFlushThread) {
246 if (isAboveLowWaterMark()) {
247 LOG.debug("Flush thread woke up because memory above low water="
248 + TraditionalBinaryPrefix.long2String(globalMemStoreLimitLowMark, "", 1));
249 if (!flushOneForGlobalPressure()) {
250
251
252
253
254
255 Thread.sleep(1000);
256 wakeUpIfBlocking();
257 }
258
259 wakeupFlushThread();
260 }
261 continue;
262 }
263 FlushRegionEntry fre = (FlushRegionEntry) fqe;
264 if (!flushRegion(fre)) {
265 break;
266 }
267 } catch (InterruptedException ex) {
268 continue;
269 } catch (ConcurrentModificationException ex) {
270 continue;
271 } catch (Exception ex) {
272 LOG.error("Cache flusher failed for entry " + fqe, ex);
273 if (!server.checkFileSystem()) {
274 break;
275 }
276 }
277 }
278 synchronized (regionsInQueue) {
279 regionsInQueue.clear();
280 flushQueue.clear();
281 }
282
283
284 wakeUpIfBlocking();
285 LOG.info(getName() + " exiting");
286 }
287 }
288
289
290 private void wakeupFlushThread() {
291 if (wakeupPending.compareAndSet(false, true)) {
292 flushQueue.add(new WakeupFlushThread());
293 }
294 }
295
296 private Region getBiggestMemstoreRegion(
297 SortedMap<Long, Region> regionsBySize,
298 Set<Region> excludedRegions,
299 boolean checkStoreFileCount) {
300 synchronized (regionsInQueue) {
301 for (Region region : regionsBySize.values()) {
302 if (excludedRegions.contains(region)) {
303 continue;
304 }
305
306 if (((HRegion)region).writestate.flushing ||
307 !((HRegion)region).writestate.writesEnabled) {
308 continue;
309 }
310
311 if (checkStoreFileCount && isTooManyStoreFiles(region)) {
312 continue;
313 }
314 return region;
315 }
316 }
317 return null;
318 }
319
320 private Region getBiggestMemstoreOfRegionReplica(SortedMap<Long, Region> regionsBySize,
321 Set<Region> excludedRegions) {
322 synchronized (regionsInQueue) {
323 for (Region region : regionsBySize.values()) {
324 if (excludedRegions.contains(region)) {
325 continue;
326 }
327
328 if (RegionReplicaUtil.isDefaultReplica(region.getRegionInfo())) {
329 continue;
330 }
331
332 return region;
333 }
334 }
335 return null;
336 }
337
338 private boolean refreshStoreFilesAndReclaimMemory(Region region) {
339 try {
340 return region.refreshStoreFiles();
341 } catch (IOException e) {
342 LOG.warn("Refreshing store files failed with exception", e);
343 }
344 return false;
345 }
346
347
348
349
350 private boolean isAboveHighWaterMark() {
351 return server.getRegionServerAccounting().
352 getGlobalMemstoreSize() >= globalMemStoreLimit;
353 }
354
355
356
357
358 private boolean isAboveLowWaterMark() {
359 return server.getRegionServerAccounting().
360 getGlobalMemstoreSize() >= globalMemStoreLimitLowMark;
361 }
362
363 @Override
364 public boolean requestFlush(Region r, boolean forceFlushAllStores) {
365 synchronized (regionsInQueue) {
366 if (!regionsInQueue.containsKey(r)) {
367
368
369 FlushRegionEntry fqe = new FlushRegionEntry(r, forceFlushAllStores);
370 this.regionsInQueue.put(r, fqe);
371 this.flushQueue.add(fqe);
372 ((HRegion)r).incrementFlushesQueuedCount();
373 return true;
374 }
375 return false;
376 }
377 }
378
379 @Override
380 public boolean requestDelayedFlush(Region r, long delay, boolean forceFlushAllStores) {
381 synchronized (regionsInQueue) {
382 if (!regionsInQueue.containsKey(r)) {
383
384 FlushRegionEntry fqe = new FlushRegionEntry(r, forceFlushAllStores);
385 fqe.requeue(delay);
386 this.regionsInQueue.put(r, fqe);
387 this.flushQueue.add(fqe);
388 ((HRegion)r).incrementFlushesQueuedCount();
389 return true;
390 }
391 return false;
392 }
393 }
394
395 public int getFlushQueueSize() {
396 return flushQueue.size();
397 }
398
399
400
401
402 void interruptIfNecessary() {
403 lock.writeLock().lock();
404 try {
405 for (FlushHandler flushHander : flushHandlers) {
406 if (flushHander != null) flushHander.interrupt();
407 }
408 } finally {
409 lock.writeLock().unlock();
410 }
411 }
412
413 synchronized void start(UncaughtExceptionHandler eh) {
414 ThreadFactory flusherThreadFactory = Threads.newDaemonThreadFactory(
415 server.getServerName().toShortString() + "-MemStoreFlusher", eh);
416 for (int i = 0; i < flushHandlers.length; i++) {
417 flushHandlers[i] = new FlushHandler("MemStoreFlusher." + i);
418 flusherThreadFactory.newThread(flushHandlers[i]);
419 flushHandlers[i].start();
420 }
421 }
422
423 boolean isAlive() {
424 for (FlushHandler flushHander : flushHandlers) {
425 if (flushHander != null && flushHander.isAlive()) {
426 return true;
427 }
428 }
429 return false;
430 }
431
432 void join() {
433 for (FlushHandler flushHander : flushHandlers) {
434 if (flushHander != null) {
435 Threads.shutdown(flushHander.getThread());
436 }
437 }
438 }
439
440
441
442
443
444
445
446
447
448 private boolean flushRegion(final FlushRegionEntry fqe) {
449 Region region = fqe.region;
450 if (!region.getRegionInfo().isMetaRegion() &&
451 isTooManyStoreFiles(region)) {
452 if (fqe.isMaximumWait(this.blockingWaitTime)) {
453 LOG.info("Waited " + (EnvironmentEdgeManager.currentTime() - fqe.createTime) +
454 "ms on a compaction to clean up 'too many store files'; waited " +
455 "long enough... proceeding with flush of " +
456 region.getRegionInfo().getRegionNameAsString());
457 } else {
458
459 if (fqe.getRequeueCount() <= 0) {
460
461 LOG.warn("Region " + region.getRegionInfo().getRegionNameAsString() + " has too many " +
462 "store files; delaying flush up to " + this.blockingWaitTime + "ms");
463 if (!this.server.compactSplitThread.requestSplit(region)) {
464 try {
465 this.server.compactSplitThread.requestSystemCompaction(
466 region, Thread.currentThread().getName());
467 } catch (IOException e) {
468 LOG.error("Cache flush failed for region " +
469 Bytes.toStringBinary(region.getRegionInfo().getRegionName()),
470 RemoteExceptionHandler.checkIOException(e));
471 }
472 }
473 }
474
475
476
477 this.flushQueue.add(fqe.requeue(this.blockingWaitTime / 100));
478
479 return true;
480 }
481 }
482 return flushRegion(region, false, fqe.isForceFlushAllStores());
483 }
484
485
486
487
488
489
490
491
492
493
494
495
496
497 private boolean flushRegion(final Region region, final boolean emergencyFlush,
498 boolean forceFlushAllStores) {
499 synchronized (this.regionsInQueue) {
500 FlushRegionEntry fqe = this.regionsInQueue.remove(region);
501
502 if (fqe != null && emergencyFlush) {
503
504
505 flushQueue.remove(fqe);
506 }
507 }
508
509 lock.readLock().lock();
510 try {
511 notifyFlushRequest(region, emergencyFlush);
512 FlushResult flushResult = region.flush(forceFlushAllStores);
513 boolean shouldCompact = flushResult.isCompactionNeeded();
514
515 boolean shouldSplit = ((HRegion)region).checkSplit() != null;
516 if (shouldSplit) {
517 this.server.compactSplitThread.requestSplit(region);
518 } else if (shouldCompact) {
519 server.compactSplitThread.requestSystemCompaction(
520 region, Thread.currentThread().getName());
521 }
522 } catch (DroppedSnapshotException ex) {
523
524
525
526
527
528 server.abort("Replay of WAL required. Forcing server shutdown", ex);
529 return false;
530 } catch (IOException ex) {
531 LOG.error("Cache flush failed" + (region != null ? (" for region " +
532 Bytes.toStringBinary(region.getRegionInfo().getRegionName())) : ""),
533 RemoteExceptionHandler.checkIOException(ex));
534 if (!server.checkFileSystem()) {
535 return false;
536 }
537 } finally {
538 lock.readLock().unlock();
539 wakeUpIfBlocking();
540 }
541 return true;
542 }
543
544 private void notifyFlushRequest(Region region, boolean emergencyFlush) {
545 FlushType type = FlushType.NORMAL;
546 if (emergencyFlush) {
547 type = isAboveHighWaterMark() ? FlushType.ABOVE_HIGHER_MARK : FlushType.ABOVE_LOWER_MARK;
548 }
549 for (FlushRequestListener listener : flushRequestListeners) {
550 listener.flushRequested(type, region);
551 }
552 }
553
554 private void wakeUpIfBlocking() {
555 synchronized (blockSignal) {
556 blockSignal.notifyAll();
557 }
558 }
559
560 private boolean isTooManyStoreFiles(Region region) {
561
562
563 if (!region.getTableDesc().isCompactionEnabled()) {
564 return false;
565 }
566
567 for (Store store : region.getStores()) {
568 if (store.hasTooManyStoreFiles()) {
569 return true;
570 }
571 }
572 return false;
573 }
574
575
576
577
578
579
580
581 public void reclaimMemStoreMemory() {
582 TraceScope scope = Trace.startSpan("MemStoreFluser.reclaimMemStoreMemory");
583 if (isAboveHighWaterMark()) {
584 if (Trace.isTracing()) {
585 scope.getSpan().addTimelineAnnotation("Force Flush. We're above high water mark.");
586 }
587 long start = EnvironmentEdgeManager.currentTime();
588 synchronized (this.blockSignal) {
589 boolean blocked = false;
590 long startTime = 0;
591 boolean interrupted = false;
592 try {
593 while (isAboveHighWaterMark() && !server.isStopped()) {
594 if (!blocked) {
595 startTime = EnvironmentEdgeManager.currentTime();
596 LOG.info("Blocking updates on "
597 + server.toString()
598 + ": the global memstore size "
599 + TraditionalBinaryPrefix.long2String(server.getRegionServerAccounting()
600 .getGlobalMemstoreSize(), "", 1) + " is >= than blocking "
601 + TraditionalBinaryPrefix.long2String(globalMemStoreLimit, "", 1) + " size");
602 }
603 blocked = true;
604 wakeupFlushThread();
605 try {
606
607
608 blockSignal.wait(5 * 1000);
609 } catch (InterruptedException ie) {
610 LOG.warn("Interrupted while waiting");
611 interrupted = true;
612 }
613 long took = EnvironmentEdgeManager.currentTime() - start;
614 LOG.warn("Memstore is above high water mark and block " + took + "ms");
615 }
616 } finally {
617 if (interrupted) {
618 Thread.currentThread().interrupt();
619 }
620 }
621
622 if(blocked){
623 final long totalTime = EnvironmentEdgeManager.currentTime() - startTime;
624 if(totalTime > 0){
625 this.updatesBlockedMsHighWater.add(totalTime);
626 }
627 LOG.info("Unblocking updates for server " + server.toString());
628 }
629 }
630 } else if (isAboveLowWaterMark()) {
631 wakeupFlushThread();
632 }
633 scope.close();
634 }
635 @Override
636 public String toString() {
637 return "flush_queue="
638 + flushQueue.size();
639 }
640
641 public String dumpQueue() {
642 StringBuilder queueList = new StringBuilder();
643 queueList.append("Flush Queue Queue dump:\n");
644 queueList.append(" Flush Queue:\n");
645 java.util.Iterator<FlushQueueEntry> it = flushQueue.iterator();
646
647 while(it.hasNext()){
648 queueList.append(" "+it.next().toString());
649 queueList.append("\n");
650 }
651
652 return queueList.toString();
653 }
654
655
656
657
658
659 @Override
660 public void registerFlushRequestListener(final FlushRequestListener listener) {
661 this.flushRequestListeners.add(listener);
662 }
663
664
665
666
667
668
669 @Override
670 public boolean unregisterFlushRequestListener(final FlushRequestListener listener) {
671 return this.flushRequestListeners.remove(listener);
672 }
673
674
675
676
677
678 @Override
679 public void setGlobalMemstoreLimit(long globalMemStoreSize) {
680 this.globalMemStoreLimit = globalMemStoreSize;
681 this.globalMemStoreLimitLowMark =
682 (long) (this.globalMemStoreLimitLowMarkPercent * globalMemStoreSize);
683 reclaimMemStoreMemory();
684 }
685
686 public long getMemoryLimit() {
687 return this.globalMemStoreLimit;
688 }
689
690 interface FlushQueueEntry extends Delayed {
691 }
692
693
694
695
696 static class WakeupFlushThread implements FlushQueueEntry {
697 @Override
698 public long getDelay(TimeUnit unit) {
699 return 0;
700 }
701
702 @Override
703 public int compareTo(Delayed o) {
704 return -1;
705 }
706
707 @Override
708 public int hashCode() {
709 return System.identityHashCode(this);
710 }
711
712 @Override
713 public boolean equals(Object obj) {
714 return Objects.equals(this, obj);
715 }
716 }
717
718
719
720
721
722
723
724
725
726 static class FlushRegionEntry implements FlushQueueEntry {
727 private final Region region;
728
729 private final long createTime;
730 private long whenToExpire;
731 private int requeueCount = 0;
732
733 private boolean forceFlushAllStores;
734
735 FlushRegionEntry(final Region r, boolean forceFlushAllStores) {
736 this.region = r;
737 this.createTime = EnvironmentEdgeManager.currentTime();
738 this.whenToExpire = this.createTime;
739 this.forceFlushAllStores = forceFlushAllStores;
740 }
741
742
743
744
745
746 public boolean isMaximumWait(final long maximumWait) {
747 return (EnvironmentEdgeManager.currentTime() - this.createTime) > maximumWait;
748 }
749
750
751
752
753
754 public int getRequeueCount() {
755 return this.requeueCount;
756 }
757
758
759
760
761 public boolean isForceFlushAllStores() {
762 return forceFlushAllStores;
763 }
764
765
766
767
768
769
770
771 public FlushRegionEntry requeue(final long when) {
772 this.whenToExpire = EnvironmentEdgeManager.currentTime() + when;
773 this.requeueCount++;
774 return this;
775 }
776
777 @Override
778 public long getDelay(TimeUnit unit) {
779 return unit.convert(this.whenToExpire - EnvironmentEdgeManager.currentTime(),
780 TimeUnit.MILLISECONDS);
781 }
782
783 @Override
784 public int compareTo(Delayed other) {
785
786 int ret = Long.valueOf(getDelay(TimeUnit.MILLISECONDS) -
787 other.getDelay(TimeUnit.MILLISECONDS)).intValue();
788 if (ret != 0) {
789 return ret;
790 }
791 FlushQueueEntry otherEntry = (FlushQueueEntry) other;
792 return hashCode() - otherEntry.hashCode();
793 }
794
795 @Override
796 public String toString() {
797 return "[flush region "+Bytes.toStringBinary(region.getRegionInfo().getRegionName())+"]";
798 }
799
800 @Override
801 public int hashCode() {
802 int hash = (int) getDelay(TimeUnit.MILLISECONDS);
803 return hash ^ region.hashCode();
804 }
805
806 @Override
807 public boolean equals(Object obj) {
808 if (this == obj) {
809 return true;
810 }
811 if (obj == null || getClass() != obj.getClass()) {
812 return false;
813 }
814 FlushRegionEntry other = (FlushRegionEntry) obj;
815 if (!Bytes.equals(this.region.getRegionInfo().getRegionName(),
816 other.region.getRegionInfo().getRegionName())) {
817 return false;
818 }
819 return compareTo(other) == 0;
820 }
821 }
822 }