1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.procedure2.store.wal;
20
21 import com.google.common.annotations.VisibleForTesting;
22
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.LinkedList;
32 import java.util.Set;
33 import java.util.concurrent.LinkedTransferQueue;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.concurrent.atomic.AtomicLong;
37 import java.util.concurrent.atomic.AtomicReference;
38 import java.util.concurrent.locks.Condition;
39 import java.util.concurrent.locks.ReentrantLock;
40
41 import org.apache.commons.collections.buffer.CircularFifoBuffer;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.hadoop.conf.Configuration;
45 import org.apache.hadoop.fs.FSDataOutputStream;
46 import org.apache.hadoop.fs.FileAlreadyExistsException;
47 import org.apache.hadoop.fs.FileStatus;
48 import org.apache.hadoop.fs.FileSystem;
49 import org.apache.hadoop.fs.Path;
50 import org.apache.hadoop.fs.PathFilter;
51 import org.apache.hadoop.hbase.classification.InterfaceAudience;
52 import org.apache.hadoop.hbase.classification.InterfaceStability;
53 import org.apache.hadoop.hbase.procedure2.Procedure;
54 import org.apache.hadoop.hbase.procedure2.store.ProcedureStoreBase;
55 import org.apache.hadoop.hbase.procedure2.store.ProcedureStoreTracker;
56 import org.apache.hadoop.hbase.procedure2.util.ByteSlot;
57 import org.apache.hadoop.hbase.procedure2.util.StringUtils;
58 import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos.ProcedureWALHeader;
59 import org.apache.hadoop.hbase.util.Threads;
60 import org.apache.hadoop.ipc.RemoteException;
61
62
63
64
65 @InterfaceAudience.Private
66 @InterfaceStability.Evolving
67 public class WALProcedureStore extends ProcedureStoreBase {
68 private static final Log LOG = LogFactory.getLog(WALProcedureStore.class);
69
70 public interface LeaseRecovery {
71 void recoverFileLease(FileSystem fs, Path path) throws IOException;
72 }
73
74 public static final String MAX_RETRIES_BEFORE_ROLL_CONF_KEY =
75 "hbase.procedure.store.wal.max.retries.before.roll";
76 private static final int DEFAULT_MAX_RETRIES_BEFORE_ROLL = 3;
77
78 public static final String WAIT_BEFORE_ROLL_CONF_KEY =
79 "hbase.procedure.store.wal.wait.before.roll";
80 private static final int DEFAULT_WAIT_BEFORE_ROLL = 500;
81
82 public static final String ROLL_RETRIES_CONF_KEY =
83 "hbase.procedure.store.wal.max.roll.retries";
84 private static final int DEFAULT_ROLL_RETRIES = 3;
85
86 public static final String MAX_SYNC_FAILURE_ROLL_CONF_KEY =
87 "hbase.procedure.store.wal.sync.failure.roll.max";
88 private static final int DEFAULT_MAX_SYNC_FAILURE_ROLL = 3;
89
90 public static final String PERIODIC_ROLL_CONF_KEY =
91 "hbase.procedure.store.wal.periodic.roll.msec";
92 private static final int DEFAULT_PERIODIC_ROLL = 60 * 60 * 1000;
93
94 public static final String SYNC_WAIT_MSEC_CONF_KEY = "hbase.procedure.store.wal.sync.wait.msec";
95 private static final int DEFAULT_SYNC_WAIT_MSEC = 100;
96
97 public static final String USE_HSYNC_CONF_KEY = "hbase.procedure.store.wal.use.hsync";
98 private static final boolean DEFAULT_USE_HSYNC = true;
99
100 public static final String ROLL_THRESHOLD_CONF_KEY = "hbase.procedure.store.wal.roll.threshold";
101 private static final long DEFAULT_ROLL_THRESHOLD = 32 * 1024 * 1024;
102
103 public static final String STORE_WAL_SYNC_STATS_COUNT =
104 "hbase.procedure.store.wal.sync.stats.count";
105 private static final int DEFAULT_SYNC_STATS_COUNT = 10;
106
107 private final LinkedList<ProcedureWALFile> logs = new LinkedList<ProcedureWALFile>();
108 private final ProcedureStoreTracker storeTracker = new ProcedureStoreTracker();
109 private final ReentrantLock lock = new ReentrantLock();
110 private final Condition waitCond = lock.newCondition();
111 private final Condition slotCond = lock.newCondition();
112 private final Condition syncCond = lock.newCondition();
113
114 private final LeaseRecovery leaseRecovery;
115 private final Configuration conf;
116 private final FileSystem fs;
117 private final Path walDir;
118
119 private final AtomicReference<Throwable> syncException = new AtomicReference<Throwable>();
120 private final AtomicBoolean loading = new AtomicBoolean(true);
121 private final AtomicBoolean inSync = new AtomicBoolean(false);
122 private final AtomicLong totalSynced = new AtomicLong(0);
123 private final AtomicLong lastRollTs = new AtomicLong(0);
124
125 private LinkedTransferQueue<ByteSlot> slotsCache = null;
126 private Set<ProcedureWALFile> corruptedLogs = null;
127 private FSDataOutputStream stream = null;
128 private long flushLogId = 0;
129 private int slotIndex = 0;
130 private Thread syncThread;
131 private ByteSlot[] slots;
132
133 private int maxRetriesBeforeRoll;
134 private int maxSyncFailureRoll;
135 private int waitBeforeRoll;
136 private int rollRetries;
137 private int periodicRollMsec;
138 private long rollThreshold;
139 private boolean useHsync;
140 private int syncWaitMsec;
141
142
143 private CircularFifoBuffer syncMetricsBuffer;
144
145 public static class SyncMetrics {
146 private long timestamp;
147 private long syncWaitMs;
148 private long totalSyncedBytes;
149 private int syncedEntries;
150 private float syncedPerSec;
151
152 public long getTimestamp() {
153 return timestamp;
154 }
155
156 public long getSyncWaitMs() {
157 return syncWaitMs;
158 }
159
160 public long getTotalSyncedBytes() {
161 return totalSyncedBytes;
162 }
163
164 public long getSyncedEntries() {
165 return syncedEntries;
166 }
167
168 public float getSyncedPerSec() {
169 return syncedPerSec;
170 }
171 }
172
173 public WALProcedureStore(final Configuration conf, final FileSystem fs, final Path walDir,
174 final LeaseRecovery leaseRecovery) {
175 this.fs = fs;
176 this.conf = conf;
177 this.walDir = walDir;
178 this.leaseRecovery = leaseRecovery;
179 }
180
181 @Override
182 public void start(int numSlots) throws IOException {
183 if (!setRunning(true)) {
184 return;
185 }
186
187
188 loading.set(true);
189 slots = new ByteSlot[numSlots];
190 slotsCache = new LinkedTransferQueue();
191 while (slotsCache.size() < numSlots) {
192 slotsCache.offer(new ByteSlot());
193 }
194
195
196 maxRetriesBeforeRoll =
197 conf.getInt(MAX_RETRIES_BEFORE_ROLL_CONF_KEY, DEFAULT_MAX_RETRIES_BEFORE_ROLL);
198 maxSyncFailureRoll = conf.getInt(MAX_SYNC_FAILURE_ROLL_CONF_KEY, DEFAULT_MAX_SYNC_FAILURE_ROLL);
199 waitBeforeRoll = conf.getInt(WAIT_BEFORE_ROLL_CONF_KEY, DEFAULT_WAIT_BEFORE_ROLL);
200 rollRetries = conf.getInt(ROLL_RETRIES_CONF_KEY, DEFAULT_ROLL_RETRIES);
201 rollThreshold = conf.getLong(ROLL_THRESHOLD_CONF_KEY, DEFAULT_ROLL_THRESHOLD);
202 periodicRollMsec = conf.getInt(PERIODIC_ROLL_CONF_KEY, DEFAULT_PERIODIC_ROLL);
203 syncWaitMsec = conf.getInt(SYNC_WAIT_MSEC_CONF_KEY, DEFAULT_SYNC_WAIT_MSEC);
204 useHsync = conf.getBoolean(USE_HSYNC_CONF_KEY, DEFAULT_USE_HSYNC);
205
206
207 syncMetricsBuffer = new CircularFifoBuffer(
208 conf.getInt(STORE_WAL_SYNC_STATS_COUNT, DEFAULT_SYNC_STATS_COUNT));
209
210
211 syncThread = new Thread("WALProcedureStoreSyncThread") {
212 @Override
213 public void run() {
214 try {
215 syncLoop();
216 } catch (Throwable e) {
217 LOG.error("Got an exception from the sync-loop", e);
218 if (!isSyncAborted()) {
219 sendAbortProcessSignal();
220 }
221 }
222 }
223 };
224 syncThread.start();
225 }
226
227 @Override
228 public void stop(boolean abort) {
229 if (!setRunning(false)) {
230 return;
231 }
232
233 LOG.info("Stopping the WAL Procedure Store");
234 sendStopSignal();
235
236 if (!abort) {
237 try {
238 while (syncThread.isAlive()) {
239 sendStopSignal();
240 syncThread.join(250);
241 }
242 } catch (InterruptedException e) {
243 LOG.warn("join interrupted", e);
244 Thread.currentThread().interrupt();
245 }
246 }
247
248
249 closeStream();
250
251
252
253
254 for (ProcedureWALFile log: logs) {
255 log.close();
256 }
257 logs.clear();
258 }
259
260 private void sendStopSignal() {
261 if (lock.tryLock()) {
262 try {
263 waitCond.signalAll();
264 syncCond.signalAll();
265 } finally {
266 lock.unlock();
267 }
268 }
269 }
270
271 @Override
272 public int getNumThreads() {
273 return slots == null ? 0 : slots.length;
274 }
275
276 public ProcedureStoreTracker getStoreTracker() {
277 return storeTracker;
278 }
279
280 public ArrayList<ProcedureWALFile> getActiveLogs() {
281 lock.lock();
282 try {
283 return new ArrayList<ProcedureWALFile>(logs);
284 } finally {
285 lock.unlock();
286 }
287 }
288
289 public Set<ProcedureWALFile> getCorruptedLogs() {
290 return corruptedLogs;
291 }
292
293 @Override
294 public void recoverLease() throws IOException {
295 lock.lock();
296 try {
297 LOG.info("Starting WAL Procedure Store lease recovery");
298 FileStatus[] oldLogs = getLogFiles();
299 while (isRunning()) {
300
301 try {
302 flushLogId = initOldLogs(oldLogs);
303 } catch (FileNotFoundException e) {
304 LOG.warn("someone else is active and deleted logs. retrying.", e);
305 oldLogs = getLogFiles();
306 continue;
307 }
308
309
310 if (!rollWriter(flushLogId + 1)) {
311
312 LOG.debug("someone else has already created log " + flushLogId);
313 continue;
314 }
315
316
317 oldLogs = getLogFiles();
318 if (getMaxLogId(oldLogs) > flushLogId) {
319 if (LOG.isDebugEnabled()) {
320 LOG.debug("Someone else created new logs. Expected maxLogId < " + flushLogId);
321 }
322 logs.getLast().removeFile();
323 continue;
324 }
325
326 LOG.info("Lease acquired for flushLogId: " + flushLogId);
327 break;
328 }
329 } finally {
330 lock.unlock();
331 }
332 }
333
334 @Override
335 public void load(final ProcedureLoader loader) throws IOException {
336 if (logs.isEmpty()) {
337 throw new RuntimeException("recoverLease() must be called before loading data");
338 }
339
340
341 if (logs.size() == 1) {
342 if (LOG.isDebugEnabled()) {
343 LOG.debug("No state logs to replay.");
344 }
345 loader.setMaxProcId(0);
346 loading.set(false);
347 return;
348 }
349
350
351 Iterator<ProcedureWALFile> it = logs.descendingIterator();
352 it.next();
353 try {
354 ProcedureWALFormat.load(it, storeTracker, new ProcedureWALFormat.Loader() {
355 @Override
356 public void setMaxProcId(long maxProcId) {
357 loader.setMaxProcId(maxProcId);
358 }
359
360 @Override
361 public void load(ProcedureIterator procIter) throws IOException {
362 loader.load(procIter);
363 }
364
365 @Override
366 public void handleCorrupted(ProcedureIterator procIter) throws IOException {
367 loader.handleCorrupted(procIter);
368 }
369
370 @Override
371 public void markCorruptedWAL(ProcedureWALFile log, IOException e) {
372 if (corruptedLogs == null) {
373 corruptedLogs = new HashSet<ProcedureWALFile>();
374 }
375 corruptedLogs.add(log);
376
377 }
378 });
379 } finally {
380 loading.set(false);
381 }
382 }
383
384 @Override
385 public void insert(final Procedure proc, final Procedure[] subprocs) {
386 if (LOG.isTraceEnabled()) {
387 LOG.trace("Insert " + proc + ", subproc=" + Arrays.toString(subprocs));
388 }
389
390 ByteSlot slot = acquireSlot();
391 try {
392
393 long[] subProcIds = null;
394 if (subprocs != null) {
395 ProcedureWALFormat.writeInsert(slot, proc, subprocs);
396 subProcIds = new long[subprocs.length];
397 for (int i = 0; i < subprocs.length; ++i) {
398 subProcIds[i] = subprocs[i].getProcId();
399 }
400 } else {
401 assert !proc.hasParent();
402 ProcedureWALFormat.writeInsert(slot, proc);
403 }
404
405
406 pushData(PushType.INSERT, slot, proc.getProcId(), subProcIds);
407 } catch (IOException e) {
408
409
410 LOG.fatal("Unable to serialize one of the procedure: proc=" + proc +
411 ", subprocs=" + Arrays.toString(subprocs), e);
412 throw new RuntimeException(e);
413 } finally {
414 releaseSlot(slot);
415 }
416 }
417
418 @Override
419 public void update(final Procedure proc) {
420 if (LOG.isTraceEnabled()) {
421 LOG.trace("Update " + proc);
422 }
423
424 ByteSlot slot = acquireSlot();
425 try {
426
427 ProcedureWALFormat.writeUpdate(slot, proc);
428
429
430 pushData(PushType.UPDATE, slot, proc.getProcId(), null);
431 } catch (IOException e) {
432
433
434 LOG.fatal("Unable to serialize the procedure: " + proc, e);
435 throw new RuntimeException(e);
436 } finally {
437 releaseSlot(slot);
438 }
439 }
440
441 @Override
442 public void delete(final long procId) {
443 if (LOG.isTraceEnabled()) {
444 LOG.trace("Delete " + procId);
445 }
446
447 ByteSlot slot = acquireSlot();
448 try {
449
450 ProcedureWALFormat.writeDelete(slot, procId);
451
452
453 pushData(PushType.DELETE, slot, procId, null);
454 } catch (IOException e) {
455
456
457 LOG.fatal("Unable to serialize the procedure: " + procId, e);
458 throw new RuntimeException(e);
459 } finally {
460 releaseSlot(slot);
461 }
462 }
463
464 @Override
465 public void delete(final Procedure proc, final long[] subProcIds) {
466 if (LOG.isTraceEnabled()) {
467 LOG.trace("Update " + proc + " and Delete " + Arrays.toString(subProcIds));
468 }
469
470 ByteSlot slot = acquireSlot();
471 try {
472
473 ProcedureWALFormat.writeDelete(slot, proc, subProcIds);
474
475
476 pushData(PushType.DELETE, slot, proc.getProcId(), subProcIds);
477 } catch (IOException e) {
478
479
480 LOG.fatal("Unable to serialize the procedure: " + proc, e);
481 throw new RuntimeException(e);
482 } finally {
483 releaseSlot(slot);
484 }
485 }
486
487 private ByteSlot acquireSlot() {
488 ByteSlot slot = slotsCache.poll();
489 return slot != null ? slot : new ByteSlot();
490 }
491
492 private void releaseSlot(final ByteSlot slot) {
493 slot.reset();
494 slotsCache.offer(slot);
495 }
496
497 private enum PushType { INSERT, UPDATE, DELETE };
498
499 private long pushData(final PushType type, final ByteSlot slot,
500 final long procId, final long[] subProcIds) {
501 if (!isRunning()) {
502 throw new RuntimeException("the store must be running before inserting data");
503 }
504 if (logs.isEmpty()) {
505 throw new RuntimeException("recoverLease() must be called before inserting data");
506 }
507
508 long logId = -1;
509 lock.lock();
510 try {
511
512 while (true) {
513 if (!isRunning()) {
514 throw new RuntimeException("store no longer running");
515 } else if (isSyncAborted()) {
516 throw new RuntimeException("sync aborted", syncException.get());
517 } else if (inSync.get()) {
518 syncCond.await();
519 } else if (slotIndex == slots.length) {
520 slotCond.signal();
521 syncCond.await();
522 } else {
523 break;
524 }
525 }
526
527 updateStoreTracker(type, procId, subProcIds);
528 slots[slotIndex++] = slot;
529 logId = flushLogId;
530
531
532 if (slotIndex == 1) {
533 waitCond.signal();
534 }
535
536
537 if (slotIndex == slots.length) {
538 waitCond.signal();
539 slotCond.signal();
540 }
541
542 syncCond.await();
543 } catch (InterruptedException e) {
544 Thread.currentThread().interrupt();
545 sendAbortProcessSignal();
546 throw new RuntimeException(e);
547 } finally {
548 lock.unlock();
549 if (isSyncAborted()) {
550 throw new RuntimeException("sync aborted", syncException.get());
551 }
552 }
553 return logId;
554 }
555
556 private void updateStoreTracker(final PushType type,
557 final long procId, final long[] subProcIds) {
558 switch (type) {
559 case INSERT:
560 if (subProcIds == null) {
561 storeTracker.insert(procId);
562 } else {
563 storeTracker.insert(procId, subProcIds);
564 }
565 break;
566 case UPDATE:
567 storeTracker.update(procId);
568 break;
569 case DELETE:
570 if (subProcIds != null && subProcIds.length > 0) {
571 storeTracker.delete(subProcIds);
572 } else {
573 storeTracker.delete(procId);
574 }
575 break;
576 default:
577 throw new RuntimeException("invalid push type " + type);
578 }
579 }
580
581 private boolean isSyncAborted() {
582 return syncException.get() != null;
583 }
584
585 private void syncLoop() throws Throwable {
586 long totalSyncedToStore = 0;
587 inSync.set(false);
588 lock.lock();
589 try {
590 while (isRunning()) {
591 try {
592
593 if (slotIndex == 0) {
594 if (!loading.get()) {
595 periodicRoll();
596 }
597
598 if (LOG.isTraceEnabled()) {
599 float rollTsSec = getMillisFromLastRoll() / 1000.0f;
600 LOG.trace(String.format("Waiting for data. flushed=%s (%s/sec)",
601 StringUtils.humanSize(totalSynced.get()),
602 StringUtils.humanSize(totalSynced.get() / rollTsSec)));
603 }
604
605 waitCond.await(getMillisToNextPeriodicRoll(), TimeUnit.MILLISECONDS);
606 if (slotIndex == 0) {
607
608 continue;
609 }
610 }
611
612 final long syncWaitSt = System.currentTimeMillis();
613 if (slotIndex != slots.length) {
614 slotCond.await(syncWaitMsec, TimeUnit.MILLISECONDS);
615 }
616
617 final long currentTs = System.currentTimeMillis();
618 final long syncWaitMs = currentTs - syncWaitSt;
619 final float rollSec = getMillisFromLastRoll() / 1000.0f;
620 final float syncedPerSec = totalSyncedToStore / rollSec;
621 if (LOG.isTraceEnabled() && (syncWaitMs > 10 || slotIndex < slots.length)) {
622 LOG.trace(String.format("Sync wait %s, slotIndex=%s , totalSynced=%s (%s/sec)",
623 StringUtils.humanTimeDiff(syncWaitMs), slotIndex,
624 StringUtils.humanSize(totalSyncedToStore),
625 StringUtils.humanSize(syncedPerSec)));
626 }
627
628
629 final SyncMetrics syncMetrics = new SyncMetrics();
630 syncMetrics.timestamp = currentTs;
631 syncMetrics.syncWaitMs = syncWaitMs;
632 syncMetrics.syncedEntries = slotIndex;
633 syncMetrics.totalSyncedBytes = totalSyncedToStore;
634 syncMetrics.syncedPerSec = syncedPerSec;
635 syncMetricsBuffer.add(syncMetrics);
636
637
638 inSync.set(true);
639 long slotSize = syncSlots();
640 logs.getLast().addToSize(slotSize);
641 totalSyncedToStore = totalSynced.addAndGet(slotSize);
642 slotIndex = 0;
643 inSync.set(false);
644 } catch (InterruptedException e) {
645 Thread.currentThread().interrupt();
646 sendAbortProcessSignal();
647 syncException.compareAndSet(null, e);
648 throw e;
649 } catch (Throwable t) {
650 syncException.compareAndSet(null, t);
651 throw t;
652 } finally {
653 syncCond.signalAll();
654 }
655 }
656 } finally {
657 lock.unlock();
658 }
659 }
660
661 public ArrayList<SyncMetrics> getSyncMetrics() {
662 lock.lock();
663 try {
664 return new ArrayList<SyncMetrics>(syncMetricsBuffer);
665 } finally {
666 lock.unlock();
667 }
668 }
669
670 private long syncSlots() throws Throwable {
671 int retry = 0;
672 int logRolled = 0;
673 long totalSynced = 0;
674 do {
675 try {
676 totalSynced = syncSlots(stream, slots, 0, slotIndex);
677 break;
678 } catch (Throwable e) {
679 LOG.warn("unable to sync slots, retry=" + retry);
680 if (++retry >= maxRetriesBeforeRoll) {
681 if (logRolled >= maxSyncFailureRoll) {
682 LOG.error("Sync slots after log roll failed, abort.", e);
683 sendAbortProcessSignal();
684 throw e;
685 }
686
687 if (!rollWriterOrDie()) {
688 throw e;
689 }
690
691 logRolled++;
692 retry = 0;
693 }
694 }
695 } while (isRunning());
696 return totalSynced;
697 }
698
699 protected long syncSlots(FSDataOutputStream stream, ByteSlot[] slots, int offset, int count)
700 throws IOException {
701 long totalSynced = 0;
702 for (int i = 0; i < count; ++i) {
703 ByteSlot data = slots[offset + i];
704 data.writeTo(stream);
705 totalSynced += data.size();
706 }
707
708 if (useHsync) {
709 stream.hsync();
710 } else {
711 stream.hflush();
712 }
713 sendPostSyncSignal();
714
715 if (LOG.isTraceEnabled()) {
716 LOG.trace("Sync slots=" + count + '/' + slots.length +
717 ", flushed=" + StringUtils.humanSize(totalSynced));
718 }
719 return totalSynced;
720 }
721
722 private boolean rollWriterOrDie() {
723 for (int i = 0; i < rollRetries; ++i) {
724 if (i > 0) Threads.sleepWithoutInterrupt(waitBeforeRoll * i);
725
726 try {
727 if (rollWriter()) {
728 return true;
729 }
730 } catch (IOException e) {
731 LOG.warn("Unable to roll the log, attempt=" + (i + 1), e);
732 }
733 }
734 LOG.fatal("Unable to roll the log");
735 sendAbortProcessSignal();
736 throw new RuntimeException("unable to roll the log");
737 }
738
739 private boolean tryRollWriter() {
740 try {
741 return rollWriter();
742 } catch (IOException e) {
743 LOG.warn("Unable to roll the log", e);
744 return false;
745 }
746 }
747
748 public long getMillisToNextPeriodicRoll() {
749 if (lastRollTs.get() > 0 && periodicRollMsec > 0) {
750 return periodicRollMsec - getMillisFromLastRoll();
751 }
752 return Long.MAX_VALUE;
753 }
754
755 public long getMillisFromLastRoll() {
756 return (System.currentTimeMillis() - lastRollTs.get());
757 }
758
759 @VisibleForTesting
760 protected void periodicRollForTesting() throws IOException {
761 lock.lock();
762 try {
763 periodicRoll();
764 } finally {
765 lock.unlock();
766 }
767 }
768
769 @VisibleForTesting
770 protected boolean rollWriterForTesting() throws IOException {
771 lock.lock();
772 try {
773 return rollWriter();
774 } finally {
775 lock.unlock();
776 }
777 }
778
779 private void periodicRoll() throws IOException {
780 if (storeTracker.isEmpty()) {
781 if (LOG.isTraceEnabled()) {
782 LOG.trace("no active procedures");
783 }
784 tryRollWriter();
785 removeAllLogs(flushLogId - 1);
786 } else {
787 if (storeTracker.isUpdated()) {
788 if (LOG.isTraceEnabled()) {
789 LOG.trace("all the active procedures are in the latest log");
790 }
791 removeAllLogs(flushLogId - 1);
792 }
793
794
795
796 if (totalSynced.get() > rollThreshold || getMillisToNextPeriodicRoll() <= 0) {
797 tryRollWriter();
798 }
799
800 removeInactiveLogs();
801 }
802 }
803
804 private boolean rollWriter() throws IOException {
805
806 if (!rollWriter(flushLogId + 1)) {
807 LOG.warn("someone else has already created log " + flushLogId);
808 return false;
809 }
810
811
812
813 if (getMaxLogId(getLogFiles()) > flushLogId) {
814 LOG.warn("Someone else created new logs. Expected maxLogId < " + flushLogId);
815 logs.getLast().removeFile();
816 return false;
817 }
818
819
820 return true;
821 }
822
823 private boolean rollWriter(final long logId) throws IOException {
824 assert logId > flushLogId : "logId=" + logId + " flushLogId=" + flushLogId;
825 assert lock.isHeldByCurrentThread() : "expected to be the lock owner. " + lock.isLocked();
826
827 ProcedureWALHeader header = ProcedureWALHeader.newBuilder()
828 .setVersion(ProcedureWALFormat.HEADER_VERSION)
829 .setType(ProcedureWALFormat.LOG_TYPE_STREAM)
830 .setMinProcId(storeTracker.getMinProcId())
831 .setLogId(logId)
832 .build();
833
834 FSDataOutputStream newStream = null;
835 Path newLogFile = null;
836 long startPos = -1;
837 newLogFile = getLogFilePath(logId);
838 try {
839 newStream = fs.create(newLogFile, false);
840 } catch (FileAlreadyExistsException e) {
841 LOG.error("Log file with id=" + logId + " already exists", e);
842 return false;
843 } catch (RemoteException re) {
844 LOG.warn("failed to create log file with id=" + logId, re);
845 return false;
846 }
847 try {
848 ProcedureWALFormat.writeHeader(newStream, header);
849 startPos = newStream.getPos();
850 } catch (IOException ioe) {
851 LOG.warn("Encountered exception writing header", ioe);
852 newStream.close();
853 return false;
854 }
855
856 closeStream();
857
858 storeTracker.resetUpdates();
859 stream = newStream;
860 flushLogId = logId;
861 totalSynced.set(0);
862 long rollTs = System.currentTimeMillis();
863 lastRollTs.set(rollTs);
864 logs.add(new ProcedureWALFile(fs, newLogFile, header, startPos, rollTs));
865
866 if (LOG.isDebugEnabled()) {
867 LOG.debug("Roll new state log: " + logId);
868 }
869 return true;
870 }
871
872 private void closeStream() {
873 try {
874 if (stream != null) {
875 try {
876 ProcedureWALFile log = logs.getLast();
877 log.setProcIds(storeTracker.getUpdatedMinProcId(), storeTracker.getUpdatedMaxProcId());
878 long trailerSize = ProcedureWALFormat.writeTrailer(stream, storeTracker);
879 log.addToSize(trailerSize);
880 } catch (IOException e) {
881 LOG.warn("Unable to write the trailer: " + e.getMessage());
882 }
883 stream.close();
884 }
885 } catch (IOException e) {
886 LOG.error("Unable to close the stream", e);
887 } finally {
888 stream = null;
889 }
890 }
891
892
893
894
895 private void removeInactiveLogs() {
896
897 while (logs.size() > 1) {
898 ProcedureWALFile log = logs.getFirst();
899 if (storeTracker.isTracking(log.getMinProcId(), log.getMaxProcId())) {
900 break;
901 }
902 removeLogFile(log);
903 }
904 }
905
906 private void removeAllLogs(long lastLogId) {
907 if (logs.size() <= 1) return;
908
909 if (LOG.isDebugEnabled()) {
910 LOG.debug("Remove all state logs with ID less than " + lastLogId);
911 }
912 while (logs.size() > 1) {
913 ProcedureWALFile log = logs.getFirst();
914 if (lastLogId < log.getLogId()) {
915 break;
916 }
917 removeLogFile(log);
918 }
919 }
920
921 private boolean removeLogFile(final ProcedureWALFile log) {
922 try {
923 if (LOG.isTraceEnabled()) {
924 LOG.trace("Removing log=" + log);
925 }
926 log.removeFile();
927 logs.remove(log);
928 if (LOG.isDebugEnabled()) {
929 LOG.info("Removed log=" + log + " activeLogs=" + logs);
930 }
931 assert logs.size() > 0 : "expected at least one log";
932 } catch (IOException e) {
933 LOG.error("Unable to remove log: " + log, e);
934 return false;
935 }
936 return true;
937 }
938
939
940
941
942 public Path getWALDir() {
943 return this.walDir;
944 }
945
946 public FileSystem getFileSystem() {
947 return this.fs;
948 }
949
950 protected Path getLogFilePath(final long logId) throws IOException {
951 return new Path(walDir, String.format("state-%020d.log", logId));
952 }
953
954 private static long getLogIdFromName(final String name) {
955 int end = name.lastIndexOf(".log");
956 int start = name.lastIndexOf('-') + 1;
957 while (start < end) {
958 if (name.charAt(start) != '0')
959 break;
960 start++;
961 }
962 return Long.parseLong(name.substring(start, end));
963 }
964
965 private static final PathFilter WALS_PATH_FILTER = new PathFilter() {
966 @Override
967 public boolean accept(Path path) {
968 String name = path.getName();
969 return name.startsWith("state-") && name.endsWith(".log");
970 }
971 };
972
973 private static final Comparator<FileStatus> FILE_STATUS_ID_COMPARATOR =
974 new Comparator<FileStatus>() {
975 @Override
976 public int compare(FileStatus a, FileStatus b) {
977 final long aId = getLogIdFromName(a.getPath().getName());
978 final long bId = getLogIdFromName(b.getPath().getName());
979 return Long.compare(aId, bId);
980 }
981 };
982
983 private FileStatus[] getLogFiles() throws IOException {
984 try {
985 FileStatus[] files = fs.listStatus(walDir, WALS_PATH_FILTER);
986 Arrays.sort(files, FILE_STATUS_ID_COMPARATOR);
987 return files;
988 } catch (FileNotFoundException e) {
989 LOG.warn("Log directory not found: " + e.getMessage());
990 return null;
991 }
992 }
993
994 private static long getMaxLogId(final FileStatus[] logFiles) {
995 long maxLogId = 0;
996 if (logFiles != null && logFiles.length > 0) {
997 for (int i = 0; i < logFiles.length; ++i) {
998 maxLogId = Math.max(maxLogId, getLogIdFromName(logFiles[i].getPath().getName()));
999 }
1000 }
1001 return maxLogId;
1002 }
1003
1004
1005
1006
1007 private long initOldLogs(final FileStatus[] logFiles) throws IOException {
1008 this.logs.clear();
1009
1010 long maxLogId = 0;
1011 if (logFiles != null && logFiles.length > 0) {
1012 for (int i = 0; i < logFiles.length; ++i) {
1013 final Path logPath = logFiles[i].getPath();
1014 leaseRecovery.recoverFileLease(fs, logPath);
1015 maxLogId = Math.max(maxLogId, getLogIdFromName(logPath.getName()));
1016
1017 ProcedureWALFile log = initOldLog(logFiles[i]);
1018 if (log != null) {
1019 this.logs.add(log);
1020 }
1021 }
1022 Collections.sort(this.logs);
1023 initTrackerFromOldLogs();
1024 }
1025 return maxLogId;
1026 }
1027
1028 private void initTrackerFromOldLogs() {
1029
1030 if (!logs.isEmpty()) {
1031 ProcedureWALFile log = logs.getLast();
1032 try {
1033 log.readTracker(storeTracker);
1034 } catch (IOException e) {
1035 LOG.warn("Unable to read tracker for " + log + " - " + e.getMessage());
1036
1037 storeTracker.reset();
1038 storeTracker.setPartialFlag(true);
1039 }
1040 }
1041 }
1042
1043 private ProcedureWALFile initOldLog(final FileStatus logFile) throws IOException {
1044 ProcedureWALFile log = new ProcedureWALFile(fs, logFile);
1045 if (logFile.getLen() == 0) {
1046 LOG.warn("Remove uninitialized log: " + logFile);
1047 log.removeFile();
1048 return null;
1049 }
1050 if (LOG.isDebugEnabled()) {
1051 LOG.debug("Opening state-log: " + logFile);
1052 }
1053 try {
1054 log.open();
1055 } catch (ProcedureWALFormat.InvalidWALDataException e) {
1056 LOG.warn("Remove uninitialized log: " + logFile, e);
1057 log.removeFile();
1058 return null;
1059 } catch (IOException e) {
1060 String msg = "Unable to read state log: " + logFile;
1061 LOG.error(msg, e);
1062 throw new IOException(msg, e);
1063 }
1064
1065 if (log.isCompacted()) {
1066 try {
1067 log.readTrailer();
1068 } catch (IOException e) {
1069 LOG.warn("Unfinished compacted log: " + logFile, e);
1070 log.removeFile();
1071 return null;
1072 }
1073 }
1074 return log;
1075 }
1076 }