/* * 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.logging.save; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.api.dataset.table.Row; import co.cask.cdap.api.dataset.table.Table; import co.cask.cdap.data2.transaction.Transactions; import co.cask.tephra.TransactionAware; import co.cask.tephra.TransactionExecutor; import co.cask.tephra.TransactionExecutorFactory; import com.google.common.collect.ImmutableMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Manages reading/writing of checkpoint information for a topic and partition. */ public final class CheckpointManager { private static final Logger LOG = LoggerFactory.getLogger(CheckpointManager.class); private static final byte [] OFFSET_COLNAME = Bytes.toBytes("nextOffset"); private static final byte [] MAX_TIME_COLNAME = Bytes.toBytes("maxEventTime"); private final byte [] rowKeyPrefix; private final LogSaverTableUtil tableUtil; private final TransactionExecutorFactory transactionExecutorFactory; private Map<Integer, Checkpoint> lastCheckpoint; public CheckpointManager(final LogSaverTableUtil tableUtil, TransactionExecutorFactory txExecutorFactory, String topic, int prefix) { this.rowKeyPrefix = Bytes.add(Bytes.toBytes(prefix), Bytes.toBytes(topic)); this.tableUtil = tableUtil; this.transactionExecutorFactory = txExecutorFactory; this.lastCheckpoint = new HashMap<>(); } private <T> T execute(TransactionExecutor.Function<Table, T> func) { try { Table table = tableUtil.getMetaTable(); if (table instanceof TransactionAware) { TransactionExecutor txExecutor = Transactions.createTransactionExecutor(transactionExecutorFactory, (TransactionAware) table); return txExecutor.execute(func, table); } else { throw new RuntimeException(String.format("Table %s is not TransactionAware, " + "Exception while trying to cast it to TransactionAware. " + "Please check why the table is not TransactionAware", table)); } } catch (Exception e) { throw new RuntimeException(String.format("Error accessing %s table", tableUtil.getMetaTableName()), e); } } private void execute(TransactionExecutor.Procedure<Table> procedure) { try { Table table = tableUtil.getMetaTable(); if (table instanceof TransactionAware) { TransactionExecutor txExecutor = Transactions.createTransactionExecutor(transactionExecutorFactory, (TransactionAware) table); txExecutor.execute(procedure, table); } else { throw new RuntimeException(String.format("Table %s is not TransactionAware, " + "Exception while trying to cast it to TransactionAware. " + "Please check why the table is not TransactionAware", table)); } } catch (Exception e) { throw new RuntimeException(String.format("Error accessing %s table", tableUtil.getMetaTableName()), e); } } public void saveCheckpoint(final Map<Integer, Checkpoint> checkpoints) throws Exception { // if the checkpoints have not changed, we skip writing to table and return. if (lastCheckpoint.equals(checkpoints)) { return; } execute(new TransactionExecutor.Procedure<Table>() { @Override public void apply(Table table) throws Exception { for (Map.Entry<Integer, Checkpoint> entry : checkpoints.entrySet()) { byte[] key = Bytes.add(rowKeyPrefix, Bytes.toBytes(entry.getKey())); Checkpoint checkpoint = entry.getValue(); table.put(key, OFFSET_COLNAME, Bytes.toBytes(checkpoint.getNextOffset())); table.put(key, MAX_TIME_COLNAME, Bytes.toBytes(checkpoint.getMaxEventTime())); } // update last checkpoint lastCheckpoint = ImmutableMap.copyOf(checkpoints); } }); LOG.trace("Saving checkpoints for partitions {}", checkpoints); } public Map<Integer, Checkpoint> getCheckpoint(final Set<Integer> partitions) throws Exception { return execute(new TransactionExecutor.Function<Table, Map<Integer, Checkpoint>>() { @Override public Map<Integer, Checkpoint> apply(Table table) throws Exception { Map<Integer, Checkpoint> checkpoints = new HashMap<Integer, Checkpoint>(); for (final int partition : partitions) { Row result = table.get(Bytes.add(rowKeyPrefix, Bytes.toBytes(partition))); checkpoints.put(partition, new Checkpoint(result.getLong(OFFSET_COLNAME, -1), result.getLong(MAX_TIME_COLNAME, -1))); } return checkpoints; } }); } public Checkpoint getCheckpoint(final int partition) throws Exception { Checkpoint checkpoint = execute(new TransactionExecutor.Function<Table, Checkpoint>() { @Override public Checkpoint apply(Table table) throws Exception { Row result = table.get(Bytes.add(rowKeyPrefix, Bytes.toBytes(partition))); return new Checkpoint(result.getLong(OFFSET_COLNAME, -1), result.getLong(MAX_TIME_COLNAME, -1)); } }); LOG.trace("Read checkpoint {} for partition {}", checkpoint, partition); return checkpoint; } }