/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package windowing;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ClientResponseWithPartitionKey;
import org.voltdb.types.TimestampType;
/**
* Runnable-implementor that runs a stored procedure at every partition.
* Each stored procedure deletes tuples that are older than a given
* cutoff date or past the maximum desired row count for the partition.
* All stored procedures are run concurrently using asynchronous calls.
*
*/
public class ContinuousDeleter implements Runnable {
// Global state
final WindowingApp app;
// track failures for reporting
final AtomicLong failureCount = new AtomicLong(0);
ContinuousDeleter(WindowingApp app) {
this.app = app;
}
@Override
public void run() {
// Will be set to true if any partition needs to delete more rows to meet the goals.
AtomicBoolean unfinished = new AtomicBoolean(false);
// Default yield time between deletes. May be shortened if there are a ton of tuples that need deleting.
long yieldTimeMs = app.config.deleteyieldtime;
try {
// Get the targets from the main app class. Only one will be used, depending on whether the user is deleting
// old rows by date or by row count.
TimestampType dateTarget = app.getTargetDate();
long rowTarget = app.getTargetRowsPerPartition();
// Send the procedure call to all partitions and get results from each partitions.
ClientResponseWithPartitionKey[] responses;
if (app.config.historyseconds > 0) {
responses = app.client.callAllPartitionProcedure("DeleteAfterDate", dateTarget, app.config.deletechunksize);
}
else /* if (app.config.maxrows > 0) */ {
responses = app.client.callAllPartitionProcedure("DeleteOldestToTarget", rowTarget, app.config.deletechunksize);
}
app.updatePartitionCount(responses.length);
for (ClientResponseWithPartitionKey resp: responses) {
if (resp.response.getStatus() == ClientResponse.SUCCESS) {
long tuplesDeleted = resp.response.getResults()[0].asScalarLong();
app.addToDeletedTuples(tuplesDeleted);
// If the procedure deleted up to its limit, reduce the time before the deletes process runs again.
if (tuplesDeleted >= app.config.deletechunksize) {
unfinished.set(true);
}
} else {
failureCount.incrementAndGet();
}
}
// If this round of deletes didn't remove all of the tuples that could have been removed, reduce the pause time before the
// next round to zero. If this process still can't keep up, then you can always add a second ContinuousDeleter instance in
// WindowingApp and schedule two of them to run concurrently.
if (unfinished.get()) {
yieldTimeMs = 0;
}
} catch (Exception e) {
failureCount.incrementAndGet();
} catch (Throwable t) {
t.printStackTrace();
// Live dangerously and ignore this.
} finally {
// Use a finally block to ensure this task is re-scheduled.
app.scheduler.schedule(this, yieldTimeMs, TimeUnit.MILLISECONDS);
}
}
}