/**
* 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);
}
}