/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.data2.transaction.queue.leveldb;
import co.cask.cdap.api.dataset.table.Row;
import co.cask.cdap.api.dataset.table.Scanner;
import co.cask.cdap.common.queue.QueueName;
import co.cask.cdap.data2.dataset2.lib.table.leveldb.LevelDBTableCore;
import co.cask.cdap.data2.transaction.queue.QueueEntryRow;
import co.cask.cdap.data2.transaction.queue.QueueEvictor;
import co.cask.tephra.Transaction;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* An evictor for the levelDB based queues.
*/
public class LevelDBQueueEvictor implements QueueEvictor {
private static final Logger LOG = LoggerFactory.getLogger(LevelDBQueueEvictor.class);
private final LevelDBTableCore core;
private final byte[] queueRowPrefix;
private final Executor executor;
private final int numGroups;
private final QueueName name;
public LevelDBQueueEvictor(LevelDBTableCore core, QueueName queueName, int numGroups, Executor executor) {
this.core = core;
this.executor = executor;
this.queueRowPrefix = QueueEntryRow.getQueueRowPrefix(queueName);
this.numGroups = numGroups;
this.name = queueName;
}
@Override
public ListenableFuture<Integer> evict(final Transaction transaction) {
final SettableFuture<Integer> result = SettableFuture.create();
executor.execute(new Runnable() {
@Override
public void run() {
try {
result.set(doEvict(transaction));
} catch (Throwable t) {
result.setException(t);
}
}
});
return result;
}
private synchronized int doEvict(Transaction transaction) throws IOException {
final byte[] stopRow = QueueEntryRow.getStopRowForTransaction(queueRowPrefix, transaction);
Row row;
List<byte[]> rowsToDelete = Lists.newArrayList();
// the scan must be non-transactional in order to see the state columns (which have latest timestamp)
Scanner scanner = core.scan(queueRowPrefix, stopRow, null, null, Transaction.ALL_VISIBLE_LATEST);
try {
while ((row = scanner.next()) != null) {
int processed = 0;
for (Map.Entry<byte[], byte[]> entry : row.getColumns().entrySet()) {
// is it a state column for a consumer instance?
if (!QueueEntryRow.isStateColumn(entry.getKey())) {
continue;
}
// is the write pointer of this state committed w.r.t. the current transaction, and is it processed?
if (QueueEntryRow.isCommittedProcessed(entry.getValue(), transaction)) {
++processed;
}
}
if (processed >= numGroups) {
rowsToDelete.add(row.getRow());
}
}
} finally {
scanner.close();
}
if (!rowsToDelete.isEmpty()) {
core.deleteRows(rowsToDelete);
LOG.trace("Evicted {} entries from queue {}", rowsToDelete.size(), name);
} else {
LOG.trace("Nothing to evict from queue {}", name);
}
return rowsToDelete.size();
}
}