/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.sysprocs.saverestore;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.voltcore.logging.VoltLogger;
import org.voltcore.utils.CoreUtils;
import org.voltdb.PrivateVoltTableFactory;
import org.voltdb.VoltDB;
import org.voltdb.utils.VoltFile;
import org.voltdb.utils.VoltTableUtil;
/**
* A utility class for handling duplicates rows found during snapshot restore.
* Converts the row data to CSV and writes them to a per table file at the specified location
*
*/
public class DuplicateRowHandler {
private static final VoltLogger SNAP_LOG = new VoltLogger("SNAPSHOT");
private final File outputPath;
private final ExecutorService es = CoreUtils.getSingleThreadExecutor("Restore duplicate row handler");
private final Map<String, FileChannel> m_outputFiles = new HashMap<String, FileChannel>();
private final String now;
public DuplicateRowHandler(String path, Date now) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HH:mm:ss.SSSZ");
this.now = sdf.format(now);
outputPath = new VoltFile(path);
if (!outputPath.exists()) {
throw new RuntimeException("Output path for duplicates \"" + outputPath + "\" does not exist");
}
if (!outputPath.canExecute()) {
throw new RuntimeException("Output path for duplicates \"" + outputPath + "\" is not executable");
}
}
public void handleDuplicates(final String tableName, byte duplicates[]) throws IOException {
final byte csvBytes[] = VoltTableUtil.toCSV( PrivateVoltTableFactory.createVoltTableFromBuffer(ByteBuffer.wrap(duplicates), true),
',',
null,
1024 * 512).getSecond();
es.execute(new Runnable() {
@Override
public void run() {
try {
handleDuplicatesInternal(tableName, csvBytes);
} catch (Exception e) {
VoltDB.crashLocalVoltDB("Error handling duplicate rows during snapshot restore", true, e);
}
}
});
}
private void handleDuplicatesInternal(final String tableName, byte csvBytes[]) throws Exception {
FileChannel fc = getTableFile(tableName);
final ByteBuffer buf = ByteBuffer.wrap(csvBytes);
while (buf.hasRemaining()) {
fc.write(buf);
}
}
private FileChannel getTableFile(String tableName) throws Exception {
FileChannel fc = m_outputFiles.get(tableName);
if (fc == null) {
File outfile = new File(outputPath, tableName + "-duplicates-" + now + ".csv");
String message = "Found duplicate rows for table " + tableName + " they will be output to " + outfile;
SNAP_LOG.warn(message);
@SuppressWarnings("resource")
FileOutputStream fos = new FileOutputStream(outfile);
fc = fos.getChannel();
m_outputFiles.put(tableName, fc);
}
return fc;
}
public void close() throws Exception {
es.execute(new Runnable() {
@Override
public void run() {
try {
for (Map.Entry<String, FileChannel> e : m_outputFiles.entrySet()) {
FileChannel fc = e.getValue();
String message = "Output " + fc.size() + " bytes worth of duplicate row data for table " + e.getKey();
SNAP_LOG.warn(message);
fc.force(true);
fc.close();
}
m_outputFiles.clear();
} catch (Exception e) {
VoltDB.crashLocalVoltDB("Error syncing and closing duplicate files during snapshot restore", true, e);
}
}
});
es.shutdown();
es.awaitTermination(365, TimeUnit.DAYS);
}
}