blob: 07b412f38ffd7fa9c8dbd48dad0a3b3869f1740d [file] [log] [blame]
/*
* 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.phoenix.schema.stats;
import static org.apache.phoenix.util.SchemaUtil.getVarCharLength;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.coprocessor.MetaDataProtocol;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.types.PLong;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.MetaDataUtil;
import org.apache.phoenix.util.SchemaUtil;
import com.google.common.collect.Sets;
/**
* Simple utility class for managing multiple key parts of the statistic
*/
public class StatisticsUtil {
private static final Set<TableName> DISABLE_STATS = Sets.newHashSetWithExpectedSize(8);
// TODO: make this declarative through new DISABLE_STATS column on SYSTEM.CATALOG table.
// Also useful would be a USE_CURRENT_TIME_FOR_STATS column on SYSTEM.CATALOG table.
static {
DISABLE_STATS.add(TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME));
DISABLE_STATS.add(TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_FUNCTION_NAME));
DISABLE_STATS.add(TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_NAME));
DISABLE_STATS.add(TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_STATS_NAME));
DISABLE_STATS.add(SchemaUtil.getPhysicalTableName(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME_BYTES,true));
DISABLE_STATS.add(SchemaUtil.getPhysicalTableName(PhoenixDatabaseMetaData.SYSTEM_FUNCTION_NAME_BYTES,true));
DISABLE_STATS.add(SchemaUtil.getPhysicalTableName(PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_NAME_BYTES,true));
DISABLE_STATS.add(SchemaUtil.getPhysicalTableName(PhoenixDatabaseMetaData.SYSTEM_STATS_NAME_BYTES,true));
}
private StatisticsUtil() {
// private ctor for utility classes
}
/** Number of parts in our complex key */
protected static final int NUM_KEY_PARTS = 3;
public static byte[] getRowKey(byte[] table, ImmutableBytesWritable fam, byte[] guidePostStartKey) {
return getRowKey(table, fam, new ImmutableBytesWritable(guidePostStartKey,0,guidePostStartKey.length));
}
public static byte[] getRowKey(byte[] table, ImmutableBytesWritable fam, ImmutableBytesWritable guidePostStartKey) {
// always starts with the source table
int guidePostLength = guidePostStartKey.getLength();
boolean hasGuidePost = guidePostLength > 0;
byte[] rowKey = new byte[table.length + fam.getLength() + guidePostLength + (hasGuidePost ? 2 : 1)];
int offset = 0;
System.arraycopy(table, 0, rowKey, offset, table.length);
offset += table.length;
rowKey[offset++] = QueryConstants.SEPARATOR_BYTE; // assumes stats table columns not DESC
System.arraycopy(fam.get(), fam.getOffset(), rowKey, offset, fam.getLength());
if (hasGuidePost) {
offset += fam.getLength();
rowKey[offset++] = QueryConstants.SEPARATOR_BYTE; // assumes stats table columns not DESC
System.arraycopy(guidePostStartKey.get(), 0, rowKey, offset, guidePostLength);
}
return rowKey;
}
private static byte[] getStartKey(byte[] table, ImmutableBytesWritable fam) {
return getKey(table, fam, false);
}
private static byte[] getEndKey(byte[] table, ImmutableBytesWritable fam) {
byte[] key = getKey(table, fam, true);
ByteUtil.nextKey(key, key.length);
return key;
}
private static byte[] getKey(byte[] table, ImmutableBytesWritable fam, boolean terminateWithSeparator) {
// always starts with the source table and column family
byte[] rowKey = new byte[table.length + fam.getLength() + 1 + (terminateWithSeparator ? 1 : 0)];
int offset = 0;
System.arraycopy(table, 0, rowKey, offset, table.length);
offset += table.length;
rowKey[offset++] = QueryConstants.SEPARATOR_BYTE; // assumes stats table columns not DESC
System.arraycopy(fam.get(), fam.getOffset(), rowKey, offset, fam.getLength());
offset += fam.getLength();
if (terminateWithSeparator) {
rowKey[offset] = QueryConstants.SEPARATOR_BYTE;
}
return rowKey;
}
private static byte[] getAdjustedKey(byte[] key, byte[] tableNameBytes, ImmutableBytesWritable cf, boolean nextKey) {
if (Bytes.compareTo(key, ByteUtil.EMPTY_BYTE_ARRAY) != 0) {
return getRowKey(tableNameBytes, cf, key);
}
key = getKey(tableNameBytes, cf, nextKey);
if (nextKey) {
ByteUtil.nextKey(key, key.length);
}
return key;
}
public static List<Result> readStatistics(HTableInterface statsHTable, byte[] tableNameBytes, ImmutableBytesPtr cf,
byte[] startKey, byte[] stopKey, long clientTimeStamp) throws IOException {
List<Result> statsForRegion = new ArrayList<Result>();
Scan s = MetaDataUtil.newTableRowsScan(
getAdjustedKey(startKey, tableNameBytes, cf, false),
getAdjustedKey(stopKey, tableNameBytes, cf, true),
MetaDataProtocol.MIN_TABLE_TIMESTAMP,
clientTimeStamp);
s.addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, QueryConstants.EMPTY_COLUMN_BYTES);
ResultScanner scanner = null;
try {
scanner = statsHTable.getScanner(s);
Result result = null;
while ((result = scanner.next()) != null) {
statsForRegion.add(result);
}
} finally {
if (scanner != null) {
scanner.close();
}
}
return statsForRegion;
}
public static GuidePostsInfo readStatistics(HTableInterface statsHTable, GuidePostsKey key, long clientTimeStamp)
throws IOException {
ImmutableBytesWritable ptr = new ImmutableBytesWritable();
ptr.set(key.getColumnFamily());
byte[] tableNameBytes = key.getPhysicalName();
byte[] startKey = getStartKey(tableNameBytes, ptr);
byte[] endKey = getEndKey(tableNameBytes, ptr);
Scan s = MetaDataUtil.newTableRowsScan(startKey, endKey, MetaDataProtocol.MIN_TABLE_TIMESTAMP, clientTimeStamp);
s.addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH_BYTES);
s.addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, PhoenixDatabaseMetaData.GUIDE_POSTS_ROW_COUNT_BYTES);
s.addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, QueryConstants.EMPTY_COLUMN_BYTES);
GuidePostsInfoBuilder guidePostsInfoWriter = new GuidePostsInfoBuilder();
Cell current = null;
try (ResultScanner scanner = statsHTable.getScanner(s)) {
Result result = null;
while ((result = scanner.next()) != null) {
CellScanner cellScanner = result.cellScanner();
long rowCount = 0;
long byteCount = 0;
while (cellScanner.advance()) {
current = cellScanner.current();
if (Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
current.getQualifierLength(), PhoenixDatabaseMetaData.GUIDE_POSTS_ROW_COUNT_BYTES, 0,
PhoenixDatabaseMetaData.GUIDE_POSTS_ROW_COUNT_BYTES.length)) {
rowCount = PLong.INSTANCE.getCodec().decodeLong(current.getValueArray(),
current.getValueOffset(), SortOrder.getDefault());
} else if (Bytes.equals(current.getQualifierArray(), current.getQualifierOffset(),
current.getQualifierLength(), PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH_BYTES, 0,
PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH_BYTES.length)) {
byteCount = PLong.INSTANCE.getCodec().decodeLong(current.getValueArray(),
current.getValueOffset(), SortOrder.getDefault());
}
}
if (current != null) {
int tableNameLength = tableNameBytes.length + 1;
int cfOffset = current.getRowOffset() + tableNameLength;
int cfLength = getVarCharLength(current.getRowArray(), cfOffset,
current.getRowLength() - tableNameLength);
ptr.set(current.getRowArray(), cfOffset, cfLength);
byte[] cfName = ByteUtil.copyKeyBytesIfNecessary(ptr);
byte[] newGPStartKey = getGuidePostsInfoFromRowKey(tableNameBytes, cfName, result.getRow());
guidePostsInfoWriter.addGuidePosts(newGPStartKey, byteCount, rowCount);
}
}
}
// We write a row with an empty KeyValue in the case that stats were generated but without enough data
// for any guideposts. If we have no rows, it means stats were never generated.
return current == null ? GuidePostsInfo.NO_GUIDEPOST : guidePostsInfoWriter.isEmpty() ? GuidePostsInfo.EMPTY_GUIDEPOST : guidePostsInfoWriter.build();
}
private static SortedMap<byte[], GuidePostsInfo> getGuidePostsPerCf(
TreeMap<byte[], GuidePostsInfoBuilder> guidePostsWriterPerCf) {
TreeMap<byte[], GuidePostsInfo> guidePostsPerCf = new TreeMap<byte[], GuidePostsInfo>(Bytes.BYTES_COMPARATOR);
for (byte[] key : guidePostsWriterPerCf.keySet()) {
guidePostsPerCf.put(key, guidePostsWriterPerCf.get(key).build());
}
return guidePostsPerCf;
}
public static long getGuidePostDepth(int guidepostPerRegion, long guidepostWidth, HTableDescriptor tableDesc) {
if (guidepostPerRegion > 0) {
long maxFileSize = HConstants.DEFAULT_MAX_FILE_SIZE;
if (tableDesc != null) {
long tableMaxFileSize = tableDesc.getMaxFileSize();
if (tableMaxFileSize >= 0) {
maxFileSize = tableMaxFileSize;
}
}
return maxFileSize / guidepostPerRegion;
} else {
return guidepostWidth;
}
}
public static byte[] getGuidePostsInfoFromRowKey(byte[] tableNameBytes, byte[] fam, byte[] row) {
if (row.length > tableNameBytes.length + 1 + fam.length) {
ImmutableBytesWritable ptr = new ImmutableBytesWritable();
int gpOffset = tableNameBytes.length + 1 + fam.length + 1;
ptr.set(row, gpOffset, row.length - gpOffset);
return ByteUtil.copyKeyBytesIfNecessary(ptr);
}
return ByteUtil.EMPTY_BYTE_ARRAY;
}
public static boolean isStatsEnabled(TableName tableName) {
return !DISABLE_STATS.contains(tableName);
}
}