org.apache.tephra.util.TxUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.tephra.util.TxUtils.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.tephra.util;

import com.google.common.primitives.Longs;
import org.apache.tephra.Transaction;
import org.apache.tephra.TransactionManager;
import org.apache.tephra.TransactionType;
import org.apache.tephra.TxConstants;
import org.apache.tephra.persist.TransactionVisibilityState;

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;
    }

    /**
     * Returns the maximum transaction that can be removed from the invalid list for the state represented by the given
     * transaction.
     */
    public static long getPruneUpperBound(Transaction tx) {
        // If there are no invalid transactions, and no in-progress transactions then we can prune the invalid list
        // up to the current read pointer
        if (tx.getInvalids().length == 0 && tx.getInProgress().length == 0) {
            return tx.getReadPointer() - 1;
        }

        long maxInvalidTx = tx.getInvalids().length > 0 ? tx.getInvalids()[tx.getInvalids().length - 1]
                : Transaction.NO_TX_IN_PROGRESS;
        long firstInProgress = tx.getFirstInProgress();
        return Math.min(maxInvalidTx, firstInProgress - 1);
    }

    /**
     * Returns the greatest transaction that has passed the maximum duration a transaction can be used for data writes.
     * In other words, at <code>timeMills</code> there can be no writes from transactions equal to or smaller
     * than the returned bound.
     *
     * @param timeMills time in milliseconds for which the inactive transaction bound needs to be determined
     * @param txMaxLifetimeMillis maximum duration a transaction can be used for data writes, in milliseconds
     */
    public static long getInactiveTxBound(long timeMills, long txMaxLifetimeMillis) {
        return (timeMills - txMaxLifetimeMillis) * TxConstants.MAX_TX_PER_MS - 1;
    }

    /**
     * Returns the timestamp at which the given transaction id was generated.
     *
     * @param txId transaction id
     * @return timestamp in milliseconds
     */
    public static long getTimestamp(long txId) {
        return txId / TxConstants.MAX_TX_PER_MS;
    }
}