private void buildRecovery( final FileSystemAbstraction fileSystemAbstraction, TransactionIdStore transactionIdStore, LogTailScanner tailScanner, RecoveryMonitor recoveryMonitor, RecoveryStartInformationProvider.Monitor positionMonitor, final LogFiles logFiles, StorageEngine storageEngine, LogicalTransactionStore logicalTransactionStore, LogVersionRepository logVersionRepository ) { RecoveryService recoveryService = new DefaultRecoveryService( storageEngine, tailScanner, transactionIdStore, logicalTransactionStore, logVersionRepository, positionMonitor ); CorruptedLogsTruncator logsTruncator = new CorruptedLogsTruncator( databaseLayout.databaseDirectory(), logFiles, fileSystemAbstraction ); ProgressReporter progressReporter = new LogProgressReporter( logService.getInternalLog( Recovery.class ) ); Recovery recovery = new Recovery( recoveryService, logsTruncator, recoveryMonitor, progressReporter, failOnCorruptedLogFiles ); life.add( recovery ); }
@Test public void doNotPruneEmptyLogs() throws IOException { logPruner.truncate( LogPosition.start( 0 ) ); assertTrue( FileUtils.isEmptyDirectory( databaseDirectory ) ); }
/** * Truncate all transaction logs after provided position. Log version specified in a position will be * truncated to provided byte offset, any subsequent log files will be deleted. Backup copy of removed data will * be stored in separate archive. * @param positionAfterLastRecoveredTransaction position after last recovered transaction * @throws IOException */ public void truncate( LogPosition positionAfterLastRecoveredTransaction ) throws IOException { long recoveredTransactionLogVersion = positionAfterLastRecoveredTransaction.getLogVersion(); long recoveredTransactionOffset = positionAfterLastRecoveredTransaction.getByteOffset(); if ( isRecoveredLogCorrupted( recoveredTransactionLogVersion, recoveredTransactionOffset ) || haveMoreRecentLogFiles( recoveredTransactionLogVersion ) ) { backupCorruptedContent( recoveredTransactionLogVersion, recoveredTransactionOffset ); truncateLogFiles( recoveredTransactionLogVersion, recoveredTransactionOffset ); } }
private void backupCorruptedContent( long recoveredTransactionLogVersion, long recoveredTransactionOffset ) throws IOException { File corruptedLogArchive = getArchiveFile( recoveredTransactionLogVersion, recoveredTransactionOffset ); try ( ZipOutputStream recoveryContent = new ZipOutputStream( new BufferedOutputStream( fs.openAsOutputStream( corruptedLogArchive, false ) ) ) ) { ByteBuffer zipBuffer = ByteBuffer.allocate( (int) ByteUnit.mebiBytes( 1 ) ); copyTransactionLogContent( recoveredTransactionLogVersion, recoveredTransactionOffset, recoveryContent, zipBuffer ); forEachSubsequentLogFile( recoveredTransactionLogVersion, fileIndex -> { try { copyTransactionLogContent( fileIndex, 0, recoveryContent, zipBuffer ); } catch ( IOException io ) { throw new UncheckedIOException( io ); } } ); } }
private void truncateLogFiles( long recoveredTransactionLogVersion, long recoveredTransactionOffset ) throws IOException { File lastRecoveredTransactionLog = logFiles.getLogFileForVersion( recoveredTransactionLogVersion ); fs.truncate( lastRecoveredTransactionLog, recoveredTransactionOffset ); forEachSubsequentLogFile( recoveredTransactionLogVersion, fileIndex -> fs.deleteFile( logFiles.getLogFileForVersion( fileIndex ) ) ); }
private void backupCorruptedContent( long recoveredTransactionLogVersion, long recoveredTransactionOffset ) throws IOException { File corruptedLogArchive = getArchiveFile( recoveredTransactionLogVersion, recoveredTransactionOffset ); try ( ZipOutputStream recoveryContent = new ZipOutputStream( new BufferedOutputStream( fs.openAsOutputStream( corruptedLogArchive, false ) ) ) ) { ByteBuffer zipBuffer = ByteBuffer.allocate( (int) ByteUnit.mebiBytes( 1 ) ); copyTransactionLogContent( recoveredTransactionLogVersion, recoveredTransactionOffset, recoveryContent, zipBuffer ); forEachSubsequentLogFile( recoveredTransactionLogVersion, fileIndex -> { try { copyTransactionLogContent( fileIndex, 0, recoveryContent, zipBuffer ); } catch ( IOException io ) { throw new UncheckedIOException( io ); } } ); } }
private void truncateLogFiles( long recoveredTransactionLogVersion, long recoveredTransactionOffset ) throws IOException { File lastRecoveredTransactionLog = logFiles.getLogFileForVersion( recoveredTransactionLogVersion ); fs.truncate( lastRecoveredTransactionLog, recoveredTransactionOffset ); forEachSubsequentLogFile( recoveredTransactionLogVersion, fileIndex -> fs.deleteFile( logFiles.getLogFileForVersion( fileIndex ) ) ); }
/** * Truncate all transaction logs after provided position. Log version specified in a position will be * truncated to provided byte offset, any subsequent log files will be deleted. Backup copy of removed data will * be stored in separate archive. * @param positionAfterLastRecoveredTransaction position after last recovered transaction * @throws IOException */ public void truncate( LogPosition positionAfterLastRecoveredTransaction ) throws IOException { long recoveredTransactionLogVersion = positionAfterLastRecoveredTransaction.getLogVersion(); long recoveredTransactionOffset = positionAfterLastRecoveredTransaction.getByteOffset(); if ( isRecoveredLogCorrupted( recoveredTransactionLogVersion, recoveredTransactionOffset ) || haveMoreRecentLogFiles( recoveredTransactionLogVersion ) ) { backupCorruptedContent( recoveredTransactionLogVersion, recoveredTransactionOffset ); truncateLogFiles( recoveredTransactionLogVersion, recoveredTransactionOffset ); } }
private boolean recover( File storeDir, LogFiles logFiles ) { LifeSupport life = new LifeSupport(); RecoveryMonitor monitor = mock( RecoveryMonitor.class ); final AtomicBoolean recoveryRequired = new AtomicBoolean(); try { StorageEngine storageEngine = mock( StorageEngine.class ); final LogEntryReader<ReadableClosablePositionAwareChannel> reader = new VersionAwareLogEntryReader<>(); LogTailScanner tailScanner = getTailScanner( logFiles, reader ); TransactionMetadataCache metadataCache = new TransactionMetadataCache(); LogicalTransactionStore txStore = new PhysicalLogicalTransactionStore( logFiles, metadataCache, reader, monitors, false ); CorruptedLogsTruncator logPruner = new CorruptedLogsTruncator( storeDir, logFiles, fileSystemRule.get() ); life.add( new Recovery( new DefaultRecoveryService( storageEngine, tailScanner, transactionIdStore, txStore, versionRepository, NO_MONITOR ) { @Override public void startRecovery() { recoveryRequired.set( true ); } }, logPruner, monitor, SilentProgressReporter.INSTANCE, false ) ); life.start(); } finally { life.shutdown(); } return recoveryRequired.get(); }
@Test public void pruneAndArchiveLastLog() throws IOException { life.start(); generateTransactionLogFiles( logFiles ); long highestLogVersion = logFiles.getHighestLogVersion(); File highestLogFile = logFiles.getHighestLogFile(); long fileSizeBeforePrune = highestLogFile.length(); int bytesToPrune = 5; long byteOffset = fileSizeBeforePrune - bytesToPrune; LogPosition prunePosition = new LogPosition( highestLogVersion, byteOffset ); logPruner.truncate( prunePosition ); assertEquals( TOTAL_NUMBER_OF_LOG_FILES, logFiles.logFiles().length ); assertEquals( byteOffset, highestLogFile.length() ); File corruptedLogsDirectory = new File( databaseDirectory, CorruptedLogsTruncator.CORRUPTED_TX_LOGS_BASE_NAME ); assertTrue( corruptedLogsDirectory.exists() ); File[] files = corruptedLogsDirectory.listFiles(); assertEquals( 1, files.length ); File corruptedLogsArchive = files[0]; checkArchiveName( highestLogVersion, byteOffset, corruptedLogsArchive ); try ( ZipFile zipFile = new ZipFile( corruptedLogsArchive ) ) { assertEquals( 1, zipFile.size() ); checkEntryNameAndSize( zipFile, highestLogFile.getName(), bytesToPrune ); } }
@Before public void setUp() throws Exception { databaseDirectory = testDirectory.databaseDir(); SimpleLogVersionRepository logVersionRepository = new SimpleLogVersionRepository(); SimpleTransactionIdStore transactionIdStore = new SimpleTransactionIdStore(); logFiles = LogFilesBuilder.logFilesBasedOnlyBuilder( databaseDirectory, fileSystemRule ) .withRotationThreshold( LogHeader.LOG_HEADER_SIZE + 9L ) .withLogVersionRepository( logVersionRepository ) .withTransactionIdStore( transactionIdStore ).build(); life.add( logFiles ); logPruner = new CorruptedLogsTruncator( databaseDirectory, logFiles, fileSystemRule ); }
@Test public void doNotPruneNonCorruptedLogs() throws IOException { life.start(); generateTransactionLogFiles( logFiles ); long highestLogVersion = logFiles.getHighestLogVersion(); long fileSizeBeforePrune = logFiles.getHighestLogFile().length(); LogPosition endOfLogsPosition = new LogPosition( highestLogVersion, fileSizeBeforePrune ); assertEquals( TOTAL_NUMBER_OF_LOG_FILES - 1, highestLogVersion ); logPruner.truncate( endOfLogsPosition ); assertEquals( TOTAL_NUMBER_OF_LOG_FILES, logFiles.logFiles().length ); assertEquals( fileSizeBeforePrune, logFiles.getHighestLogFile().length() ); assertTrue( ArrayUtil.isEmpty( databaseDirectory.listFiles( File::isDirectory ) ) ); }
LogicalTransactionStore txStore = new PhysicalLogicalTransactionStore( logFiles, metadataCache, reader, monitors, false ); CorruptedLogsTruncator logPruner = new CorruptedLogsTruncator( storeDir, logFiles, fileSystemRule.get() ); life.add( new Recovery( new DefaultRecoveryService( storageEngine, tailScanner, transactionIdStore, txStore, versionRepository, NO_MONITOR )
life.shutdown(); logPruner.truncate( prunePosition );
LogicalTransactionStore txStore = new PhysicalLogicalTransactionStore( logFiles, metadataCache, reader, monitors, false ); CorruptedLogsTruncator logPruner = new CorruptedLogsTruncator( storeDir, logFiles, fileSystemRule.get() ); life.add( new Recovery( new DefaultRecoveryService( storageEngine, tailScanner, transactionIdStore, txStore, versionRepository, NO_MONITOR )
logsTruncator.truncate( recoveryToPosition );
CorruptedLogsTruncator logPruner = new CorruptedLogsTruncator( databaseDirectory, logFiles, fileSystemRule.get() ); life.add( new Recovery( new RecoveryService()
logsTruncator.truncate( recoveryToPosition );
private void buildRecovery( final FileSystemAbstraction fileSystemAbstraction, TransactionIdStore transactionIdStore, LogTailScanner tailScanner, RecoveryMonitor recoveryMonitor, RecoveryStartInformationProvider.Monitor positionMonitor, final LogFiles logFiles, StorageEngine storageEngine, LogicalTransactionStore logicalTransactionStore, LogVersionRepository logVersionRepository ) { RecoveryService recoveryService = new DefaultRecoveryService( storageEngine, tailScanner, transactionIdStore, logicalTransactionStore, logVersionRepository, positionMonitor ); CorruptedLogsTruncator logsTruncator = new CorruptedLogsTruncator( databaseLayout.databaseDirectory(), logFiles, fileSystemAbstraction ); ProgressReporter progressReporter = new LogProgressReporter( logService.getInternalLog( Recovery.class ) ); Recovery recovery = new Recovery( recoveryService, logsTruncator, recoveryMonitor, progressReporter, failOnCorruptedLogFiles ); life.add( recovery ); }