/* * 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.tephra.util; import co.cask.tephra.Transaction; import co.cask.tephra.TransactionManager; import co.cask.tephra.TransactionType; import co.cask.tephra.TxConstants; import co.cask.tephra.persist.TransactionVisibilityState; import com.google.common.primitives.Longs; import java.util.Map; /** * Utility methods supporting transaction operations. */ public class TxUtils { // Any cell with timestamp less than MAX_NON_TX_TIMESTAMP is assumed to be pre-existing data, // i.e. data written before table was converted into transactional table using Tephra. // Using 1.1 times current time to determine whether a timestamp is transactional timestamp or not is safe, and does // not produce any false positives or false negatives. // // To prove this, let's say the earliest transactional timestamp written by Tephra was in year 2000, and the oldest // that will be written is in year 2200. // 01-Jan-2000 GMT is 946684800000 milliseconds since epoch. // 31-Dec-2200 GMT is 7289654399000 milliseconds since epoch. // // Let's say that we enabled transactions on a table on 01-Jan-2000, then 1.1 * 946684800000 = 31-Dec-2002. Using // 31-Dec-2002, we can safely say from 01-Jan-2000 onwards, whether a cell timestamp is // non-transactional (<= 946684800000). // Note that transactional timestamp will be greater than 946684800000000000 (> year 31969) at this instant. // // On the other end, let's say we enabled transactions on a table on 31-Dec-2200, // then 1.1 * 7289654399000 = 07-Feb-2224. Again, we can use this time from 31-Dec-2200 onwards to say whether a // cell timestamp is transactional (<= 7289654399000). // Note that transactional timestamp will be greater than 7289654399000000000 (> year 232969) at this instant. private static final long MAX_NON_TX_TIMESTAMP = (long) (System.currentTimeMillis() * 1.1); /** * Returns the oldest visible timestamp for the given transaction, based on the TTLs configured for each column * family. If no TTL is set on any column family, the oldest visible timestamp will be {@code 0}. * @param ttlByFamily A map of column family name to TTL value (in milliseconds) * @param tx The current transaction * @return The oldest timestamp that will be visible for the given transaction and TTL configuration */ public static long getOldestVisibleTimestamp(Map<byte[], Long> ttlByFamily, Transaction tx) { long maxTTL = getMaxTTL(ttlByFamily); // we know that data will not be cleaned up while this tx is running up to this point as janitor uses it return maxTTL < Long.MAX_VALUE ? tx.getVisibilityUpperBound() - maxTTL * TxConstants.MAX_TX_PER_MS : 0; } /** * Returns the oldest visible timestamp for the given transaction, based on the TTLs configured for each column * family. If no TTL is set on any column family, the oldest visible timestamp will be {@code 0}. * @param ttlByFamily A map of column family name to TTL value (in milliseconds) * @param tx The current transaction * @param readNonTxnData indicates that the timestamp returned should allow reading non-transactional data * @return The oldest timestamp that will be visible for the given transaction and TTL configuration */ public static long getOldestVisibleTimestamp(Map<byte[], Long> ttlByFamily, Transaction tx, boolean readNonTxnData) { if (readNonTxnData) { long maxTTL = getMaxTTL(ttlByFamily); return maxTTL < Long.MAX_VALUE ? System.currentTimeMillis() - maxTTL : 0; } return getOldestVisibleTimestamp(ttlByFamily, tx); } /** * Returns the maximum timestamp to use for time-range operations, based on the given transaction. * @param tx The current transaction * @return The maximum timestamp (exclusive) to use for time-range operations */ public static long getMaxVisibleTimestamp(Transaction tx) { // NOTE: +1 here because we want read up to writepointer inclusive, but timerange's end is exclusive // however, we also need to guard against overflow in the case write pointer is set to MAX_VALUE return tx.getWritePointer() < Long.MAX_VALUE ? tx.getWritePointer() + 1 : tx.getWritePointer(); } /** * Creates a "dummy" transaction based on the given txVisibilityState's state. This is not a "real" transaction in * the sense that it has not been started, data should not be written with it, and it cannot be committed. However, * this can still be useful for filtering data according to the txVisibilityState's state. Instead of the actual * write pointer from the txVisibilityState, however, we use {@code Long.MAX_VALUE} to avoid mis-identifying any cells * as being written by this transaction (and therefore visible). */ public static Transaction createDummyTransaction(TransactionVisibilityState txVisibilityState) { return new Transaction(txVisibilityState.getReadPointer(), Long.MAX_VALUE, Longs.toArray(txVisibilityState.getInvalid()), Longs.toArray(txVisibilityState.getInProgress().keySet()), TxUtils.getFirstShortInProgress(txVisibilityState.getInProgress()), TransactionType.SHORT); } /** * Returns the write pointer for the first "short" transaction that in the in-progress set, or * {@link Transaction#NO_TX_IN_PROGRESS} if none. */ public static long getFirstShortInProgress(Map<Long, TransactionManager.InProgressTx> inProgress) { long firstShort = Transaction.NO_TX_IN_PROGRESS; for (Map.Entry<Long, TransactionManager.InProgressTx> entry : inProgress.entrySet()) { if (!entry.getValue().isLongRunning()) { firstShort = entry.getKey(); break; } } return firstShort; } /** * Returns the timestamp for calculating time to live for the given cell timestamp. * This takes into account pre-existing non-transactional cells while calculating the time. */ public static long getTimestampForTTL(long cellTs) { return isPreExistingVersion(cellTs) ? cellTs * TxConstants.MAX_TX_PER_MS : cellTs; } /** * Returns the max TTL for the given TTL values. Returns Long.MAX_VALUE if any of the column families has no TTL set. */ private static long getMaxTTL(Map<byte[], Long> ttlByFamily) { long maxTTL = 0; for (Long familyTTL : ttlByFamily.values()) { maxTTL = Math.max(familyTTL <= 0 ? Long.MAX_VALUE : familyTTL, maxTTL); } return maxTTL == 0 ? Long.MAX_VALUE : maxTTL; } /** * Returns true if version was written before table was converted into transactional table, false otherwise. */ public static boolean isPreExistingVersion(long version) { return version < MAX_NON_TX_TIMESTAMP; } }