org.apache.hadoop.hbase.index.coprocessor.regionserver.IndexRegionObserver.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.index.coprocessor.regionserver.IndexRegionObserver.java

Source

/**
 * Copyright 2011 The Apache Software Foundation
 *
 * 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.hadoop.hbase.index.coprocessor.regionserver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.RegionObserverExt;
import org.apache.hadoop.hbase.index.ColumnQualifier;
import org.apache.hadoop.hbase.index.Constants;
import org.apache.hadoop.hbase.index.IndexSpecification;
import org.apache.hadoop.hbase.index.IndexedHTableDescriptor;
import org.apache.hadoop.hbase.index.manager.IndexManager;
import org.apache.hadoop.hbase.index.util.ByteArrayBuilder;
import org.apache.hadoop.hbase.index.util.IndexUtils;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.regionserver.InternalScanner;
import org.apache.hadoop.hbase.regionserver.KeyValueScanner;
import org.apache.hadoop.hbase.regionserver.OperationStatus;
import org.apache.hadoop.hbase.regionserver.RegionScanner;
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
import org.apache.hadoop.hbase.regionserver.ScanType;
import org.apache.hadoop.hbase.regionserver.SplitTransaction;
import org.apache.hadoop.hbase.regionserver.SplitTransaction.SplitInfo;
import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.PairOfSameType;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

public class IndexRegionObserver extends BaseRegionObserver implements RegionObserverExt {

    private static final Log LOG = LogFactory.getLog(IndexRegionObserver.class);

    // variable will be set to true in test case for testing
    // All below public static fields are used for testing.
    static boolean isTestingEnabled = false;

    public static boolean isSeekpointAddded = false;

    public static boolean isIndexedFlowUsed = false;

    public static List<byte[]> seekPoints = null;

    public static List<byte[]> seekPointsForMultipleIndices = null;

    private Map<RegionScanner, SeekPointFetcher> scannerMap = new ConcurrentHashMap<RegionScanner, SeekPointFetcher>();

    private IndexManager indexManager = IndexManager.getInstance();

    public static final ThreadLocal<IndexEdits> threadLocal = new ThreadLocal<IndexEdits>() {
        @Override
        protected IndexEdits initialValue() {
            return new IndexEdits();
        }
    };

    @Override
    public void postOpen(ObserverContext<RegionCoprocessorEnvironment> contx) {
        byte[] tableName = contx.getEnvironment().getRegion().getTableDesc().getName();
        String tableNameStr = Bytes.toString(tableName);
        if (IndexUtils.isCatalogTable(tableName) || IndexUtils.isIndexTable(tableNameStr)) {
            return;
        }
        LOG.trace("Entering postOpen for the table " + tableNameStr);
        this.indexManager.incrementRegionCount(tableNameStr);
        List<IndexSpecification> list = indexManager.getIndicesForTable(tableNameStr);
        if (null != list) {
            LOG.trace("Index Manager already contains an entry for the table " + ". Hence returning from postOpen");
            return;
        }
        RegionServerServices rss = contx.getEnvironment().getRegionServerServices();
        Configuration conf = rss.getConfiguration();
        IndexedHTableDescriptor tableDescriptor = null;
        try {
            tableDescriptor = IndexUtils.getIndexedHTableDescriptor(tableName, conf);
        } catch (IOException e) {
            rss.abort("Some unidentified scenario while reading from the "
                    + "table descriptor . Aborting RegionServer", e);
        }
        if (tableDescriptor != null) {
            list = tableDescriptor.getIndices();
            if (list != null && list.size() > 0) {
                indexManager.addIndexForTable(tableNameStr, list);
                LOG.trace("Added index Specification in the Manager for the " + tableNameStr);
            } else {
                list = new ArrayList<IndexSpecification>();
                indexManager.addIndexForTable(tableNameStr, list);
                LOG.trace("Added index Specification in the Manager for the " + tableNameStr);
            }
        }
        LOG.trace("Exiting postOpen for the table " + tableNameStr);
    }

    @Override
    public void preBatchMutate(final ObserverContext<RegionCoprocessorEnvironment> ctx,
            final List<Pair<Mutation, OperationStatus>> mutationVsBatchOp, final WALEdit edit) throws IOException {
        HRegionServer rs = (HRegionServer) ctx.getEnvironment().getRegionServerServices();
        HRegion userRegion = ctx.getEnvironment().getRegion();
        HTableDescriptor userTableDesc = userRegion.getTableDesc();
        String tableName = userTableDesc.getNameAsString();
        if (IndexUtils.isCatalogTable(userTableDesc.getName()) || IndexUtils.isIndexTable(tableName)) {
            return;
        }
        List<IndexSpecification> indices = indexManager.getIndicesForTable(tableName);
        if (indices == null || indices.isEmpty()) {
            LOG.trace("skipping preBatchMutate for the table " + tableName + " as there are no indices");
            return;
        }
        LOG.trace("Entering preBatchMutate for the table " + tableName);
        LOG.trace("Indices for the table " + tableName + " are: " + indices);
        HRegion indexRegion = getIndexTableRegion(tableName, userRegion, rs);
        // Storing this found HRegion in the index table within the thread locale.
        IndexEdits indexEdits = threadLocal.get();
        indexEdits.indexRegion = indexRegion;
        for (Pair<Mutation, OperationStatus> mutation : mutationVsBatchOp) {
            if (mutation.getSecond().getOperationStatusCode() != OperationStatusCode.NOT_RUN) {
                continue;
            }
            // only for successful puts
            Mutation m = mutation.getFirst();
            if (m instanceof Put) {
                try {
                    prepareIndexMutations(indices, userRegion, m, tableName, indexRegion);
                } catch (IOException e) {
                    mutation.setSecond(
                            new OperationStatus(OperationStatusCode.SANITY_CHECK_FAILURE, e.getMessage()));
                }
            } else if (m instanceof Delete) {
                prepareIndexMutations(indices, userRegion, m, tableName, indexRegion);
            }
        }
        indexEdits.setUpdateLocked();
        indexRegion.updateLock();
        LOG.trace("Exiting preBatchMutate for the table " + tableName);
    }

    private HRegion getIndexTableRegion(String tableName, HRegion userRegion, HRegionServer rs) throws IOException {
        String indexTableName = IndexUtils.getIndexTableName(tableName);
        Collection<HRegion> idxTabRegions = rs.getOnlineRegions(Bytes.toBytes(indexTableName));
        for (HRegion idxTabRegion : idxTabRegions) {
            // TODO start key check is enough? May be we can check for the
            // possibility for N-1 Mapping?
            if (Bytes.equals(idxTabRegion.getStartKey(), userRegion.getStartKey())) {
                return idxTabRegion;
            }
        }
        // No corresponding index region found in the RS online regions list!
        LOG.warn("Index Region not found on the region server . " + "So skipping the put. Need Balancing");
        // TODO give a proper Exception msg
        throw new IOException();
    }

    private void prepareIndexMutations(List<IndexSpecification> indices, HRegion userRegion, Mutation mutation,
            String tableName, HRegion indexRegion) throws IOException {
        IndexEdits indexEdits = threadLocal.get();
        if (mutation instanceof Put) {
            for (IndexSpecification index : indices) {
                // Handle each of the index
                Mutation indexPut = IndexUtils.prepareIndexPut((Put) mutation, index, indexRegion);
                if (null != indexPut) {
                    // This mutation can be null when the user table mutation is not
                    // containing all of the indexed col value.
                    indexEdits.add(indexPut);
                }
            }
        } else if (mutation instanceof Delete) {
            Collection<? extends Mutation> indexDeletes = prepareIndexDeletes((Delete) mutation, userRegion,
                    indices, indexRegion);
            indexEdits.addAll(indexDeletes);
        } else {
            // TODO : Log or throw exception
        }
    }

    Collection<? extends Mutation> prepareIndexDeletes(Delete delete, HRegion userRegion,
            List<IndexSpecification> indexSpecs, HRegion indexRegion) throws IOException {
        Collection<Delete> indexDeletes = new LinkedHashSet<Delete>();
        for (Entry<byte[], List<KeyValue>> entry : delete.getFamilyMap().entrySet()) {
            for (KeyValue kv : entry.getValue()) {
                indexDeletes.addAll(getIndexDeletes(indexSpecs, userRegion, indexRegion, kv));
            }
        }
        return indexDeletes;
    }

    private static Collection<Delete> getIndexDeletes(List<IndexSpecification> indexSpecs, HRegion userRegion,
            HRegion indexRegion, KeyValue deleteKV) throws IOException {
        Collection<Delete> indexDeletes = new LinkedHashSet<Delete>();
        List<IndexSpecification> indicesToUpdate = new LinkedList<IndexSpecification>();
        Multimap<Long, KeyValue> groupedKV = doGetAndGroupByTS(indexSpecs, userRegion, deleteKV, indicesToUpdate);

        // There can be multiple index kvs for each user kv
        // So, prepare all resultant index delete kvs for this user delete kv
        for (Entry<Long, Collection<KeyValue>> entry : groupedKV.asMap().entrySet()) {
            for (IndexSpecification index : indicesToUpdate) {
                ByteArrayBuilder indexRow = IndexUtils.getIndexRowKeyHeader(index, indexRegion.getStartKey(),
                        deleteKV.getRow());
                boolean update = false;
                for (ColumnQualifier cq : index.getIndexColumns()) {
                    KeyValue kvFound = null;
                    for (KeyValue kv : entry.getValue()) {
                        if (Bytes.equals(cq.getColumnFamily(), kv.getFamily())
                                && Bytes.equals(cq.getQualifier(), kv.getQualifier())) {
                            kvFound = kv;
                            update = true;
                            break;
                        }
                    }
                    if (kvFound == null) {
                        indexRow.position(indexRow.position() + cq.getMaxValueLength());
                    } else {
                        IndexUtils.updateRowKeyForKV(cq, kvFound, indexRow);
                    }
                }
                if (update) {
                    // Append the actual row key at the end of the index row key.
                    indexRow.put(deleteKV.getRow());
                    Delete idxDelete = new Delete(indexRow.array());
                    if (deleteKV.isDeleteType()) {
                        idxDelete.deleteColumn(Constants.IDX_COL_FAMILY, Constants.IDX_COL_QUAL, entry.getKey());
                    } else {
                        idxDelete.deleteFamily(Constants.IDX_COL_FAMILY, entry.getKey());
                    }
                    idxDelete.setWriteToWAL(false);
                    indexDeletes.add(idxDelete);
                }
            }
        }
        return indexDeletes;
    }

    private static Multimap<Long, KeyValue> doGetAndGroupByTS(List<IndexSpecification> indexSpecs,
            HRegion userRegion, KeyValue deleteKV, List<IndexSpecification> indicesToConsider) throws IOException {

        Get get = new Get(deleteKV.getRow());
        long maxTS = HConstants.LATEST_TIMESTAMP;

        if (deleteKV.getTimestamp() < maxTS) {
            // Add +1 to make the current get includes the timestamp
            maxTS = deleteKV.getTimestamp() + 1;
        }
        get.setTimeRange(HConstants.OLDEST_TIMESTAMP, maxTS);

        for (IndexSpecification index : indexSpecs) {
            // Get all indices involves this family/qualifier
            if (index.contains(deleteKV.getFamily(), deleteKV.getQualifier())) {
                indicesToConsider.add(index);
                for (ColumnQualifier cq : index.getIndexColumns()) {
                    get.addColumn(cq.getColumnFamily(), cq.getQualifier());
                }
            }
        }
        if (deleteKV.isDeleteType()) {
            get.setMaxVersions(1);
        } else if (deleteKV.isDeleteColumnOrFamily()) {
            get.setMaxVersions();
        }
        List<KeyValue> userKVs = userRegion.get(get, 0).list();

        // Group KV based on timestamp
        Multimap<Long, KeyValue> groupedKV = HashMultimap.create();

        if (userKVs != null) {
            for (KeyValue userKV : userKVs) {
                groupedKV.put(userKV.getTimestamp(), userKV);
            }
        }
        return groupedKV;
    }

    // collection of edits for index table's memstore and WAL
    public static class IndexEdits {
        private WALEdit walEdit = new WALEdit();
        private HRegion indexRegion;
        private boolean updatesLocked = false;

        /**
         * Collection of mutations with locks. Locks will be null always as they not yet acquired for
         * index table.
         * @see HRegion#batchMutate(Pair[])
         */
        private List<Pair<Mutation, Integer>> mutations = new ArrayList<Pair<Mutation, Integer>>();

        public WALEdit getWALEdit() {
            return this.walEdit;
        }

        public boolean isUpdatesLocked() {
            return this.updatesLocked;
        }

        public void setUpdateLocked() {
            updatesLocked = true;
        }

        public void add(Mutation mutation) {
            // Check if WAL is disabled
            for (List<KeyValue> kvs : mutation.getFamilyMap().values()) {
                for (KeyValue kv : kvs) {
                    this.walEdit.add(kv);
                }
            }
            // There is no lock acquired for index table. So, set it to null
            this.mutations.add(new Pair<Mutation, Integer>(mutation, null));
        }

        public void addAll(Collection<? extends Mutation> mutations) {
            for (Mutation mutation : mutations) {
                add(mutation);
            }
        }

        public List<Pair<Mutation, Integer>> getIndexMutations() {
            return this.mutations;
        }

        public HRegion getRegion() {
            return this.indexRegion;
        }
    }

    @Override
    public void postBatchMutate(final ObserverContext<RegionCoprocessorEnvironment> ctx,
            final List<Mutation> mutations, WALEdit walEdit) {
        HTableDescriptor userTableDesc = ctx.getEnvironment().getRegion().getTableDesc();
        String tableName = userTableDesc.getNameAsString();
        if (IndexUtils.isCatalogTable(userTableDesc.getName()) || IndexUtils.isIndexTable(tableName)) {
            return;
        }
        List<IndexSpecification> indices = indexManager.getIndicesForTable(tableName);
        if (indices == null || indices.isEmpty()) {
            LOG.trace("skipping postBatchMutate for the table " + tableName + " as there are no indices");
            return;
        }
        LOG.trace("Entering postBatchMutate for the table " + tableName);
        IndexEdits indexEdits = threadLocal.get();
        List<Pair<Mutation, Integer>> indexMutations = indexEdits.getIndexMutations();

        if (indexMutations.size() == 0) {
            return;
        }
        HRegion hr = indexEdits.getRegion();
        LOG.trace("Updating index table " + hr.getRegionInfo().getTableNameAsString());
        try {
            hr.batchMutateForIndex(indexMutations.toArray(new Pair[indexMutations.size()]));
        } catch (IOException e) {
            // TODO This can come? If so we need to revert the actual put
            // and make the op failed.
            LOG.error("Error putting data into the index region", e);
        }
        LOG.trace("Exiting postBatchMutate for the table " + tableName);
    }

    @Override
    public void postCompleteBatchMutate(final ObserverContext<RegionCoprocessorEnvironment> ctx,
            List<Mutation> mutations) throws IOException {
        IndexEdits indexEdits = threadLocal.get();
        if (indexEdits != null) {
            if (indexEdits.isUpdatesLocked()) {
                indexEdits.getRegion().releaseLock();
            }

        }
        threadLocal.remove();

    }

    @Override
    public boolean postScannerFilterRow(ObserverContext<RegionCoprocessorEnvironment> e, InternalScanner s,
            byte[] currentRow, boolean hasMore) throws IOException {
        String tableName = e.getEnvironment().getRegion().getTableDesc().getNameAsString();
        if (IndexUtils.isIndexTable(tableName)) {
            return true;
        }
        SeekAndReadRegionScanner bsrs = SeekAndReadRegionScannerHolder.getRegionScanner();
        if (bsrs != null) {
            while (false == bsrs.seekToNextPoint()) {
                SeekPointFetcher seekPointFetcher = scannerMap.get(bsrs);
                if (null != seekPointFetcher) {
                    List<byte[]> seekPoints = new ArrayList<byte[]>(1);
                    seekPointFetcher.nextSeekPoints(seekPoints, 1);
                    // TODO use return boolean?
                    if (seekPoints.isEmpty()) {
                        LOG.trace("No seekpoints are remaining hence returning..  ");
                        return false;
                    }
                    bsrs.addSeekPoints(seekPoints);
                    if (isTestingEnabled) {
                        setSeekPoints(seekPoints);
                        setSeekpointAdded(true);
                        addSeekPoints(seekPoints);
                    }
                } else {
                    // This will happen for a region with no index
                    break;
                }
            }
        }
        return true;
    }

    public RegionScanner postScannerOpen(ObserverContext<RegionCoprocessorEnvironment> e, Scan scan,
            RegionScanner s) {
        HRegion region = e.getEnvironment().getRegion();
        String tableName = region.getTableDesc().getNameAsString();
        HRegionServer rs = (HRegionServer) e.getEnvironment().getRegionServerServices();
        // If the passed region is a region from an indexed table
        SeekAndReadRegionScanner bsrs = null;

        try {
            List<IndexSpecification> indexlist = IndexManager.getInstance().getIndicesForTable(tableName);
            if (indexlist != null) {
                if (indexlist == null || indexlist.isEmpty()) {
                    // Not an indexed table. Just return.
                    return s;
                }
                LOG.trace("Entering postScannerOpen for the table " + tableName);
                Collection<HRegion> onlineRegions = rs.getOnlineRegionsLocalContext();
                for (HRegion onlineIdxRegion : onlineRegions) {
                    if (IndexUtils
                            .isCatalogTable(Bytes.toBytes(onlineIdxRegion.getTableDesc().getNameAsString()))) {
                        continue;
                    }
                    if (onlineIdxRegion.equals(region)) {
                        continue;
                    }
                    if (Bytes.equals(onlineIdxRegion.getStartKey(), region.getStartKey()) && Bytes.equals(
                            Bytes.toBytes(IndexUtils.getIndexTableName(region.getTableDesc().getNameAsString())),
                            onlineIdxRegion.getTableDesc().getName())) {
                        ScanFilterEvaluator mapper = new ScanFilterEvaluator();
                        IndexRegionScanner indexScanner = mapper.evaluate(scan, indexlist,
                                onlineIdxRegion.getStartKey(), onlineIdxRegion, tableName);
                        if (indexScanner == null)
                            return s;
                        SeekPointFetcher spf = new SeekPointFetcher(indexScanner);
                        ReInitializableRegionScanner reinitializeScanner = new ReInitializableRegionScannerImpl(s,
                                scan, spf);
                        bsrs = new BackwardSeekableRegionScanner(reinitializeScanner, scan, region, null);
                        scannerMap.put(bsrs, spf);
                        LOG.trace("Scanner Map has " + scannerMap);
                        break;
                    }
                }
                LOG.trace("Exiting postScannerOpen for the table " + tableName);
            }
        } catch (Exception ex) {
            LOG.error("Exception occured in postScannerOpen for the table " + tableName, ex);
        }
        if (bsrs != null) {
            return bsrs;
        } else {
            return s;
        }
    }

    public boolean preScannerNext(ObserverContext<RegionCoprocessorEnvironment> e, InternalScanner s,
            List<Result> results, int nbRows, boolean hasMore) throws IOException {
        HRegion region = e.getEnvironment().getRegion();
        String tableName = region.getTableDesc().getNameAsString();
        try {
            if (s instanceof SeekAndReadRegionScanner) {
                LOG.trace("Entering preScannerNext for the table " + tableName);
                BackwardSeekableRegionScanner bsrs = (BackwardSeekableRegionScanner) s;
                SeekAndReadRegionScannerHolder.setRegionScanner(bsrs);
                SeekPointFetcher spf = scannerMap.get(bsrs);
                List<byte[]> seekPoints = null;
                if (spf != null) {
                    if (isTestingEnabled) {
                        setIndexedFlowUsed(true);
                    }
                    seekPoints = new ArrayList<byte[]>();
                    spf.nextSeekPoints(seekPoints, nbRows);
                }
                if (seekPoints == null || seekPoints.isEmpty()) {
                    LOG.trace("No seekpoints are remaining hence returning..  ");
                    SeekAndReadRegionScannerHolder.removeRegionScanner();
                    e.bypass();
                    return false;
                }
                bsrs.addSeekPoints(seekPoints);
                // This setting is just for testing purpose
                if (isTestingEnabled) {
                    setSeekPoints(seekPoints);
                    setSeekpointAdded(true);
                    addSeekPoints(seekPoints);
                }
                LOG.trace("Exiting preScannerNext for the table " + tableName);
            }
        } catch (Exception ex) {
            LOG.error("Exception occured in preScannerNext for the table " + tableName + ex);
        }
        return true;
    }

    @Override
    public boolean postScannerNext(ObserverContext<RegionCoprocessorEnvironment> e, InternalScanner s,
            List<Result> results, int limit, boolean hasMore) throws IOException {
        if (s instanceof SeekAndReadRegionScanner) {
            SeekAndReadRegionScannerHolder.removeRegionScanner();
        }
        return true;
    }

    @Override
    public void preScannerClose(ObserverContext<RegionCoprocessorEnvironment> e, InternalScanner s)
            throws IOException {
        if (s instanceof BackwardSeekableRegionScanner) {
            scannerMap.remove((RegionScanner) s);
        }
    }

    @Override
    public SplitInfo preSplitBeforePONR(ObserverContext<RegionCoprocessorEnvironment> e, byte[] splitKey)
            throws IOException {
        RegionCoprocessorEnvironment environment = e.getEnvironment();
        HRegionServer rs = (HRegionServer) environment.getRegionServerServices();
        HRegion region = environment.getRegion();
        String userTableName = region.getTableDesc().getNameAsString();
        LOG.trace("Entering preSplitBeforePONR for the table " + userTableName + " for the region "
                + region.getRegionInfo());
        String indexTableName = IndexUtils.getIndexTableName(userTableName);
        if (indexManager.getIndicesForTable(userTableName) != null) {
            HRegion indexRegion = null;
            SplitTransaction st = null;
            try {
                indexRegion = getIndexRegion(rs, region.getStartKey(), indexTableName);
                if (null != indexRegion) {
                    LOG.info("Flushing the cache for the index table " + indexTableName + " for the region "
                            + indexRegion.getRegionInfo());
                    indexRegion.flushcache();
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Forcing split for the index table " + indexTableName + " with split key "
                                + Bytes.toString(splitKey));
                    }
                    st = new SplitTransaction(indexRegion, splitKey);
                    if (!st.prepare()) {
                        LOG.error("Prepare for the index table " + indexTableName + " failed. So returning null. ");
                        return null;
                    }
                    indexRegion.forceSplit(splitKey);
                    PairOfSameType<HRegion> daughterRegions = st.stepsBeforeAddingPONR(rs, rs, false);
                    SplitInfo splitInfo = splitThreadLocal.get();
                    splitInfo.setDaughtersAndTransaction(daughterRegions, st);
                    LOG.info("Daughter regions created for the index table " + indexTableName + " for the region "
                            + indexRegion.getRegionInfo());
                    return splitInfo;
                } else {
                    LOG.error("IndexRegion for the table " + indexTableName + " is null. So returning null. ");
                    return null;
                }
            } catch (Exception ex) {
                LOG.error("Error while spliting the indexTabRegion or not able to get the indexTabRegion:"
                        + indexRegion != null ? indexRegion.getRegionName() : "", ex);
                st.rollback(rs, rs);
                return null;
            }
        }
        LOG.trace("Indexes for the table " + userTableName + " are null. So returning the empty SplitInfo");
        return new SplitInfo();
    }

    @Override
    public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
        if (splitThreadLocal != null) {
            splitThreadLocal.remove();
            splitThreadLocal.set(new SplitInfo());
        }
    }

    private HRegion getIndexRegion(HRegionServer rs, byte[] startKey, String indexTableName) throws IOException {
        List<HRegion> indexTabRegions = rs.getOnlineRegions(Bytes.toBytes(indexTableName));
        for (HRegion indexRegion : indexTabRegions) {
            if (Bytes.equals(startKey, indexRegion.getStartKey())) {
                return indexRegion;
            }
        }
        return null;
    }

    @Override
    public void preSplitAfterPONR(ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException {
        RegionCoprocessorEnvironment environment = ctx.getEnvironment();
        HRegionServer rs = (HRegionServer) environment.getRegionServerServices();
        HRegion region = environment.getRegion();
        String userTableName = region.getTableDesc().getNameAsString();
        String indexTableName = IndexUtils.getIndexTableName(userTableName);
        if (IndexUtils.isIndexTable(userTableName)) {
            return;
        }
        LOG.trace(
                "Entering postSplit for the table " + userTableName + " for the region " + region.getRegionInfo());
        IndexManager indexManager = IndexManager.getInstance();
        SplitTransaction splitTransaction = null;
        if (indexManager.getIndicesForTable(userTableName) != null) {
            try {
                SplitInfo splitInfo = splitThreadLocal.get();
                splitTransaction = splitInfo.getSplitTransaction();
                PairOfSameType<HRegion> daughters = splitInfo.getDaughters();
                if (splitTransaction != null && daughters != null) {
                    splitTransaction.stepsAfterPONR(rs, rs, daughters);
                    LOG.info("Daughter regions are opened and split transaction finished"
                            + " for zknodes for index table " + indexTableName + " for the region "
                            + region.getRegionInfo());
                }
            } catch (Exception ex) {
                String msg = "Splitting of index region has failed in stepsAfterPONR stage so aborting the server";
                LOG.error(msg, ex);
                rs.abort(msg);
            }
        }
    }

    // A thread local variable used to get the splitted region information of the index region.
    // This is needed becuase in order to do the PONR entry we need the info of the index
    // region's daughter entries.
    public static final ThreadLocal<SplitInfo> splitThreadLocal = new ThreadLocal<SplitInfo>() {
        protected SplitInfo initialValue() {
            return null;
        };
    };

    @Override
    public void preRollBack(ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException {
        RegionCoprocessorEnvironment environment = ctx.getEnvironment();
        HRegionServer rs = (HRegionServer) environment.getRegionServerServices();
        HRegion region = environment.getRegion();
        String userTableName = region.getTableDesc().getNameAsString();
        if (IndexUtils.isIndexTable(userTableName)) {
            return;
        }
        LOG.trace("Entering preRollBack for the table " + userTableName + " for the region "
                + region.getRegionInfo());
        SplitInfo splitInfo = splitThreadLocal.get();
        SplitTransaction splitTransaction = splitInfo.getSplitTransaction();
        try {
            if (splitTransaction != null) {
                splitTransaction.rollback(rs, rs);
                LOG.info("preRollBack successfully done for the table " + userTableName + " for the region "
                        + region.getRegionInfo());
            }
        } catch (Exception e) {
            LOG.error("Error while rolling back the split failure for index region " + splitTransaction.getParent(),
                    e);
            rs.abort("Abort; we got an error during rollback of index");
        }
    }

    // For testing to check whether final step of seek point is added
    public static void setSeekpointAdded(boolean isSeekpointAddded) {
        IndexRegionObserver.isSeekpointAddded = isSeekpointAddded;
    }

    // For testing
    public static boolean getSeekpointAdded() {
        return isSeekpointAddded;
    }

    // For testing to ensure indexed flow is used or not
    public static void setIndexedFlowUsed(boolean isIndexedFlowUsed) {
        IndexRegionObserver.isIndexedFlowUsed = isIndexedFlowUsed;
    }

    // For testing
    public static boolean getIndexedFlowUsed() {
        return isIndexedFlowUsed;
    }

    // For testing
    static List<byte[]> getSeekpoints() {
        return seekPoints;
    }

    // For testing to ensure cache size is returned correctly
    public static void setSeekPoints(List<byte[]> seekPoints) {
        IndexRegionObserver.seekPoints = seekPoints;
    }

    public static void setIsTestingEnabled(boolean isTestingEnabled) {
        IndexRegionObserver.isTestingEnabled = isTestingEnabled;
    }

    public static void addSeekPoints(List<byte[]> seekPoints) {
        if (seekPoints == null) {
            IndexRegionObserver.seekPointsForMultipleIndices = null;
            return;
        }
        if (IndexRegionObserver.seekPointsForMultipleIndices == null) {
            IndexRegionObserver.seekPointsForMultipleIndices = new ArrayList<byte[]>();
        }
        IndexRegionObserver.seekPointsForMultipleIndices.addAll(seekPoints);
    }

    public static List<byte[]> getMultipleSeekPoints() {
        return IndexRegionObserver.seekPointsForMultipleIndices;
    }

    private static class SeekAndReadRegionScannerHolder {
        private static ThreadLocal<SeekAndReadRegionScanner> holder = new ThreadLocal<SeekAndReadRegionScanner>();

        public static void setRegionScanner(SeekAndReadRegionScanner scanner) {
            holder.set(scanner);
        }

        public static SeekAndReadRegionScanner getRegionScanner() {
            return holder.get();
        }

        public static void removeRegionScanner() {
            holder.remove();
        }
    }

    @Override
    public InternalScanner preCompactScannerOpen(ObserverContext<RegionCoprocessorEnvironment> c, Store store,
            List<? extends KeyValueScanner> scanners, ScanType scanType, long earliestPutTs, InternalScanner s)
            throws IOException {
        HRegionServer rs = (HRegionServer) c.getEnvironment().getRegionServerServices();
        if (!store.getTableName().contains(Constants.INDEX_TABLE_SUFFIX)) {
            // Not an index table
            return null;
        }
        long smallestReadPoint = c.getEnvironment().getRegion().getSmallestReadPoint();
        String actualTableName = IndexUtils.getActualTableNameFromIndexTableName(store.getTableName());
        TTLStoreScanner ttlStoreScanner = new TTLStoreScanner(store, smallestReadPoint, earliestPutTs, scanType,
                scanners, new TTLExpiryChecker(), actualTableName, rs);
        return ttlStoreScanner;
    }

    @Override
    public void postClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested) {
        HRegion region = e.getEnvironment().getRegion();
        byte[] tableName = region.getRegionInfo().getTableName();
        if (IndexUtils.isCatalogTable(tableName) || IndexUtils.isIndexTable(tableName)) {
            return;
        }
        if (splitThreadLocal.get() == null) {
            this.indexManager.decrementRegionCount(Bytes.toString(tableName), true);
        } else {
            this.indexManager.decrementRegionCount(Bytes.toString(tableName), false);
        }
    }

    private boolean isValidIndexMutation(HTableDescriptor userTableDesc, String tableName) {
        if (IndexUtils.isCatalogTable(userTableDesc.getName()) || IndexUtils.isIndexTable(tableName)) {
            return false;
        }
        List<IndexSpecification> indices = indexManager.getIndicesForTable(tableName);
        if (indices == null || indices.isEmpty()) {
            LOG.trace("skipping preBatchMutate for the table " + tableName + " as there are no indices");
            return false;
        }
        return true;
    }

    private void acquireLockOnIndexRegion(String tableName, HRegion userRegion, HRegionServer rs)
            throws IOException {
        HRegion indexRegion = getIndexTableRegion(tableName, userRegion, rs);
        indexRegion.checkResources();
        indexRegion.startRegionOperation();
    }

    @Override
    public void postCloseRegionOperation(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
        HRegionServer rs = (HRegionServer) e.getEnvironment().getRegionServerServices();
        HRegion userRegion = e.getEnvironment().getRegion();
        HTableDescriptor userTableDesc = userRegion.getTableDesc();
        String tableName = userTableDesc.getNameAsString();
        if (!isValidIndexMutation(userTableDesc, tableName)) {
            // Ideally need not release any lock because in the preStartRegionOperationHook we would not
            // have
            // acquired
            // any lock on the index region
            return;
        }
        HRegion indexRegion = getIndexTableRegion(tableName, userRegion, rs);
        // This check for isClosed and isClosing is needed because we should not unlock
        // when the index region lock would have already been released before throwing NSRE

        // TODO : What is the scenario that i may get an IllegalMonitorStateException
        if (!indexRegion.isClosed() || !indexRegion.isClosing()) {
            indexRegion.closeRegionOperation();
        }
    }

    @Override
    public void postStartRegionOperation(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
        HRegionServer rs = (HRegionServer) e.getEnvironment().getRegionServerServices();
        HRegion userRegion = e.getEnvironment().getRegion();
        HTableDescriptor userTableDesc = userRegion.getTableDesc();
        String tableName = userTableDesc.getNameAsString();
        if (!isValidIndexMutation(userTableDesc, tableName)) {
            return;
        }
        acquireLockOnIndexRegion(tableName, userRegion, rs);

    }

}