HBASE-2541 Remove transactional contrib
git-svn-id: https://svn.apache.org/repos/asf/hadoop/hbase/trunk@944058 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
48143c8f96
commit
590c27c2c9
|
@ -20,6 +20,7 @@ Release 0.21.0 - Unreleased
|
||||||
HBASE-2392 Upgrade to ZooKeeper 3.3.0
|
HBASE-2392 Upgrade to ZooKeeper 3.3.0
|
||||||
HBASE-2294 Enumerate ACID properties of HBase in a well defined spec
|
HBASE-2294 Enumerate ACID properties of HBase in a well defined spec
|
||||||
(Todd Lipcon via Stack)
|
(Todd Lipcon via Stack)
|
||||||
|
HBASE-2541 Remove transactional contrib (Clint Morgan via Stack)
|
||||||
|
|
||||||
BUG FIXES
|
BUG FIXES
|
||||||
HBASE-1791 Timeout in IndexRecordWriter (Bradford Stephens via Andrew
|
HBASE-1791 Timeout in IndexRecordWriter (Bradford Stephens via Andrew
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
This contrib contains transactional hbase (THBase) and indexed table hbase (ITHBase).
|
|
||||||
For how to use, include hbase-X.X.X-transactional.jar in your CLASSPATH and follow
|
|
||||||
the instruction in javadoc under the respective packages: org.apache.hadoop.hbase.client.transactional
|
|
||||||
and org.apache.hadoop.hbase.client.tableindexed.
|
|
|
@ -1,54 +0,0 @@
|
||||||
# Copyright 2009 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.
|
|
||||||
|
|
||||||
# TableIndexed.rb
|
|
||||||
# Extends HBase shell with operations on IndexedTables.
|
|
||||||
|
|
||||||
# Usage: within the HBase shell, load 'TableIndexed.rb'. Transactional
|
|
||||||
# jar must be in the classpath.
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.client.tableindexed.IndexedTableAdmin
|
|
||||||
import org.apache.hadoop.hbase.client.tableindexed.IndexSpecification
|
|
||||||
|
|
||||||
# Creates an index using the supplied index specification.
|
|
||||||
# [table_name] the name of the table to index.
|
|
||||||
# [index_spec] the IndexSpecification describing the index wanted.
|
|
||||||
def create_index(table_name, index_spec)
|
|
||||||
@iadmin ||= IndexedTableAdmin.new(@configuration)
|
|
||||||
@iadmin.addIndex(table_name.to_java_bytes, index_spec)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates an index for a field guaranteed to have unique values. If
|
|
||||||
# application code does not ensure uniqueness, behavior is undefined.
|
|
||||||
# [table_name] the name of the table to index.
|
|
||||||
# [index_name] the name of the index.
|
|
||||||
# [column] the column name to be indexed, must respond_to to_java_bytes.
|
|
||||||
def create_unique_index(table_name, index_name, column)
|
|
||||||
spec = IndexSpecification.for_unique_index(index_name, column.to_java_bytes)
|
|
||||||
create_index(table_name, spec)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates an index using the standard simple index key. Supports one
|
|
||||||
# to many mappings from indexed values to rows in the primary table.
|
|
||||||
# [table_name] the name of the table to index.
|
|
||||||
# [index_name] the name of the index.
|
|
||||||
# [column] the column name to be indexed, must respond_to to_java_bytes.
|
|
||||||
def create_simple_index(table_name, index_name, column)
|
|
||||||
spec = new IndexSpecification(index_name, column.to_java_bytes)
|
|
||||||
create_index(table_name, spec)
|
|
||||||
end
|
|
|
@ -1,64 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>hbase-contrib-transactional</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
<name>HBase Contrib - Transactional</name>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.apache.hbase</groupId>
|
|
||||||
<artifactId>hbase-contrib</artifactId>
|
|
||||||
<version>0.21.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<forkMode>always</forkMode>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-logging</groupId>
|
|
||||||
<artifactId>commons-logging</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
It is unfortunately neccessary to keep hbase-core before hadoop-core.
|
|
||||||
For an explanation see the pom.xml from hbase-core.
|
|
||||||
-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>${project.groupId}</groupId>
|
|
||||||
<artifactId>hbase-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.hadoop</groupId>
|
|
||||||
<artifactId>hadoop-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Test dependencies -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>${project.groupId}</groupId>
|
|
||||||
<artifactId>hbase-core</artifactId>
|
|
||||||
<classifier>tests</classifier>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.hadoop</groupId>
|
|
||||||
<artifactId>hadoop-test</artifactId>
|
|
||||||
<version>${hadoop.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
|
@ -1,38 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.hadoop.io.Writable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for generating an index-row-key from a row in the base table.
|
|
||||||
*/
|
|
||||||
public interface IndexKeyGenerator extends Writable {
|
|
||||||
|
|
||||||
/** Create an index key from a base row.
|
|
||||||
*
|
|
||||||
* @param rowKey the row key of the base row
|
|
||||||
* @param columns the columns in the base row
|
|
||||||
* @return the row key in the indexed row.
|
|
||||||
*/
|
|
||||||
byte[] createIndexKey(byte[] rowKey, Map<byte[], byte[]> columns);
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when asking for an index that does not exist.
|
|
||||||
*/
|
|
||||||
public class IndexNotFoundException extends IOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 6533971528557000965L;
|
|
||||||
|
|
||||||
public IndexNotFoundException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexNotFoundException(String arg0) {
|
|
||||||
super(arg0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexNotFoundException(Throwable arg0) {
|
|
||||||
super(arg0.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexNotFoundException(String arg0, Throwable arg1) {
|
|
||||||
super(arg0+arg1.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
import org.apache.hadoop.io.ObjectWritable;
|
|
||||||
import org.apache.hadoop.io.Writable;
|
|
||||||
import org.apache.hadoop.io.WritableComparable;
|
|
||||||
|
|
||||||
/** Holds the specification for a single secondary index. */
|
|
||||||
public class IndexSpecification implements Writable {
|
|
||||||
|
|
||||||
// Columns that are indexed (part of the indexRowKey)
|
|
||||||
private byte[][] indexedColumns;
|
|
||||||
|
|
||||||
// Constructs the
|
|
||||||
private IndexKeyGenerator keyGenerator;
|
|
||||||
|
|
||||||
// Additional columns mapped into the indexed row. These will be available for
|
|
||||||
// filters when scanning the index.
|
|
||||||
private byte[][] additionalColumns;
|
|
||||||
|
|
||||||
private byte[][] allColumns;
|
|
||||||
|
|
||||||
// Id of this index, unique within a table.
|
|
||||||
private String indexId;
|
|
||||||
|
|
||||||
/** Construct an "simple" index spec for a single column.
|
|
||||||
* @param indexId
|
|
||||||
* @param indexedColumn
|
|
||||||
*/
|
|
||||||
public IndexSpecification(String indexId, byte[] indexedColumn) {
|
|
||||||
this(indexId, new byte[][] { indexedColumn }, null,
|
|
||||||
new SimpleIndexKeyGenerator(indexedColumn));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Construct an index spec for a single column that has only unique values.
|
|
||||||
* @param indexId the name of the index
|
|
||||||
* @param indexedColumn the column to index
|
|
||||||
* @return the IndexSpecification
|
|
||||||
*/
|
|
||||||
public static IndexSpecification forUniqueIndex(String indexId, byte[] indexedColumn) {
|
|
||||||
return new IndexSpecification(indexId, new byte[][] { indexedColumn },
|
|
||||||
null, new UniqueIndexKeyGenerator(indexedColumn));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an index spec by specifying everything.
|
|
||||||
*
|
|
||||||
* @param indexId
|
|
||||||
* @param indexedColumns
|
|
||||||
* @param additionalColumns
|
|
||||||
* @param keyGenerator
|
|
||||||
*/
|
|
||||||
public IndexSpecification(String indexId, byte[][] indexedColumns,
|
|
||||||
byte[][] additionalColumns, IndexKeyGenerator keyGenerator) {
|
|
||||||
this.indexId = indexId;
|
|
||||||
this.indexedColumns = indexedColumns;
|
|
||||||
this.keyGenerator = keyGenerator;
|
|
||||||
this.additionalColumns = (additionalColumns == null)? new byte[0][0] :
|
|
||||||
additionalColumns;
|
|
||||||
this.makeAllColumns();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexSpecification() {
|
|
||||||
// For writable
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeAllColumns() {
|
|
||||||
this.allColumns = new byte[indexedColumns.length
|
|
||||||
+ (additionalColumns == null ? 0 : additionalColumns.length)][];
|
|
||||||
System.arraycopy(indexedColumns, 0, allColumns, 0, indexedColumns.length);
|
|
||||||
if (additionalColumns != null) {
|
|
||||||
System.arraycopy(additionalColumns, 0, allColumns, indexedColumns.length,
|
|
||||||
additionalColumns.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the indexedColumns.
|
|
||||||
*
|
|
||||||
* @return Return the indexedColumns.
|
|
||||||
*/
|
|
||||||
public byte[][] getIndexedColumns() {
|
|
||||||
return indexedColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the keyGenerator.
|
|
||||||
*
|
|
||||||
* @return Return the keyGenerator.
|
|
||||||
*/
|
|
||||||
public IndexKeyGenerator getKeyGenerator() {
|
|
||||||
return keyGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the additionalColumns.
|
|
||||||
*
|
|
||||||
* @return Return the additionalColumns.
|
|
||||||
*/
|
|
||||||
public byte[][] getAdditionalColumns() {
|
|
||||||
return additionalColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the indexId.
|
|
||||||
*
|
|
||||||
* @return Return the indexId.
|
|
||||||
*/
|
|
||||||
public String getIndexId() {
|
|
||||||
return indexId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[][] getAllColumns() {
|
|
||||||
return allColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsColumn(byte[] column) {
|
|
||||||
for (byte[] col : allColumns) {
|
|
||||||
if (Bytes.equals(column, col)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getIndexedTableName(byte[] baseTableName) {
|
|
||||||
return Bytes.add(baseTableName, Bytes.toBytes("-" + indexId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final HBaseConfiguration CONF = new HBaseConfiguration();
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void readFields(DataInput in) throws IOException {
|
|
||||||
indexId = in.readUTF();
|
|
||||||
int numIndexedCols = in.readInt();
|
|
||||||
indexedColumns = new byte[numIndexedCols][];
|
|
||||||
for (int i = 0; i < numIndexedCols; i++) {
|
|
||||||
indexedColumns[i] = Bytes.readByteArray(in);
|
|
||||||
}
|
|
||||||
int numAdditionalCols = in.readInt();
|
|
||||||
additionalColumns = new byte[numAdditionalCols][];
|
|
||||||
for (int i = 0; i < numAdditionalCols; i++) {
|
|
||||||
additionalColumns[i] = Bytes.readByteArray(in);
|
|
||||||
}
|
|
||||||
makeAllColumns();
|
|
||||||
keyGenerator = (IndexKeyGenerator) ObjectWritable.readObject(in, CONF);
|
|
||||||
|
|
||||||
// FIXME this is to read the deprecated comparator, in existing data
|
|
||||||
ObjectWritable.readObject(in, CONF);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void write(DataOutput out) throws IOException {
|
|
||||||
out.writeUTF(indexId);
|
|
||||||
out.writeInt(indexedColumns.length);
|
|
||||||
for (byte[] col : indexedColumns) {
|
|
||||||
Bytes.writeByteArray(out, col);
|
|
||||||
}
|
|
||||||
if (additionalColumns != null) {
|
|
||||||
out.writeInt(additionalColumns.length);
|
|
||||||
for (byte[] col : additionalColumns) {
|
|
||||||
Bytes.writeByteArray(out, col);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.writeInt(0);
|
|
||||||
}
|
|
||||||
ObjectWritable
|
|
||||||
.writeObject(out, keyGenerator, IndexKeyGenerator.class, CONF);
|
|
||||||
|
|
||||||
// FIXME need to maintain this for exisitng data
|
|
||||||
ObjectWritable.writeObject(out, null, WritableComparable.class,
|
|
||||||
CONF);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("ID => ");
|
|
||||||
sb.append(indexId);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.io.Writable;
|
|
||||||
/** Holds an array of index specifications.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class IndexSpecificationArray implements Writable {
|
|
||||||
|
|
||||||
private IndexSpecification [] indexSpecifications;
|
|
||||||
|
|
||||||
public IndexSpecificationArray() {
|
|
||||||
// FOr writable
|
|
||||||
}
|
|
||||||
public IndexSpecificationArray(IndexSpecification[] specs) {
|
|
||||||
this.indexSpecifications = specs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void readFields(DataInput in) throws IOException {
|
|
||||||
int size = in.readInt();
|
|
||||||
indexSpecifications = new IndexSpecification[size];
|
|
||||||
for (int i=0; i<size; i++) {
|
|
||||||
indexSpecifications[i] = new IndexSpecification();
|
|
||||||
indexSpecifications[i].readFields(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(DataOutput out) throws IOException {
|
|
||||||
out.writeInt(indexSpecifications.length);
|
|
||||||
for (IndexSpecification indexSpec : indexSpecifications) {
|
|
||||||
indexSpec.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Get indexSpecifications.
|
|
||||||
* @return indexSpecifications
|
|
||||||
*/
|
|
||||||
public IndexSpecification[] getIndexSpecifications() {
|
|
||||||
return indexSpecifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,277 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
|
||||||
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.client.transactional.TransactionalTable;
|
|
||||||
import org.apache.hadoop.hbase.filter.Filter;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/** HTable extended with indexed support. */
|
|
||||||
public class IndexedTable extends TransactionalTable {
|
|
||||||
|
|
||||||
// TODO move these schema constants elsewhere
|
|
||||||
public static final byte[] INDEX_COL_FAMILY_NAME = Bytes.toBytes("__INDEX__");
|
|
||||||
public static final byte[] INDEX_COL_FAMILY = Bytes.add(
|
|
||||||
INDEX_COL_FAMILY_NAME, KeyValue.COLUMN_FAMILY_DELIM_ARRAY);
|
|
||||||
public static final byte[] INDEX_BASE_ROW = Bytes.toBytes("ROW");
|
|
||||||
public static final byte[] INDEX_BASE_ROW_COLUMN = Bytes.add(
|
|
||||||
INDEX_COL_FAMILY, INDEX_BASE_ROW);
|
|
||||||
|
|
||||||
static final Log LOG = LogFactory.getLog(IndexedTable.class);
|
|
||||||
|
|
||||||
private final IndexedTableDescriptor indexedTableDescriptor;
|
|
||||||
private Map<String, HTable> indexIdToTable = new HashMap<String, HTable>();
|
|
||||||
|
|
||||||
public IndexedTable(final HBaseConfiguration conf, final byte[] tableName)
|
|
||||||
throws IOException {
|
|
||||||
super(conf, tableName);
|
|
||||||
this.indexedTableDescriptor = new IndexedTableDescriptor(super.getTableDescriptor());
|
|
||||||
for (IndexSpecification spec : this.indexedTableDescriptor.getIndexes()) {
|
|
||||||
indexIdToTable.put(spec.getIndexId(), new HTable(conf, spec
|
|
||||||
.getIndexedTableName(tableName)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexedTableDescriptor getIndexedTableDescriptor() {
|
|
||||||
return this.indexedTableDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open up an indexed scanner. Results will come back in the indexed order,
|
|
||||||
* but will contain RowResults from the original table.
|
|
||||||
*
|
|
||||||
* @param indexId the id of the index to use
|
|
||||||
* @param indexStartRow (created from the IndexKeyGenerator)
|
|
||||||
* @param indexStopRow (created from the IndexKeyGenerator)
|
|
||||||
* @param indexColumns in the index table
|
|
||||||
* @param indexFilter filter to run on the index'ed table. This can only use
|
|
||||||
* columns that have been added to the index.
|
|
||||||
* @param baseColumns from the original table
|
|
||||||
* @return scanner
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IndexNotFoundException
|
|
||||||
*/
|
|
||||||
public ResultScanner getIndexedScanner(String indexId, final byte[] indexStartRow, final byte[] indexStopRow,
|
|
||||||
byte[][] indexColumns, final Filter indexFilter,
|
|
||||||
final byte[][] baseColumns) throws IOException, IndexNotFoundException {
|
|
||||||
IndexSpecification indexSpec = this.indexedTableDescriptor.getIndex(indexId);
|
|
||||||
if (indexSpec == null) {
|
|
||||||
throw new IndexNotFoundException("Index " + indexId
|
|
||||||
+ " not defined in table "
|
|
||||||
+ super.getTableDescriptor().getNameAsString());
|
|
||||||
}
|
|
||||||
verifyIndexColumns(indexColumns, indexSpec);
|
|
||||||
// TODO, verify/remove index columns from baseColumns
|
|
||||||
|
|
||||||
HTable indexTable = indexIdToTable.get(indexId);
|
|
||||||
|
|
||||||
byte[][] allIndexColumns;
|
|
||||||
if (indexColumns != null) {
|
|
||||||
allIndexColumns = new byte[indexColumns.length + 1][];
|
|
||||||
System
|
|
||||||
.arraycopy(indexColumns, 0, allIndexColumns, 0, indexColumns.length);
|
|
||||||
allIndexColumns[indexColumns.length] = INDEX_BASE_ROW_COLUMN;
|
|
||||||
} else {
|
|
||||||
byte[][] allColumns = indexSpec.getAllColumns();
|
|
||||||
allIndexColumns = new byte[allColumns.length + 1][];
|
|
||||||
System.arraycopy(allColumns, 0, allIndexColumns, 0, allColumns.length);
|
|
||||||
allIndexColumns[allColumns.length] = INDEX_BASE_ROW_COLUMN;
|
|
||||||
}
|
|
||||||
|
|
||||||
Scan indexScan = new Scan();
|
|
||||||
indexScan.setFilter(indexFilter);
|
|
||||||
for(byte [] column : allIndexColumns) {
|
|
||||||
byte [][] famQf = KeyValue.parseColumn(column);
|
|
||||||
if(famQf.length == 1) {
|
|
||||||
indexScan.addFamily(famQf[0]);
|
|
||||||
} else {
|
|
||||||
indexScan.addColumn(famQf[0], famQf[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (indexStartRow != null) {
|
|
||||||
indexScan.setStartRow(indexStartRow);
|
|
||||||
}
|
|
||||||
if (indexStopRow != null) {
|
|
||||||
indexScan.setStopRow(indexStopRow);
|
|
||||||
}
|
|
||||||
ResultScanner indexScanner = indexTable.getScanner(indexScan);
|
|
||||||
|
|
||||||
return new ScannerWrapper(indexScanner, baseColumns);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyIndexColumns(byte[][] requestedColumns,
|
|
||||||
IndexSpecification indexSpec) {
|
|
||||||
if (requestedColumns == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (byte[] requestedColumn : requestedColumns) {
|
|
||||||
boolean found = false;
|
|
||||||
for (byte[] indexColumn : indexSpec.getAllColumns()) {
|
|
||||||
if (Bytes.equals(requestedColumn, indexColumn)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
throw new RuntimeException("Column [" + Bytes.toString(requestedColumn)
|
|
||||||
+ "] not in index " + indexSpec.getIndexId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ScannerWrapper implements ResultScanner {
|
|
||||||
|
|
||||||
private ResultScanner indexScanner;
|
|
||||||
private byte[][] columns;
|
|
||||||
|
|
||||||
public ScannerWrapper(ResultScanner indexScanner, byte[][] columns) {
|
|
||||||
this.indexScanner = indexScanner;
|
|
||||||
this.columns = columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public Result next() throws IOException {
|
|
||||||
Result[] result = next(1);
|
|
||||||
if (result == null || result.length < 1)
|
|
||||||
return null;
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public Result[] next(int nbRows) throws IOException {
|
|
||||||
Result[] indexResult = indexScanner.next(nbRows);
|
|
||||||
if (indexResult == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Result[] result = new Result[indexResult.length];
|
|
||||||
for (int i = 0; i < indexResult.length; i++) {
|
|
||||||
Result row = indexResult[i];
|
|
||||||
|
|
||||||
byte[] baseRow = row.getValue(INDEX_COL_FAMILY_NAME, INDEX_BASE_ROW);
|
|
||||||
if (baseRow == null) {
|
|
||||||
throw new IllegalStateException("Missing base row for indexed row: ["+Bytes.toString(row.getRow())+"]");
|
|
||||||
}
|
|
||||||
LOG.debug("next index row [" + Bytes.toString(row.getRow())
|
|
||||||
+ "] -> base row [" + Bytes.toString(baseRow) + "]");
|
|
||||||
Result baseResult = null;
|
|
||||||
if (columns != null && columns.length > 0) {
|
|
||||||
LOG.debug("Going to base table for remaining columns");
|
|
||||||
Get baseGet = new Get(baseRow);
|
|
||||||
for(byte [] column : columns) {
|
|
||||||
byte [][] famQf = KeyValue.parseColumn(column);
|
|
||||||
if(famQf.length == 1) {
|
|
||||||
baseGet.addFamily(famQf[0]);
|
|
||||||
} else {
|
|
||||||
baseGet.addColumn(famQf[0], famQf[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
baseResult = IndexedTable.this.get(baseGet);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<KeyValue> results = new ArrayList<KeyValue>();
|
|
||||||
for (KeyValue indexKV : row.list()) {
|
|
||||||
if (indexKV.matchingFamily(INDEX_COL_FAMILY_NAME)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.add(new KeyValue(baseRow, indexKV.getFamily(),
|
|
||||||
indexKV.getQualifier(), indexKV.getTimestamp(), KeyValue.Type.Put,
|
|
||||||
indexKV.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseResult != null) {
|
|
||||||
List<KeyValue> list = baseResult.list();
|
|
||||||
if (list != null) {
|
|
||||||
results.addAll(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result[i] = new Result(results);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void close() {
|
|
||||||
indexScanner.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from HTable.ClientScanner.iterator()
|
|
||||||
public Iterator<Result> iterator() {
|
|
||||||
return new Iterator<Result>() {
|
|
||||||
// The next RowResult, possibly pre-read
|
|
||||||
Result next = null;
|
|
||||||
|
|
||||||
// return true if there is another item pending, false if there isn't.
|
|
||||||
// this method is where the actual advancing takes place, but you need
|
|
||||||
// to call next() to consume it. hasNext() will only advance if there
|
|
||||||
// isn't a pending next().
|
|
||||||
public boolean hasNext() {
|
|
||||||
if (next == null) {
|
|
||||||
try {
|
|
||||||
next = ScannerWrapper.this.next();
|
|
||||||
return next != null;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the pending next item and advance the iterator. returns null if
|
|
||||||
// there is no next item.
|
|
||||||
public Result next() {
|
|
||||||
// since hasNext() does the real advancing, we call this to determine
|
|
||||||
// if there is a next before proceeding.
|
|
||||||
if (!hasNext()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we get to here, then hasNext() has given us an item to return.
|
|
||||||
// we want to return the item and then null out the next pointer, so
|
|
||||||
// we use a temporary variable.
|
|
||||||
Result temp = next;
|
|
||||||
next = null;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.ColumnNameParseException;
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.MasterNotRunningException;
|
|
||||||
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
|
||||||
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.regionserver.tableindexed.IndexMaintenanceUtils;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension of HBaseAdmin that creates indexed tables.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class IndexedTableAdmin extends HBaseAdmin {
|
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(IndexedTableAdmin.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @param conf Configuration object
|
|
||||||
* @throws MasterNotRunningException
|
|
||||||
*/
|
|
||||||
public IndexedTableAdmin(HBaseConfiguration conf)
|
|
||||||
throws MasterNotRunningException {
|
|
||||||
super(conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new indexed table
|
|
||||||
*
|
|
||||||
* @param desc table descriptor for table
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void createIndexedTable(IndexedTableDescriptor desc) throws IOException {
|
|
||||||
super.createTable(desc.getBaseTableDescriptor());
|
|
||||||
this.createIndexTables(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createIndexTables(IndexedTableDescriptor indexDesc) throws IOException {
|
|
||||||
byte[] baseTableName = indexDesc.getBaseTableDescriptor().getName();
|
|
||||||
for (IndexSpecification indexSpec : indexDesc.getIndexes()) {
|
|
||||||
HTableDescriptor indexTableDesc = createIndexTableDesc(baseTableName,
|
|
||||||
indexSpec);
|
|
||||||
super.createTable(indexTableDesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HTableDescriptor createIndexTableDesc(byte[] baseTableName,
|
|
||||||
IndexSpecification indexSpec) throws ColumnNameParseException {
|
|
||||||
HTableDescriptor indexTableDesc = new HTableDescriptor(indexSpec
|
|
||||||
.getIndexedTableName(baseTableName));
|
|
||||||
Set<byte[]> families = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
|
|
||||||
families.add(IndexedTable.INDEX_COL_FAMILY_NAME);
|
|
||||||
for (byte[] column : indexSpec.getAllColumns()) {
|
|
||||||
families.add(KeyValue.parseColumn(column)[0]);
|
|
||||||
}
|
|
||||||
for (byte[] colFamily : families) {
|
|
||||||
indexTableDesc.addFamily(new HColumnDescriptor(colFamily));
|
|
||||||
}
|
|
||||||
return indexTableDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remove an index for a table.
|
|
||||||
* @throws IOException
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public void removeIndex(byte[] baseTableName, String indexId) throws IOException {
|
|
||||||
super.disableTable(baseTableName);
|
|
||||||
HTableDescriptor desc = super.getTableDescriptor(baseTableName);
|
|
||||||
IndexedTableDescriptor indexDesc = new IndexedTableDescriptor(desc);
|
|
||||||
IndexSpecification spec = indexDesc.getIndex(indexId);
|
|
||||||
indexDesc.removeIndex(indexId);
|
|
||||||
this.disableTable(spec.getIndexedTableName(baseTableName));
|
|
||||||
this.deleteTable(spec.getIndexedTableName(baseTableName));
|
|
||||||
super.modifyTable(baseTableName, desc);
|
|
||||||
super.enableTable(baseTableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add an index to a table. */
|
|
||||||
public void addIndex(byte []baseTableName, IndexSpecification indexSpec) throws IOException {
|
|
||||||
LOG.warn("Adding index to existing table ["+Bytes.toString(baseTableName)+"], this may take a long time");
|
|
||||||
// TODO, make table read-only
|
|
||||||
LOG.warn("Not putting table in readonly, if its being written to, the index may get out of sync");
|
|
||||||
HTableDescriptor indexTableDesc = createIndexTableDesc(baseTableName, indexSpec);
|
|
||||||
super.createTable(indexTableDesc);
|
|
||||||
super.disableTable(baseTableName);
|
|
||||||
IndexedTableDescriptor indexDesc = new IndexedTableDescriptor(super.getTableDescriptor(baseTableName));
|
|
||||||
indexDesc.addIndex(indexSpec);
|
|
||||||
super.modifyTable(baseTableName, indexDesc.getBaseTableDescriptor());
|
|
||||||
super.enableTable(baseTableName);
|
|
||||||
reIndexTable(baseTableName, indexSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reIndexTable(byte[] baseTableName, IndexSpecification indexSpec) throws IOException {
|
|
||||||
HTable baseTable = new HTable(baseTableName);
|
|
||||||
HTable indexTable = new HTable(indexSpec.getIndexedTableName(baseTableName));
|
|
||||||
Scan baseScan = new Scan();
|
|
||||||
for(byte [] column : indexSpec.getAllColumns()) {
|
|
||||||
byte [][] famQf = KeyValue.parseColumn(column);
|
|
||||||
if(famQf.length == 1) {
|
|
||||||
baseScan.addFamily(famQf[0]);
|
|
||||||
} else {
|
|
||||||
baseScan.addColumn(famQf[0], famQf[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Result result : baseTable.getScanner(baseScan)) {
|
|
||||||
SortedMap<byte[], byte[]> columnValues = new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
|
|
||||||
for(KeyValue kv : result.sorted()) {
|
|
||||||
columnValues.put(Bytes.add(kv.getFamily(), KeyValue.COLUMN_FAMILY_DELIM_ARRAY,
|
|
||||||
kv.getQualifier()), kv.getValue());
|
|
||||||
}
|
|
||||||
if (IndexMaintenanceUtils.doesApplyToIndex(indexSpec, columnValues)) {
|
|
||||||
Put indexUpdate = IndexMaintenanceUtils.createIndexUpdate(indexSpec, result.getRow(), columnValues);
|
|
||||||
indexTable.put(indexUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
import org.apache.hadoop.io.Writable;
|
|
||||||
|
|
||||||
public class IndexedTableDescriptor {
|
|
||||||
|
|
||||||
private static final byte[] INDEXES_KEY = Bytes.toBytes("INDEXES");
|
|
||||||
|
|
||||||
private final HTableDescriptor baseTableDescriptor;
|
|
||||||
// Key is indexId
|
|
||||||
private final Map<String, IndexSpecification> indexes = new HashMap<String, IndexSpecification>();
|
|
||||||
|
|
||||||
public IndexedTableDescriptor(HTableDescriptor baseTableDescriptor)
|
|
||||||
throws IOException {
|
|
||||||
this.baseTableDescriptor = baseTableDescriptor;
|
|
||||||
readFromTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HTableDescriptor getBaseTableDescriptor() {
|
|
||||||
return this.baseTableDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readFromTable() throws IOException {
|
|
||||||
byte [] bytes = baseTableDescriptor.getValue(INDEXES_KEY);
|
|
||||||
if (bytes == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
|
||||||
DataInputStream dis = new DataInputStream(bais);
|
|
||||||
IndexSpecificationArray indexArray = new IndexSpecificationArray();
|
|
||||||
indexArray.readFields(dis);
|
|
||||||
for (Writable index : indexArray.getIndexSpecifications()) {
|
|
||||||
IndexSpecification indexSpec = (IndexSpecification) index;
|
|
||||||
indexes.put(indexSpec.getIndexId(), indexSpec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeToTable() {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(baos);
|
|
||||||
IndexSpecificationArray indexArray = new IndexSpecificationArray(indexes.values().toArray(new IndexSpecification[0]));
|
|
||||||
|
|
||||||
try {
|
|
||||||
indexArray.write(dos);
|
|
||||||
dos.flush();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
baseTableDescriptor.setValue(INDEXES_KEY, baos.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<IndexSpecification> getIndexes() {
|
|
||||||
return indexes.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexSpecification getIndex(String indexId) {
|
|
||||||
return indexes.get(indexId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addIndex(IndexSpecification index) {
|
|
||||||
indexes.put(index.getIndexId(), index);
|
|
||||||
writeToTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeIndex(String indexId) {
|
|
||||||
indexes.remove(indexId);
|
|
||||||
writeToTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder s = new StringBuilder(baseTableDescriptor.toString());
|
|
||||||
|
|
||||||
if (!indexes.isEmpty()) {
|
|
||||||
s.append(", ");
|
|
||||||
s.append("INDEXES");
|
|
||||||
s.append(" => ");
|
|
||||||
s.append(indexes.values());
|
|
||||||
}
|
|
||||||
return s.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**Creates indexed keys for a single column. Index key consists of the column
|
|
||||||
* value followed by the row key of the indexed table to disambiguate.
|
|
||||||
*
|
|
||||||
* If the column values are guaranteed to be unique, consider
|
|
||||||
* {@link UniqueIndexKeyGenerator}.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SimpleIndexKeyGenerator implements IndexKeyGenerator {
|
|
||||||
|
|
||||||
private byte [] column;
|
|
||||||
|
|
||||||
public SimpleIndexKeyGenerator(byte [] column) {
|
|
||||||
this.column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleIndexKeyGenerator() {
|
|
||||||
// For Writable
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public byte[] createIndexKey(byte[] rowKey, Map<byte[], byte[]> columns) {
|
|
||||||
return Bytes.add(columns.get(column), rowKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void readFields(DataInput in) throws IOException {
|
|
||||||
column = Bytes.readByteArray(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void write(DataOutput out) throws IOException {
|
|
||||||
Bytes.writeByteArray(out, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates index row keys which exactly match the indexed column. This allows a
|
|
||||||
* direct get() lookup on the index table, but at the cost that the column
|
|
||||||
* values must be unique.
|
|
||||||
*
|
|
||||||
* If you are indexing a column which can have duplicated values, consider
|
|
||||||
* {@link SimpleIndexKeyGenerator}.
|
|
||||||
*/
|
|
||||||
public class UniqueIndexKeyGenerator implements IndexKeyGenerator {
|
|
||||||
private byte[] column;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param column the column to index
|
|
||||||
*/
|
|
||||||
public UniqueIndexKeyGenerator(byte[] column) {
|
|
||||||
this.column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UniqueIndexKeyGenerator() {
|
|
||||||
// For Writable
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public byte[] createIndexKey(byte[] rowKey, Map<byte[], byte[]> columns) {
|
|
||||||
return columns.get(column).clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void readFields(DataInput in) throws IOException {
|
|
||||||
column = Bytes.readByteArray(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public void write(DataOutput out) throws IOException {
|
|
||||||
Bytes.writeByteArray(out, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
/** Thrown when a transaction cannot be committed.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class CommitUnsuccessfulException extends Exception {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 7062921444531109202L;
|
|
||||||
|
|
||||||
/** Default Constructor */
|
|
||||||
public CommitUnsuccessfulException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param arg0 message
|
|
||||||
* @param arg1 cause
|
|
||||||
*/
|
|
||||||
public CommitUnsuccessfulException(String arg0, Throwable arg1) {
|
|
||||||
super(arg0, arg1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param arg0 message
|
|
||||||
*/
|
|
||||||
public CommitUnsuccessfulException(String arg0) {
|
|
||||||
super(arg0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param arg0 cause
|
|
||||||
*/
|
|
||||||
public CommitUnsuccessfulException(Throwable arg0) {
|
|
||||||
super(arg0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
public class HBaseBackedTransactionLogger implements TransactionLogger {
|
|
||||||
|
|
||||||
/** The name of the transaction status table. */
|
|
||||||
public static final String TABLE_NAME = "__GLOBAL_TRX_LOG__";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Column which holds the transaction status.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private static final byte [] STATUS_FAMILY = Bytes.toBytes("Info");
|
|
||||||
private static final byte [] STATUS_QUALIFIER = Bytes.toBytes("Status");
|
|
||||||
/**
|
|
||||||
* Create the table.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static void createTable() throws IOException {
|
|
||||||
HTableDescriptor tableDesc = new HTableDescriptor(TABLE_NAME);
|
|
||||||
tableDesc.addFamily(new HColumnDescriptor(STATUS_FAMILY));
|
|
||||||
HBaseAdmin admin = new HBaseAdmin(new HBaseConfiguration());
|
|
||||||
admin.createTable(tableDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Random random = new Random();
|
|
||||||
private HTable table;
|
|
||||||
|
|
||||||
public HBaseBackedTransactionLogger() throws IOException {
|
|
||||||
initTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initTable() throws IOException {
|
|
||||||
HBaseAdmin admin = new HBaseAdmin(new HBaseConfiguration());
|
|
||||||
|
|
||||||
if (!admin.tableExists(TABLE_NAME)) {
|
|
||||||
throw new RuntimeException("Table not created. Call createTable() first");
|
|
||||||
}
|
|
||||||
this.table = new HTable(TABLE_NAME);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public long createNewTransactionLog() {
|
|
||||||
long id;
|
|
||||||
TransactionStatus existing;
|
|
||||||
|
|
||||||
do {
|
|
||||||
id = random.nextLong();
|
|
||||||
existing = getStatusForTransaction(id);
|
|
||||||
} while (existing != null);
|
|
||||||
|
|
||||||
setStatusForTransaction(id, TransactionStatus.PENDING);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransactionStatus getStatusForTransaction(long transactionId) {
|
|
||||||
try {
|
|
||||||
Result result = table.get(new Get(getRow(transactionId)));
|
|
||||||
if (result == null || result.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte [] statusValue = result.getValue(STATUS_FAMILY, STATUS_QUALIFIER);
|
|
||||||
if (statusValue == null) {
|
|
||||||
throw new RuntimeException("No status cell for row " + transactionId);
|
|
||||||
}
|
|
||||||
String statusString = Bytes.toString(statusValue);
|
|
||||||
return TransactionStatus.valueOf(statusString);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte [] getRow(long transactionId) {
|
|
||||||
return Bytes.toBytes(""+transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusForTransaction(long transactionId,
|
|
||||||
TransactionStatus status) {
|
|
||||||
Put put = new Put(getRow(transactionId));
|
|
||||||
put.add(STATUS_FAMILY, STATUS_QUALIFIER, Bytes.toBytes(status.name()));
|
|
||||||
try {
|
|
||||||
table.put(put);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forgetTransaction(long transactionId) {
|
|
||||||
Delete delete = new Delete(getRow(transactionId));
|
|
||||||
delete.deleteColumns(STATUS_FAMILY, STATUS_QUALIFIER);
|
|
||||||
try {
|
|
||||||
table.delete(delete);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.transaction.xa.XAException;
|
|
||||||
import javax.transaction.xa.XAResource;
|
|
||||||
import javax.transaction.xa.Xid;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View hbase as a JTA transactional resource. This allows it to participate in
|
|
||||||
* transactions across multiple resources.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class JtaXAResource implements XAResource {
|
|
||||||
|
|
||||||
static final Log LOG = LogFactory.getLog(JtaXAResource.class);
|
|
||||||
|
|
||||||
private Map<Xid, TransactionState> xidToTransactionState = new HashMap<Xid, TransactionState>();
|
|
||||||
private final TransactionManager transactionManager;
|
|
||||||
private ThreadLocal<TransactionState> threadLocalTransactionState = new ThreadLocal<TransactionState>();
|
|
||||||
|
|
||||||
public JtaXAResource(TransactionManager transactionManager) {
|
|
||||||
this.transactionManager = transactionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void commit(Xid xid, boolean onePhase) throws XAException {
|
|
||||||
LOG.trace("commit [" + xid.toString() + "] "
|
|
||||||
+ (onePhase ? "one phase" : "two phase"));
|
|
||||||
TransactionState state = xidToTransactionState.remove(xid);
|
|
||||||
if (state == null) {
|
|
||||||
throw new XAException(XAException.XAER_NOTA);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (onePhase) {
|
|
||||||
transactionManager.tryCommit(state);
|
|
||||||
} else {
|
|
||||||
transactionManager.doCommit(state);
|
|
||||||
}
|
|
||||||
} catch (CommitUnsuccessfulException e) {
|
|
||||||
throw new XAException(XAException.XA_RBROLLBACK);
|
|
||||||
} catch (IOException e) {
|
|
||||||
XAException xae = new XAException(XAException.XAER_RMERR);
|
|
||||||
xae.initCause(e);
|
|
||||||
throw xae;
|
|
||||||
} finally {
|
|
||||||
threadLocalTransactionState.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void end(Xid xid, int flags) throws XAException {
|
|
||||||
LOG.trace("end [" + xid.toString() + "] ");
|
|
||||||
threadLocalTransactionState.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forget(Xid xid) throws XAException {
|
|
||||||
LOG.trace("forget [" + xid.toString() + "] ");
|
|
||||||
threadLocalTransactionState.remove();
|
|
||||||
TransactionState state = xidToTransactionState.remove(xid);
|
|
||||||
if (state != null) {
|
|
||||||
try {
|
|
||||||
transactionManager.abort(state);
|
|
||||||
} catch (IOException e) {
|
|
||||||
XAException xae = new XAException(XAException.XAER_RMERR);
|
|
||||||
xae.initCause(e);
|
|
||||||
throw xae;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTransactionTimeout() throws XAException {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSameRM(XAResource xares) throws XAException {
|
|
||||||
if (xares instanceof JtaXAResource) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int prepare(Xid xid) throws XAException {
|
|
||||||
LOG.trace("prepare [" + xid.toString() + "] ");
|
|
||||||
TransactionState state = xidToTransactionState.get(xid);
|
|
||||||
int status;
|
|
||||||
try {
|
|
||||||
status = this.transactionManager.prepareCommit(state);
|
|
||||||
} catch (CommitUnsuccessfulException e) {
|
|
||||||
XAException xae = new XAException(XAException.XA_HEURRB);
|
|
||||||
xae.initCause(e);
|
|
||||||
throw xae;
|
|
||||||
} catch (IOException e) {
|
|
||||||
XAException xae = new XAException(XAException.XAER_RMERR);
|
|
||||||
xae.initCause(e);
|
|
||||||
throw xae;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case TransactionalRegionInterface.COMMIT_OK:
|
|
||||||
return XAResource.XA_OK;
|
|
||||||
case TransactionalRegionInterface.COMMIT_OK_READ_ONLY:
|
|
||||||
return XAResource.XA_RDONLY;
|
|
||||||
default:
|
|
||||||
throw new XAException(XAException.XA_RBPROTO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Xid[] recover(int flag) throws XAException {
|
|
||||||
return xidToTransactionState.keySet().toArray(new Xid[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rollback(Xid xid) throws XAException {
|
|
||||||
LOG.trace("rollback [" + xid.toString() + "] ");
|
|
||||||
forget(xid);
|
|
||||||
threadLocalTransactionState.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setTransactionTimeout(int seconds) throws XAException {
|
|
||||||
return false; // Currently not supported. (Only global lease time)
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(Xid xid, int flags) throws XAException {
|
|
||||||
LOG.trace("start [" + xid.toString() + "] ");
|
|
||||||
// TODO, check flags
|
|
||||||
TransactionState state = this.transactionManager.beginTransaction();
|
|
||||||
threadLocalTransactionState.set(state);
|
|
||||||
xidToTransactionState.put(xid, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the threadLocalTransaction state.
|
|
||||||
*/
|
|
||||||
public TransactionState getThreadLocalTransactionState() {
|
|
||||||
return threadLocalTransactionState.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A local, in-memory implementation of the transaction logger. Does not provide a global view, so
|
|
||||||
* it can't be relighed on by
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class LocalTransactionLogger implements TransactionLogger {
|
|
||||||
|
|
||||||
private static LocalTransactionLogger instance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates singleton if it does not exist
|
|
||||||
*
|
|
||||||
* @return reference to singleton
|
|
||||||
*/
|
|
||||||
public synchronized static LocalTransactionLogger getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new LocalTransactionLogger();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Random random = new Random();
|
|
||||||
private Map<Long, TransactionStatus> transactionIdToStatusMap = Collections
|
|
||||||
.synchronizedMap(new HashMap<Long, TransactionStatus>());
|
|
||||||
|
|
||||||
private LocalTransactionLogger() {
|
|
||||||
// Enforce singlton
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return random longs to minimize possibility of collision */
|
|
||||||
public long createNewTransactionLog() {
|
|
||||||
long id;
|
|
||||||
do {
|
|
||||||
id = random.nextLong();
|
|
||||||
} while (transactionIdToStatusMap.containsKey(id));
|
|
||||||
transactionIdToStatusMap.put(id, TransactionStatus.PENDING);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransactionStatus getStatusForTransaction(final long transactionId) {
|
|
||||||
return transactionIdToStatusMap.get(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusForTransaction(final long transactionId,
|
|
||||||
final TransactionStatus status) {
|
|
||||||
transactionIdToStatusMap.put(transactionId, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forgetTransaction(long transactionId) {
|
|
||||||
transactionIdToStatusMap.remove(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple interface used to provide a log about transaction status. Written to
|
|
||||||
* by the client, and read by regionservers in case of failure.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface TransactionLogger {
|
|
||||||
|
|
||||||
/** Transaction status values */
|
|
||||||
enum TransactionStatus {
|
|
||||||
/** Transaction is pending */
|
|
||||||
PENDING,
|
|
||||||
/** Transaction was committed */
|
|
||||||
COMMITTED,
|
|
||||||
/** Transaction was aborted */
|
|
||||||
ABORTED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new transaction log. Return the transaction's globally unique id.
|
|
||||||
* Log's initial value should be PENDING
|
|
||||||
*
|
|
||||||
* @return transaction id
|
|
||||||
*/
|
|
||||||
long createNewTransactionLog();
|
|
||||||
|
|
||||||
/** Get the status of a transaction.
|
|
||||||
* @param transactionId
|
|
||||||
* @return transaction status
|
|
||||||
*/
|
|
||||||
TransactionStatus getStatusForTransaction(long transactionId);
|
|
||||||
|
|
||||||
/** Set the status for a transaction.
|
|
||||||
* @param transactionId
|
|
||||||
* @param status
|
|
||||||
*/
|
|
||||||
void setStatusForTransaction(long transactionId, TransactionStatus status);
|
|
||||||
|
|
||||||
/** This transaction's state is no longer needed.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
*/
|
|
||||||
void forgetTransaction(long transactionId);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.HRegionLocation;
|
|
||||||
import org.apache.hadoop.hbase.NotServingRegionException;
|
|
||||||
import org.apache.hadoop.hbase.client.HConnection;
|
|
||||||
import org.apache.hadoop.hbase.client.HConnectionManager;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transaction Manager. Responsible for committing transactions.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TransactionManager {
|
|
||||||
static final Log LOG = LogFactory.getLog(TransactionManager.class);
|
|
||||||
|
|
||||||
private final HConnection connection;
|
|
||||||
private final TransactionLogger transactionLogger;
|
|
||||||
private JtaXAResource xAResource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param conf
|
|
||||||
*/
|
|
||||||
public TransactionManager(final HBaseConfiguration conf) {
|
|
||||||
this(LocalTransactionLogger.getInstance(), conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param transactionLogger
|
|
||||||
* @param conf
|
|
||||||
*/
|
|
||||||
public TransactionManager(final TransactionLogger transactionLogger,
|
|
||||||
final HBaseConfiguration conf) {
|
|
||||||
this.transactionLogger = transactionLogger;
|
|
||||||
connection = HConnectionManager.getConnection(conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to start a transaction.
|
|
||||||
*
|
|
||||||
* @return new transaction state
|
|
||||||
*/
|
|
||||||
public TransactionState beginTransaction() {
|
|
||||||
long transactionId = transactionLogger.createNewTransactionLog();
|
|
||||||
LOG.debug("Begining transaction " + transactionId);
|
|
||||||
return new TransactionState(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare to commit a transaction.
|
|
||||||
*
|
|
||||||
* @param transactionState
|
|
||||||
* @return commitStatusCode (see {@link TransactionalRegionInterface})
|
|
||||||
* @throws IOException
|
|
||||||
* @throws CommitUnsuccessfulException
|
|
||||||
*/
|
|
||||||
public int prepareCommit(final TransactionState transactionState)
|
|
||||||
throws CommitUnsuccessfulException, IOException {
|
|
||||||
boolean allReadOnly = true;
|
|
||||||
try {
|
|
||||||
Iterator<HRegionLocation> locationIterator = transactionState.getParticipatingRegions().iterator();
|
|
||||||
while (locationIterator.hasNext()) {
|
|
||||||
HRegionLocation location = locationIterator.next();
|
|
||||||
TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection
|
|
||||||
.getHRegionConnection(location.getServerAddress());
|
|
||||||
int commitStatus = transactionalRegionServer.commitRequest(location
|
|
||||||
.getRegionInfo().getRegionName(), transactionState
|
|
||||||
.getTransactionId());
|
|
||||||
boolean canCommit = true;
|
|
||||||
switch (commitStatus) {
|
|
||||||
case TransactionalRegionInterface.COMMIT_OK:
|
|
||||||
allReadOnly = false;
|
|
||||||
break;
|
|
||||||
case TransactionalRegionInterface.COMMIT_OK_READ_ONLY:
|
|
||||||
locationIterator.remove(); // No need to doCommit for read-onlys
|
|
||||||
break;
|
|
||||||
case TransactionalRegionInterface.COMMIT_UNSUCESSFUL:
|
|
||||||
canCommit = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new CommitUnsuccessfulException(
|
|
||||||
"Unexpected return code from prepareCommit: " + commitStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isTraceEnabled()) {
|
|
||||||
LOG.trace("Region ["
|
|
||||||
+ location.getRegionInfo().getRegionNameAsString() + "] votes "
|
|
||||||
+ (canCommit ? "to commit" : "to abort") + " transaction "
|
|
||||||
+ transactionState.getTransactionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canCommit) {
|
|
||||||
LOG.debug("Aborting [" + transactionState.getTransactionId() + "]");
|
|
||||||
abort(transactionState, location);
|
|
||||||
throw new CommitUnsuccessfulException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug("Commit of transaction [" + transactionState.getTransactionId()
|
|
||||||
+ "] was unsucsessful", e);
|
|
||||||
// This happens on a NSRE that is triggered by a split
|
|
||||||
try {
|
|
||||||
abort(transactionState);
|
|
||||||
} catch (Exception abortException) {
|
|
||||||
LOG.warn("Exeption durring abort", abortException);
|
|
||||||
}
|
|
||||||
throw new CommitUnsuccessfulException(e);
|
|
||||||
}
|
|
||||||
return allReadOnly ? TransactionalRegionInterface.COMMIT_OK_READ_ONLY : TransactionalRegionInterface.COMMIT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try and commit a transaction. This does both phases of the 2-phase protocol: prepare and commit.
|
|
||||||
*
|
|
||||||
* @param transactionState
|
|
||||||
* @throws IOException
|
|
||||||
* @throws CommitUnsuccessfulException
|
|
||||||
*/
|
|
||||||
public void tryCommit(final TransactionState transactionState)
|
|
||||||
throws CommitUnsuccessfulException, IOException {
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
LOG.debug("atempting to commit trasaction: " + transactionState.toString());
|
|
||||||
int status = prepareCommit(transactionState);
|
|
||||||
|
|
||||||
if (status == TransactionalRegionInterface.COMMIT_OK) {
|
|
||||||
doCommit(transactionState);
|
|
||||||
} else if (status == TransactionalRegionInterface.COMMIT_OK_READ_ONLY) {
|
|
||||||
transactionLogger.forgetTransaction(transactionState.getTransactionId());
|
|
||||||
}
|
|
||||||
LOG.debug("Committed transaction ["+transactionState.getTransactionId()+"] in ["+((System.currentTimeMillis()-startTime))+"]ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Do the commit. This is the 2nd phase of the 2-phase protocol.
|
|
||||||
*
|
|
||||||
* @param transactionState
|
|
||||||
* @throws CommitUnsuccessfulException
|
|
||||||
*/
|
|
||||||
public void doCommit(final TransactionState transactionState)
|
|
||||||
throws CommitUnsuccessfulException{
|
|
||||||
try {
|
|
||||||
LOG.debug("Commiting [" + transactionState.getTransactionId() + "]");
|
|
||||||
|
|
||||||
transactionLogger.setStatusForTransaction(transactionState
|
|
||||||
.getTransactionId(), TransactionLogger.TransactionStatus.COMMITTED);
|
|
||||||
|
|
||||||
for (HRegionLocation location : transactionState
|
|
||||||
.getParticipatingRegions()) {
|
|
||||||
TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection
|
|
||||||
.getHRegionConnection(location.getServerAddress());
|
|
||||||
transactionalRegionServer.commit(location.getRegionInfo()
|
|
||||||
.getRegionName(), transactionState.getTransactionId());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug("Commit of transaction [" + transactionState.getTransactionId()
|
|
||||||
+ "] was unsucsessful", e);
|
|
||||||
// This happens on a NSRE that is triggered by a split
|
|
||||||
try {
|
|
||||||
abort(transactionState);
|
|
||||||
} catch (Exception abortException) {
|
|
||||||
LOG.warn("Exeption durring abort", abortException);
|
|
||||||
}
|
|
||||||
throw new CommitUnsuccessfulException(e);
|
|
||||||
}
|
|
||||||
transactionLogger.forgetTransaction(transactionState.getTransactionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abort a s transaction.
|
|
||||||
*
|
|
||||||
* @param transactionState
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void abort(final TransactionState transactionState) throws IOException {
|
|
||||||
abort(transactionState, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void abort(final TransactionState transactionState,
|
|
||||||
final HRegionLocation locationToIgnore) throws IOException {
|
|
||||||
transactionLogger.setStatusForTransaction(transactionState
|
|
||||||
.getTransactionId(), TransactionLogger.TransactionStatus.ABORTED);
|
|
||||||
|
|
||||||
for (HRegionLocation location : transactionState.getParticipatingRegions()) {
|
|
||||||
if (locationToIgnore != null && location.equals(locationToIgnore)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection
|
|
||||||
.getHRegionConnection(location.getServerAddress());
|
|
||||||
|
|
||||||
transactionalRegionServer.abort(location.getRegionInfo()
|
|
||||||
.getRegionName(), transactionState.getTransactionId());
|
|
||||||
} catch (UnknownTransactionException e) {
|
|
||||||
LOG
|
|
||||||
.debug("Got unknown transaciton exception durring abort. Transaction: ["
|
|
||||||
+ transactionState.getTransactionId()
|
|
||||||
+ "], region: ["
|
|
||||||
+ location.getRegionInfo().getRegionNameAsString()
|
|
||||||
+ "]. Ignoring.");
|
|
||||||
} catch (NotServingRegionException e) {
|
|
||||||
LOG
|
|
||||||
.debug("Got NSRE durring abort. Transaction: ["
|
|
||||||
+ transactionState.getTransactionId() + "], region: ["
|
|
||||||
+ location.getRegionInfo().getRegionNameAsString()
|
|
||||||
+ "]. Ignoring.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
transactionLogger.forgetTransaction(transactionState.getTransactionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized JtaXAResource getXAResource() {
|
|
||||||
if (xAResource == null){
|
|
||||||
xAResource = new JtaXAResource(this);
|
|
||||||
}
|
|
||||||
return xAResource;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.client.HConnection;
|
|
||||||
import org.apache.hadoop.hbase.client.Scan;
|
|
||||||
import org.apache.hadoop.hbase.client.ScannerCallable;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
|
|
||||||
class TransactionScannerCallable extends ScannerCallable {
|
|
||||||
|
|
||||||
private TransactionState transactionState;
|
|
||||||
|
|
||||||
TransactionScannerCallable(final TransactionState transactionState,
|
|
||||||
final HConnection connection, final byte[] tableName, Scan scan) {
|
|
||||||
super(connection, tableName, scan);
|
|
||||||
this.transactionState = transactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long openScanner() throws IOException {
|
|
||||||
if (transactionState.addRegion(location)) {
|
|
||||||
((TransactionalRegionInterface) server).beginTransaction(transactionState
|
|
||||||
.getTransactionId(), location.getRegionInfo().getRegionName());
|
|
||||||
}
|
|
||||||
return ((TransactionalRegionInterface) server).openScanner(transactionState
|
|
||||||
.getTransactionId(), this.location.getRegionInfo().getRegionName(),
|
|
||||||
getScan());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.HRegionLocation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds client-side transaction information. Client's use them as opaque
|
|
||||||
* objects passed around to transaction operations.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TransactionState {
|
|
||||||
static final Log LOG = LogFactory.getLog(TransactionState.class);
|
|
||||||
|
|
||||||
private final long transactionId;
|
|
||||||
|
|
||||||
private Set<HRegionLocation> participatingRegions = new HashSet<HRegionLocation>();
|
|
||||||
|
|
||||||
TransactionState(final long transactionId) {
|
|
||||||
this.transactionId = transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean addRegion(final HRegionLocation hregion) {
|
|
||||||
boolean added = participatingRegions.add(hregion);
|
|
||||||
|
|
||||||
if (added) {
|
|
||||||
LOG.debug("Adding new hregion ["
|
|
||||||
+ hregion.getRegionInfo().getRegionNameAsString()
|
|
||||||
+ "] to transaction [" + transactionId + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<HRegionLocation> getParticipatingRegions() {
|
|
||||||
return participatingRegions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the transactionId.
|
|
||||||
*
|
|
||||||
* @return Return the transactionId.
|
|
||||||
*/
|
|
||||||
public long getTransactionId() {
|
|
||||||
return transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see java.lang.Object#toString()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "id: " + transactionId + ", particpants: "
|
|
||||||
+ participatingRegions.size();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,190 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HConnection;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
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.client.ScannerCallable;
|
|
||||||
import org.apache.hadoop.hbase.client.ServerCallable;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Table with transactional support.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TransactionalTable extends HTable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param conf
|
|
||||||
* @param tableName
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public TransactionalTable(final HBaseConfiguration conf,
|
|
||||||
final String tableName) throws IOException {
|
|
||||||
this(conf, Bytes.toBytes(tableName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param conf
|
|
||||||
* @param tableName
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public TransactionalTable(final HBaseConfiguration conf,
|
|
||||||
final byte[] tableName) throws IOException {
|
|
||||||
super(conf, tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static abstract class TransactionalServerCallable<T> extends
|
|
||||||
ServerCallable<T> {
|
|
||||||
protected TransactionState transactionState;
|
|
||||||
|
|
||||||
protected TransactionalRegionInterface getTransactionServer() {
|
|
||||||
return (TransactionalRegionInterface) server;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void recordServer() throws IOException {
|
|
||||||
if (transactionState.addRegion(location)) {
|
|
||||||
getTransactionServer().beginTransaction(
|
|
||||||
transactionState.getTransactionId(),
|
|
||||||
location.getRegionInfo().getRegionName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param connection
|
|
||||||
* @param tableName
|
|
||||||
* @param row
|
|
||||||
* @param transactionState
|
|
||||||
*/
|
|
||||||
public TransactionalServerCallable(final HConnection connection,
|
|
||||||
final byte[] tableName, final byte[] row,
|
|
||||||
final TransactionState transactionState) {
|
|
||||||
super(connection, tableName, row);
|
|
||||||
this.transactionState = transactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method for getting data from a row
|
|
||||||
* @param get the Get to fetch
|
|
||||||
* @return the result
|
|
||||||
* @throws IOException
|
|
||||||
* @since 0.20.0
|
|
||||||
*/
|
|
||||||
public Result get(final TransactionState transactionState, final Get get) throws IOException {
|
|
||||||
return super.getConnection().getRegionServerWithRetries(
|
|
||||||
new TransactionalServerCallable<Result>(super.getConnection(), super
|
|
||||||
.getTableName(), get.getRow(), transactionState) {
|
|
||||||
public Result call() throws IOException {
|
|
||||||
recordServer();
|
|
||||||
return getTransactionServer().get(
|
|
||||||
transactionState.getTransactionId(),
|
|
||||||
location.getRegionInfo().getRegionName(), get);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param delete
|
|
||||||
* @throws IOException
|
|
||||||
* @since 0.20.0
|
|
||||||
*/
|
|
||||||
public void delete(final TransactionState transactionState, final Delete delete)
|
|
||||||
throws IOException {
|
|
||||||
super.getConnection().getRegionServerWithRetries(
|
|
||||||
new TransactionalServerCallable<Object>(super.getConnection(), super
|
|
||||||
.getTableName(), delete.getRow(), transactionState) {
|
|
||||||
public Object call() throws IOException {
|
|
||||||
recordServer();
|
|
||||||
getTransactionServer().delete(
|
|
||||||
transactionState.getTransactionId(),
|
|
||||||
location.getRegionInfo().getRegionName(), delete);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit a Put to the table.
|
|
||||||
* <p>
|
|
||||||
* If autoFlush is false, the update is buffered.
|
|
||||||
* @param put
|
|
||||||
* @throws IOException
|
|
||||||
* @since 0.20.0
|
|
||||||
*/
|
|
||||||
public synchronized void put(TransactionState transactionState, final Put put) throws IOException {
|
|
||||||
//super.validatePut(put);
|
|
||||||
super.getConnection().getRegionServerWithRetries(
|
|
||||||
new TransactionalServerCallable<Object>(super.getConnection(), super
|
|
||||||
.getTableName(), put.getRow(), transactionState) {
|
|
||||||
public Object call() throws IOException {
|
|
||||||
recordServer();
|
|
||||||
getTransactionServer().put(
|
|
||||||
transactionState.getTransactionId(),
|
|
||||||
location.getRegionInfo().getRegionName(), put);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultScanner getScanner(final TransactionState transactionState,
|
|
||||||
Scan scan) throws IOException {
|
|
||||||
ClientScanner scanner = new TransactionalClientScanner(transactionState, scan);
|
|
||||||
scanner.initialize();
|
|
||||||
return scanner;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class TransactionalClientScanner extends HTable.ClientScanner {
|
|
||||||
|
|
||||||
private TransactionState transactionState;
|
|
||||||
|
|
||||||
protected TransactionalClientScanner(
|
|
||||||
final TransactionState transactionState, Scan scan) {
|
|
||||||
super(scan);
|
|
||||||
this.transactionState = transactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ScannerCallable getScannerCallable(
|
|
||||||
final byte[] localStartKey, int caching) {
|
|
||||||
TransactionScannerCallable t =
|
|
||||||
new TransactionScannerCallable(transactionState, getConnection(),
|
|
||||||
getTableName(), getScan());
|
|
||||||
t.setCaching(caching);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.DoNotRetryIOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown if a region server is passed an unknown transaction id
|
|
||||||
*/
|
|
||||||
public class UnknownTransactionException extends DoNotRetryIOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 698575374929591099L;
|
|
||||||
|
|
||||||
/** constructor */
|
|
||||||
public UnknownTransactionException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param s message
|
|
||||||
*/
|
|
||||||
public UnknownTransactionException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2008 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.ipc;
|
|
||||||
|
|
||||||
/** Interface for the indexed region server. */
|
|
||||||
public interface IndexedRegionInterface extends TransactionalRegionInterface {
|
|
||||||
// No methods for now...
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.ipc;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
|
||||||
import org.apache.hadoop.hbase.client.Scan;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for transactional region servers.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* NOTE: if you change the interface, you must change the RPC version number in
|
|
||||||
* HBaseRPCProtocolVersion
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface TransactionalRegionInterface extends HRegionInterface {
|
|
||||||
|
|
||||||
/** Status code representing a transaction that can be committed. */
|
|
||||||
int COMMIT_OK = 1;
|
|
||||||
/** Status code representing a read-only transaction that can be committed. */
|
|
||||||
int COMMIT_OK_READ_ONLY = 2;
|
|
||||||
/** Status code representing a transaction that cannot be committed. */
|
|
||||||
int COMMIT_UNSUCESSFUL = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sent to initiate a transaction.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
* @param regionName name of region
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
void beginTransaction(long transactionId, final byte[] regionName)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform a transactional Get operation.
|
|
||||||
* @param regionName name of region to get from
|
|
||||||
* @param get Get operation
|
|
||||||
* @return Result
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public Result get(long transactionId, byte [] regionName, Get get) throws IOException;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transactional put data into the specified region
|
|
||||||
* @param regionName
|
|
||||||
* @param put the data to be put
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void put(long transactionId, final byte [] regionName, final Put put)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put an array of puts into the specified region
|
|
||||||
* @param regionName
|
|
||||||
* @param puts
|
|
||||||
* @return result
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public int put(long transactionId, final byte[] regionName, final Put [] puts)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all the KeyValues that match those found in the Delete object,
|
|
||||||
* if their ts <= to the Delete. In case of a delete with a specific ts it
|
|
||||||
* only deletes that specific KeyValue.
|
|
||||||
* @param regionName
|
|
||||||
* @param delete
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void delete(long transactionId, final byte[] regionName, final Delete delete)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
//
|
|
||||||
// remote scanner interface
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a remote transactional scanner with a RowFilter.
|
|
||||||
*
|
|
||||||
* @param regionName name of region to scan
|
|
||||||
* @param scan configured scan object
|
|
||||||
* @return scannerId scanner identifier used in other calls
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public long openScanner(long transactionId, final byte [] regionName, final Scan scan)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask if we can commit the given transaction.
|
|
||||||
*
|
|
||||||
* @param regionName
|
|
||||||
* @param transactionId
|
|
||||||
* @return status of COMMIT_OK, COMMIT_READ_ONLY, or COMMIT_UNSUSESSFULL
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
int commitRequest(final byte[] regionName, long transactionId)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to commit the given transaction. This is used when there is only one
|
|
||||||
* participating region.
|
|
||||||
*
|
|
||||||
* @param regionName
|
|
||||||
* @param transactionId
|
|
||||||
* @return true if committed
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
boolean commitIfPossible(final byte[] regionName, long transactionId)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit the transaction.
|
|
||||||
*
|
|
||||||
* @param regionName
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
void commit(final byte[] regionName, long transactionId) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abort the transaction.
|
|
||||||
*
|
|
||||||
* @param regionName
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
void abort(final byte[] regionName, long transactionId) throws IOException;
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.tableindexed;
|
|
||||||
|
|
||||||
import java.util.SortedMap;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.ColumnNameParseException;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.tableindexed.IndexSpecification;
|
|
||||||
import org.apache.hadoop.hbase.client.tableindexed.IndexedTable;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton class for index maintence logic.
|
|
||||||
*/
|
|
||||||
public class IndexMaintenanceUtils {
|
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(IndexMaintenanceUtils.class);
|
|
||||||
|
|
||||||
public static Put createIndexUpdate(final IndexSpecification indexSpec, final byte[] row,
|
|
||||||
final SortedMap<byte[], byte[]> columnValues) {
|
|
||||||
byte[] indexRow = indexSpec.getKeyGenerator().createIndexKey(row, columnValues);
|
|
||||||
Put update = new Put(indexRow);
|
|
||||||
|
|
||||||
update.add(IndexedTable.INDEX_COL_FAMILY_NAME, IndexedTable.INDEX_BASE_ROW, row);
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (byte[] col : indexSpec.getIndexedColumns()) {
|
|
||||||
byte[] val = columnValues.get(col);
|
|
||||||
if (val == null) {
|
|
||||||
throw new RuntimeException("Unexpected missing column value. [" + Bytes.toString(col) + "]");
|
|
||||||
}
|
|
||||||
byte [][] colSeparated = KeyValue.parseColumn(col);
|
|
||||||
if(colSeparated.length == 1) {
|
|
||||||
throw new ColumnNameParseException("Expected family:qualifier but only got a family");
|
|
||||||
}
|
|
||||||
update.add(colSeparated[0], colSeparated[1], val);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (byte[] col : indexSpec.getAdditionalColumns()) {
|
|
||||||
byte[] val = columnValues.get(col);
|
|
||||||
if (val != null) {
|
|
||||||
byte [][] colSeparated = KeyValue.parseColumn(col);
|
|
||||||
if(colSeparated.length == 1) {
|
|
||||||
throw new ColumnNameParseException("Expected family:qualifier but only got a family");
|
|
||||||
}
|
|
||||||
update.add(colSeparated[0], colSeparated[1], val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ColumnNameParseException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return update;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask if this update does apply to the index.
|
|
||||||
*
|
|
||||||
* @param indexSpec
|
|
||||||
* @param columnValues
|
|
||||||
* @return true if possibly apply.
|
|
||||||
*/
|
|
||||||
public static boolean doesApplyToIndex(final IndexSpecification indexSpec,
|
|
||||||
final SortedMap<byte[], byte[]> columnValues) {
|
|
||||||
|
|
||||||
for (byte[] neededCol : indexSpec.getIndexedColumns()) {
|
|
||||||
if (!columnValues.containsKey(neededCol)) {
|
|
||||||
LOG.debug("Index [" + indexSpec.getIndexId() + "] can't be updated because ["
|
|
||||||
+ Bytes.toString(neededCol) + "] is missing");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,345 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.tableindexed;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NavigableMap;
|
|
||||||
import java.util.NavigableSet;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.Leases;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
|
||||||
import org.apache.hadoop.hbase.client.tableindexed.IndexSpecification;
|
|
||||||
import org.apache.hadoop.hbase.client.tableindexed.IndexedTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.FlushRequester;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.transactional.TransactionalRegion;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.HLog;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
class IndexedRegion extends TransactionalRegion {
|
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(IndexedRegion.class);
|
|
||||||
|
|
||||||
private final Configuration conf;
|
|
||||||
private final IndexedTableDescriptor indexTableDescriptor;
|
|
||||||
private Map<IndexSpecification, HTable> indexSpecToTable = new HashMap<IndexSpecification, HTable>();
|
|
||||||
|
|
||||||
public IndexedRegion(final Path basedir, final HLog log, final FileSystem fs,
|
|
||||||
final Configuration conf, final HRegionInfo regionInfo,
|
|
||||||
final FlushRequester flushListener, Leases trxLeases) throws IOException {
|
|
||||||
super(basedir, log, fs, conf, regionInfo, flushListener, trxLeases);
|
|
||||||
this.indexTableDescriptor = new IndexedTableDescriptor(regionInfo.getTableDesc());
|
|
||||||
this.conf = conf;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized HTable getIndexTable(IndexSpecification index)
|
|
||||||
throws IOException {
|
|
||||||
HTable indexTable = indexSpecToTable.get(index);
|
|
||||||
if (indexTable == null) {
|
|
||||||
indexTable = new HTable(conf, index.getIndexedTableName(super
|
|
||||||
.getRegionInfo().getTableDesc().getName()));
|
|
||||||
indexSpecToTable.put(index, indexTable);
|
|
||||||
}
|
|
||||||
return indexTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<IndexSpecification> getIndexes() {
|
|
||||||
return indexTableDescriptor.getIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param batchUpdate
|
|
||||||
* @param lockid
|
|
||||||
* @param writeToWAL if true, then we write this update to the log
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void put(Put put, Integer lockId, boolean writeToWAL)
|
|
||||||
throws IOException {
|
|
||||||
updateIndexes(put, lockId); // Do this first because will want to see the old row
|
|
||||||
super.put(put, lockId, writeToWAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateIndexes(Put put, Integer lockId) throws IOException {
|
|
||||||
List<IndexSpecification> indexesToUpdate = new LinkedList<IndexSpecification>();
|
|
||||||
|
|
||||||
// Find the indexes we need to update
|
|
||||||
for (IndexSpecification index : getIndexes()) {
|
|
||||||
if (possiblyAppliesToIndex(index, put)) {
|
|
||||||
indexesToUpdate.add(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexesToUpdate.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigableSet<byte[]> neededColumns = getColumnsForIndexes(indexesToUpdate);
|
|
||||||
NavigableMap<byte[], byte[]> newColumnValues = getColumnsFromPut(put);
|
|
||||||
|
|
||||||
Get oldGet = new Get(put.getRow());
|
|
||||||
for (byte [] neededCol : neededColumns) {
|
|
||||||
byte [][] famQf = KeyValue.parseColumn(neededCol);
|
|
||||||
if(famQf.length == 1) {
|
|
||||||
oldGet.addFamily(famQf[0]);
|
|
||||||
} else {
|
|
||||||
oldGet.addColumn(famQf[0], famQf[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result oldResult = super.get(oldGet, lockId);
|
|
||||||
|
|
||||||
// Add the old values to the new if they are not there
|
|
||||||
if (oldResult != null && oldResult.raw() != null) {
|
|
||||||
for (KeyValue oldKV : oldResult.raw()) {
|
|
||||||
byte [] column = KeyValue.makeColumn(oldKV.getFamily(),
|
|
||||||
oldKV.getQualifier());
|
|
||||||
if (!newColumnValues.containsKey(column)) {
|
|
||||||
newColumnValues.put(column, oldKV.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<IndexSpecification> indexIterator = indexesToUpdate.iterator();
|
|
||||||
while (indexIterator.hasNext()) {
|
|
||||||
IndexSpecification indexSpec = indexIterator.next();
|
|
||||||
if (!IndexMaintenanceUtils.doesApplyToIndex(indexSpec, newColumnValues)) {
|
|
||||||
indexIterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SortedMap<byte[], byte[]> oldColumnValues = convertToValueMap(oldResult);
|
|
||||||
|
|
||||||
for (IndexSpecification indexSpec : indexesToUpdate) {
|
|
||||||
updateIndex(indexSpec, put, newColumnValues, oldColumnValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: This call takes place in an RPC, and requires an RPC. This makes for
|
|
||||||
// a likely deadlock if the number of RPCs we are trying to serve is >= the
|
|
||||||
// number of handler threads.
|
|
||||||
private void updateIndex(IndexSpecification indexSpec, Put put,
|
|
||||||
NavigableMap<byte[], byte[]> newColumnValues,
|
|
||||||
SortedMap<byte[], byte[]> oldColumnValues) throws IOException {
|
|
||||||
Delete indexDelete = makeDeleteToRemoveOldIndexEntry(indexSpec, put.getRow(), oldColumnValues);
|
|
||||||
Put indexPut = makeIndexUpdate(indexSpec, put.getRow(), newColumnValues);
|
|
||||||
|
|
||||||
HTable indexTable = getIndexTable(indexSpec);
|
|
||||||
if (indexDelete != null && !Bytes.equals(indexDelete.getRow(), indexPut.getRow())) {
|
|
||||||
// Only do the delete if the row changed. This way we save the put after delete issues in HBASE-2256
|
|
||||||
LOG.debug("Deleting old index row ["+Bytes.toString(indexDelete.getRow())+"]. New row is ["+Bytes.toString(indexPut.getRow())+"].");
|
|
||||||
indexTable.delete(indexDelete);
|
|
||||||
} else if (indexDelete != null){
|
|
||||||
LOG.debug("Skipping deleting index row ["+Bytes.toString(indexDelete.getRow())+"] because it has not changed.");
|
|
||||||
}
|
|
||||||
indexTable.put(indexPut);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Return the columns needed for the update. */
|
|
||||||
private NavigableSet<byte[]> getColumnsForIndexes(Collection<IndexSpecification> indexes) {
|
|
||||||
NavigableSet<byte[]> neededColumns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
|
|
||||||
for (IndexSpecification indexSpec : indexes) {
|
|
||||||
for (byte[] col : indexSpec.getAllColumns()) {
|
|
||||||
neededColumns.add(col);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return neededColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Delete makeDeleteToRemoveOldIndexEntry(IndexSpecification indexSpec, byte[] row,
|
|
||||||
SortedMap<byte[], byte[]> oldColumnValues) throws IOException {
|
|
||||||
for (byte[] indexedCol : indexSpec.getIndexedColumns()) {
|
|
||||||
if (!oldColumnValues.containsKey(indexedCol)) {
|
|
||||||
LOG.debug("Index [" + indexSpec.getIndexId()
|
|
||||||
+ "] not trying to remove old entry for row ["
|
|
||||||
+ Bytes.toString(row) + "] because col ["
|
|
||||||
+ Bytes.toString(indexedCol) + "] is missing");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] oldIndexRow = indexSpec.getKeyGenerator().createIndexKey(row,
|
|
||||||
oldColumnValues);
|
|
||||||
LOG.debug("Index [" + indexSpec.getIndexId() + "] removing old entry ["
|
|
||||||
+ Bytes.toString(oldIndexRow) + "]");
|
|
||||||
return new Delete(oldIndexRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
private NavigableMap<byte[], byte[]> getColumnsFromPut(Put put) {
|
|
||||||
NavigableMap<byte[], byte[]> columnValues = new TreeMap<byte[], byte[]>(
|
|
||||||
Bytes.BYTES_COMPARATOR);
|
|
||||||
for (List<KeyValue> familyPuts : put.getFamilyMap().values()) {
|
|
||||||
for (KeyValue kv : familyPuts) {
|
|
||||||
byte [] column = KeyValue.makeColumn(kv.getFamily(), kv.getQualifier());
|
|
||||||
columnValues.put(column, kv.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return columnValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Ask if this put *could* apply to the index. It may actually apply if some of the columns needed are missing.
|
|
||||||
*
|
|
||||||
* @param indexSpec
|
|
||||||
* @param put
|
|
||||||
* @return true if possibly apply.
|
|
||||||
*/
|
|
||||||
private boolean possiblyAppliesToIndex(IndexSpecification indexSpec, Put put) {
|
|
||||||
for (List<KeyValue> familyPuts : put.getFamilyMap().values()) {
|
|
||||||
for (KeyValue kv : familyPuts) {
|
|
||||||
byte [] column = KeyValue.makeColumn(kv.getFamily(), kv.getQualifier());
|
|
||||||
if (indexSpec.containsColumn(column)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Put makeIndexUpdate(IndexSpecification indexSpec, byte[] row,
|
|
||||||
SortedMap<byte[], byte[]> columnValues) throws IOException {
|
|
||||||
Put indexUpdate = IndexMaintenanceUtils.createIndexUpdate(indexSpec, row, columnValues);
|
|
||||||
LOG.debug("Index [" + indexSpec.getIndexId() + "] adding new entry ["
|
|
||||||
+ Bytes.toString(indexUpdate.getRow()) + "] for row ["
|
|
||||||
+ Bytes.toString(row) + "]");
|
|
||||||
return indexUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME we can be smarter about this and avoid the base gets and index maintenance in many cases.
|
|
||||||
@Override
|
|
||||||
public void delete(Delete delete, final Integer lockid, boolean writeToWAL)
|
|
||||||
throws IOException {
|
|
||||||
// First look at the current (to be the old) state.
|
|
||||||
SortedMap<byte[], byte[]> oldColumnValues = null;
|
|
||||||
if (!getIndexes().isEmpty()) {
|
|
||||||
// Need all columns
|
|
||||||
NavigableSet<byte[]> neededColumns = getColumnsForIndexes(getIndexes());
|
|
||||||
|
|
||||||
Get get = new Get(delete.getRow());
|
|
||||||
for (byte [] col : neededColumns) {
|
|
||||||
byte [][] famQf = KeyValue.parseColumn(col);
|
|
||||||
if(famQf.length == 1) {
|
|
||||||
get.addFamily(famQf[0]);
|
|
||||||
} else {
|
|
||||||
get.addColumn(famQf[0], famQf[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result oldRow = super.get(get, lockid);
|
|
||||||
oldColumnValues = convertToValueMap(oldRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.delete(delete, lockid, writeToWAL);
|
|
||||||
|
|
||||||
if (!getIndexes().isEmpty()) {
|
|
||||||
Get get = new Get(delete.getRow());
|
|
||||||
|
|
||||||
// Rebuild index if there is still a version visible.
|
|
||||||
Result currentRow = super.get(get, lockid);
|
|
||||||
SortedMap<byte[], byte[]> currentColumnValues = convertToValueMap(currentRow);
|
|
||||||
for (IndexSpecification indexSpec : getIndexes()) {
|
|
||||||
Delete indexDelete = null;
|
|
||||||
if (IndexMaintenanceUtils.doesApplyToIndex(indexSpec, oldColumnValues)) {
|
|
||||||
indexDelete = makeDeleteToRemoveOldIndexEntry(indexSpec, delete
|
|
||||||
.getRow(), oldColumnValues);
|
|
||||||
}
|
|
||||||
Put indexPut = null;
|
|
||||||
if (IndexMaintenanceUtils.doesApplyToIndex(indexSpec,
|
|
||||||
currentColumnValues)) {
|
|
||||||
indexPut = makeIndexUpdate(indexSpec, delete.getRow(),
|
|
||||||
currentColumnValues);
|
|
||||||
}
|
|
||||||
if (indexPut == null && indexDelete == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTable indexTable = getIndexTable(indexSpec);
|
|
||||||
if (indexDelete != null
|
|
||||||
&& (indexPut == null || !Bytes.equals(indexDelete.getRow(),
|
|
||||||
indexPut.getRow()))) {
|
|
||||||
// Only do the delete if the row changed. This way we save the put
|
|
||||||
// after delete issues in HBASE-2256
|
|
||||||
LOG.debug("Deleting old index row ["
|
|
||||||
+ Bytes.toString(indexDelete.getRow()) + "].");
|
|
||||||
indexTable.delete(indexDelete);
|
|
||||||
} else if (indexDelete != null) {
|
|
||||||
LOG.debug("Skipping deleting index row ["
|
|
||||||
+ Bytes.toString(indexDelete.getRow())
|
|
||||||
+ "] because it has not changed.");
|
|
||||||
|
|
||||||
for (byte [] indexCol : indexSpec.getAdditionalColumns()) {
|
|
||||||
byte[][] parsed = KeyValue.parseColumn(indexCol);
|
|
||||||
List<KeyValue> famDeletes = delete.getFamilyMap().get(parsed[0]);
|
|
||||||
if (famDeletes != null) {
|
|
||||||
for (KeyValue kv : famDeletes) {
|
|
||||||
if (Bytes.equals(parsed[0], kv.getFamily()) && Bytes.equals(parsed[1], kv.getQualifier())) {
|
|
||||||
LOG.debug("Need to delete this specific column: "+Bytes.toString(indexCol));
|
|
||||||
Delete columnDelete = new Delete(indexDelete.getRow());
|
|
||||||
columnDelete.deleteColumns(parsed[0],parsed[1]);
|
|
||||||
indexTable.delete(columnDelete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexPut != null) {
|
|
||||||
getIndexTable(indexSpec).put(indexPut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private SortedMap<byte[], byte[]> convertToValueMap(Result result) {
|
|
||||||
SortedMap<byte[], byte[]> currentColumnValues = new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
|
|
||||||
|
|
||||||
if (result == null || result.raw() == null) {
|
|
||||||
return currentColumnValues;
|
|
||||||
}
|
|
||||||
List<KeyValue> list = result.list();
|
|
||||||
if (list != null) {
|
|
||||||
for(KeyValue kv : result.list()) {
|
|
||||||
byte [] column = KeyValue.makeColumn(kv.getFamily(), kv.getQualifier());
|
|
||||||
currentColumnValues.put(column, kv.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentColumnValues;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.tableindexed;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.ipc.HBaseRPCProtocolVersion;
|
|
||||||
import org.apache.hadoop.hbase.ipc.IndexedRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.HRegion;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.transactional.TransactionalRegionServer;
|
|
||||||
import org.apache.hadoop.util.Progressable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RegionServer which maintains secondary indexes.
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
public class IndexedRegionServer extends TransactionalRegionServer implements
|
|
||||||
IndexedRegionInterface {
|
|
||||||
|
|
||||||
public IndexedRegionServer(Configuration conf) throws IOException {
|
|
||||||
super(conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getProtocolVersion(final String protocol, final long clientVersion)
|
|
||||||
throws IOException {
|
|
||||||
if (protocol.equals(IndexedRegionInterface.class.getName())) {
|
|
||||||
return HBaseRPCProtocolVersion.versionID;
|
|
||||||
}
|
|
||||||
return super.getProtocolVersion(protocol, clientVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HRegion instantiateRegion(final HRegionInfo regionInfo)
|
|
||||||
throws IOException {
|
|
||||||
HRegion r = new IndexedRegion(HTableDescriptor.getTableDir(super
|
|
||||||
.getRootDir(), regionInfo.getTableDesc().getName()), super.hlog, super
|
|
||||||
.getFileSystem(), super.conf, regionInfo, super.getFlushRequester(), super.getTransactionalLeases());
|
|
||||||
r.initialize(null, new Progressable() {
|
|
||||||
public void progress() {
|
|
||||||
addProcessingMessage(regionInfo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.Chore;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.HRegion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up committed transactions when they are no longer needed to verify
|
|
||||||
* pending transactions.
|
|
||||||
*/
|
|
||||||
class CleanOldTransactionsChore extends Chore {
|
|
||||||
|
|
||||||
private static final String SLEEP_CONF = "hbase.transaction.clean.sleep";
|
|
||||||
private static final int DEFAULT_SLEEP = 60 * 1000;
|
|
||||||
|
|
||||||
private final TransactionalRegionServer regionServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param regionServer
|
|
||||||
* @param stopRequest
|
|
||||||
*/
|
|
||||||
public CleanOldTransactionsChore(
|
|
||||||
final TransactionalRegionServer regionServer,
|
|
||||||
final AtomicBoolean stopRequest) {
|
|
||||||
super(regionServer.getConfiguration().getInt(SLEEP_CONF, DEFAULT_SLEEP),
|
|
||||||
stopRequest);
|
|
||||||
this.regionServer = regionServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void chore() {
|
|
||||||
for (HRegion region : regionServer.getOnlineRegions()) {
|
|
||||||
((TransactionalRegion) region).removeUnNeededCommitedTransactions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.HLog;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.HLogKey;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.LogRollListener;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add support for transactional operations to the regionserver's
|
|
||||||
* write-ahead-log.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class THLog extends HLog {
|
|
||||||
|
|
||||||
public THLog(FileSystem fs, Path dir, Path oldLogDir, Configuration conf,
|
|
||||||
LogRollListener listener) throws IOException {
|
|
||||||
super(fs, dir, oldLogDir, conf, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HLogKey makeKey(byte[] regionName, byte[] tableName, long seqNum,
|
|
||||||
long now) {
|
|
||||||
return new THLogKey(regionName, tableName, seqNum, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUpdateToLog(HRegionInfo regionInfo, final long transactionId, final Put update)
|
|
||||||
throws IOException {
|
|
||||||
this.append(regionInfo, update, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeDeleteToLog(HRegionInfo regionInfo, final long transactionId, final Delete delete)
|
|
||||||
throws IOException {
|
|
||||||
this.append(regionInfo, delete, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeCommitToLog(HRegionInfo regionInfo, final long transactionId) throws IOException {
|
|
||||||
this.append(regionInfo, System.currentTimeMillis(),
|
|
||||||
THLogKey.TrxOp.COMMIT, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeAbortToLog(HRegionInfo regionInfo, final long transactionId) throws IOException {
|
|
||||||
this.append(regionInfo, System.currentTimeMillis(),
|
|
||||||
THLogKey.TrxOp.ABORT, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a general transaction op to the log. This covers: start, commit, and
|
|
||||||
* abort.
|
|
||||||
*
|
|
||||||
* @param regionInfo
|
|
||||||
* @param now
|
|
||||||
* @param txOp
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void append(HRegionInfo regionInfo, long now, THLogKey.TrxOp txOp,
|
|
||||||
long transactionId) throws IOException {
|
|
||||||
THLogKey key = new THLogKey(regionInfo.getRegionName(),
|
|
||||||
regionInfo.getTableDesc().getName(), -1, now, txOp, transactionId);
|
|
||||||
WALEdit e = new WALEdit();
|
|
||||||
e.add(new KeyValue(new byte [0], 0, 0)); // Empty KeyValue
|
|
||||||
super.append(regionInfo, key, e, regionInfo.isMetaRegion());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a transactional update to the log.
|
|
||||||
*
|
|
||||||
* @param regionInfo
|
|
||||||
* @param update
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void append(HRegionInfo regionInfo, Put update, long transactionId)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
long commitTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
THLogKey key = new THLogKey(regionInfo.getRegionName(), regionInfo
|
|
||||||
.getTableDesc().getName(), -1, commitTime, THLogKey.TrxOp.OP,
|
|
||||||
transactionId);
|
|
||||||
|
|
||||||
for (KeyValue value : convertToKeyValues(update)) {
|
|
||||||
WALEdit e = new WALEdit();
|
|
||||||
e.add(value);
|
|
||||||
super.append(regionInfo, key, e, regionInfo.isMetaRegion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a transactional delete to the log.
|
|
||||||
*
|
|
||||||
* @param regionInfo
|
|
||||||
* @param delete
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void append(HRegionInfo regionInfo, Delete delete, long transactionId)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
long commitTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
THLogKey key = new THLogKey(regionInfo.getRegionName(), regionInfo
|
|
||||||
.getTableDesc().getName(), -1, commitTime, THLogKey.TrxOp.OP,
|
|
||||||
transactionId);
|
|
||||||
|
|
||||||
for (KeyValue value : convertToKeyValues(delete)) {
|
|
||||||
WALEdit e = new WALEdit();
|
|
||||||
e.add(value);
|
|
||||||
super.append(regionInfo, key, e, regionInfo.isMetaRegion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<KeyValue> convertToKeyValues(Put update) {
|
|
||||||
List<KeyValue> edits = new ArrayList<KeyValue>();
|
|
||||||
|
|
||||||
for (List<KeyValue> kvs : update.getFamilyMap().values()) {
|
|
||||||
for (KeyValue kv : kvs) {
|
|
||||||
edits.add(kv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return edits;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<KeyValue> convertToKeyValues(Delete delete) {
|
|
||||||
List<KeyValue> edits = new ArrayList<KeyValue>();
|
|
||||||
|
|
||||||
for (List<KeyValue> kvs : delete.getFamilyMap().values()) {
|
|
||||||
for (KeyValue kv : kvs) {
|
|
||||||
edits.add(kv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return edits;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.HLogKey;
|
|
||||||
|
|
||||||
public class THLogKey extends HLogKey {
|
|
||||||
|
|
||||||
/** Type of Transactional op going into the HLot
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public enum TrxOp {
|
|
||||||
/** A standard operation that is transactional. KV holds the op. */
|
|
||||||
OP((byte)2),
|
|
||||||
/** A transaction was committed. */
|
|
||||||
COMMIT((byte)3),
|
|
||||||
/** A transaction was aborted. */
|
|
||||||
ABORT((byte)4);
|
|
||||||
|
|
||||||
private final byte opCode;
|
|
||||||
|
|
||||||
private TrxOp(byte opCode) {
|
|
||||||
this.opCode = opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TrxOp fromByte(byte opCode) {
|
|
||||||
for (TrxOp op : TrxOp.values()) {
|
|
||||||
if (op.opCode == opCode) {
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte transactionOp = -1;
|
|
||||||
private long transactionId = -1;
|
|
||||||
|
|
||||||
public THLogKey() {
|
|
||||||
// For Writable
|
|
||||||
}
|
|
||||||
|
|
||||||
public THLogKey(byte[] regionName, byte[] tablename, long logSeqNum, long now) {
|
|
||||||
super(regionName, tablename, logSeqNum, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
public THLogKey(byte[] regionName, byte[] tablename, long logSeqNum, long now, TrxOp op, long transactionId) {
|
|
||||||
super(regionName, tablename, logSeqNum, now);
|
|
||||||
this.transactionOp = op.opCode;
|
|
||||||
this.transactionId = transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrxOp getTrxOp() {
|
|
||||||
return TrxOp.fromByte(this.transactionOp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTransactionId() {
|
|
||||||
return this.transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(DataOutput out) throws IOException {
|
|
||||||
super.write(out);
|
|
||||||
out.writeByte(transactionOp);
|
|
||||||
out.writeLong(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFields(DataInput in) throws IOException {
|
|
||||||
super.readFields(in);
|
|
||||||
this.transactionOp = in.readByte();
|
|
||||||
this.transactionId = in.readLong();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.fs.FileStatus;
|
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.HLog;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.HBaseBackedTransactionLogger;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.TransactionLogger;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
import org.apache.hadoop.util.Progressable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Responsible recovering transactional information from the HLog.
|
|
||||||
*/
|
|
||||||
class THLogRecoveryManager {
|
|
||||||
private static final Log LOG = LogFactory
|
|
||||||
.getLog(THLogRecoveryManager.class);
|
|
||||||
|
|
||||||
private final FileSystem fileSystem;
|
|
||||||
private final HRegionInfo regionInfo;
|
|
||||||
private final Configuration conf;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param region
|
|
||||||
*/
|
|
||||||
public THLogRecoveryManager(final TransactionalRegion region) {
|
|
||||||
this.fileSystem = region.getFilesystem();
|
|
||||||
this.regionInfo = region.getRegionInfo();
|
|
||||||
this.conf = region.getConf();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Testing
|
|
||||||
THLogRecoveryManager(final FileSystem fileSystem,
|
|
||||||
final HRegionInfo regionInfo, final Configuration conf) {
|
|
||||||
this.fileSystem = fileSystem;
|
|
||||||
this.regionInfo = regionInfo;
|
|
||||||
this.conf = conf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go through the WAL, and look for transactions that were started, but never
|
|
||||||
* completed. If the transaction was committed, then those edits will need to
|
|
||||||
* be applied.
|
|
||||||
*
|
|
||||||
* @param reconstructionLog
|
|
||||||
* @param maxSeqID
|
|
||||||
* @param reporter
|
|
||||||
* @return map of batch updates
|
|
||||||
* @throws UnsupportedEncodingException
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public Map<Long, List<WALEdit>> getCommitsFromLog(
|
|
||||||
final Path reconstructionLog, final long maxSeqID,
|
|
||||||
final Progressable reporter) throws UnsupportedEncodingException,
|
|
||||||
IOException {
|
|
||||||
if (reconstructionLog == null || !fileSystem.exists(reconstructionLog)) {
|
|
||||||
// Nothing to do.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Check its not empty.
|
|
||||||
FileStatus[] stats = fileSystem.listStatus(reconstructionLog);
|
|
||||||
if (stats == null || stats.length == 0) {
|
|
||||||
LOG.warn("Passed reconstruction log " + reconstructionLog
|
|
||||||
+ " is zero-length");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
SortedMap<Long, List<WALEdit>> pendingTransactionsById =
|
|
||||||
new TreeMap<Long, List<WALEdit>>();
|
|
||||||
Set<Long> commitedTransactions = new HashSet<Long>();
|
|
||||||
Set<Long> abortedTransactions = new HashSet<Long>();
|
|
||||||
|
|
||||||
HLog.Reader reader = HLog.getReader(fileSystem, reconstructionLog, conf);
|
|
||||||
try {
|
|
||||||
long skippedEdits = 0;
|
|
||||||
long totalEdits = 0;
|
|
||||||
long startCount = 0;
|
|
||||||
long writeCount = 0;
|
|
||||||
long abortCount = 0;
|
|
||||||
long commitCount = 0;
|
|
||||||
// How many edits to apply before we send a progress report.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int reportInterval = conf.getInt("hbase.hstore.report.interval.edits",
|
|
||||||
2000);
|
|
||||||
|
|
||||||
HLog.Entry entry;
|
|
||||||
while ((entry = reader.next()) != null) {
|
|
||||||
THLogKey key = (THLogKey)entry.getKey();
|
|
||||||
WALEdit val = entry.getEdit();
|
|
||||||
if (LOG.isTraceEnabled()) {
|
|
||||||
LOG.trace("Processing edit: key: " + key.toString() + " val: "
|
|
||||||
+ val.toString());
|
|
||||||
}
|
|
||||||
if (key.getLogSeqNum() < maxSeqID) {
|
|
||||||
skippedEdits++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.getTrxOp() == null || !Bytes.equals(key.getRegionName(), regionInfo.getRegionName())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long transactionId = key.getTransactionId();
|
|
||||||
|
|
||||||
List<WALEdit> updates = pendingTransactionsById.get(transactionId);
|
|
||||||
switch (key.getTrxOp()) {
|
|
||||||
|
|
||||||
case OP:
|
|
||||||
if (updates == null) {
|
|
||||||
updates = new ArrayList<WALEdit>();
|
|
||||||
pendingTransactionsById.put(transactionId, updates);
|
|
||||||
startCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.add(val);
|
|
||||||
val = new WALEdit();
|
|
||||||
writeCount++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ABORT:
|
|
||||||
if (updates == null) {
|
|
||||||
LOG.error("Processing abort for transaction: " + transactionId
|
|
||||||
+ ", but have not seen start message");
|
|
||||||
throw new IOException("Corrupted transaction log");
|
|
||||||
}
|
|
||||||
abortedTransactions.add(transactionId);
|
|
||||||
pendingTransactionsById.remove(transactionId);
|
|
||||||
abortCount++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMIT:
|
|
||||||
if (updates == null) {
|
|
||||||
LOG.error("Processing commit for transaction: " + transactionId
|
|
||||||
+ ", but have not seen start message");
|
|
||||||
throw new IOException("Corrupted transaction log");
|
|
||||||
}
|
|
||||||
if (abortedTransactions.contains(transactionId)) {
|
|
||||||
LOG.error("Processing commit for transaction: " + transactionId
|
|
||||||
+ ", but also have abort message");
|
|
||||||
throw new IOException("Corrupted transaction log");
|
|
||||||
}
|
|
||||||
if (commitedTransactions.contains(transactionId)) {
|
|
||||||
LOG.error("Processing commit for transaction: " + transactionId
|
|
||||||
+ ", but have already commited transaction with that id");
|
|
||||||
throw new IOException("Corrupted transaction log");
|
|
||||||
}
|
|
||||||
pendingTransactionsById.remove(transactionId);
|
|
||||||
commitedTransactions.add(transactionId);
|
|
||||||
commitCount++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unexpected log entry type");
|
|
||||||
}
|
|
||||||
totalEdits++;
|
|
||||||
|
|
||||||
if (reporter != null && (totalEdits % reportInterval) == 0) {
|
|
||||||
reporter.progress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
LOG.debug("Read " + totalEdits + " tranasctional operations (skipped "
|
|
||||||
+ skippedEdits + " because sequence id <= " + maxSeqID + "): "
|
|
||||||
+ startCount + " starts, " + writeCount + " writes, " + abortCount
|
|
||||||
+ " aborts, and " + commitCount + " commits.");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingTransactionsById.size() > 0) {
|
|
||||||
return resolvePendingTransaction(pendingTransactionsById);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SortedMap<Long, List<WALEdit>> resolvePendingTransaction(
|
|
||||||
SortedMap<Long, List<WALEdit>> pendingTransactionsById
|
|
||||||
) {
|
|
||||||
SortedMap<Long, List<WALEdit>> commitedTransactionsById =
|
|
||||||
new TreeMap<Long, List<WALEdit>>();
|
|
||||||
|
|
||||||
LOG.info("Region log has " + pendingTransactionsById.size()
|
|
||||||
+ " unfinished transactions. Going to the transaction log to resolve");
|
|
||||||
|
|
||||||
for (Entry<Long, List<WALEdit>> entry : pendingTransactionsById.entrySet()) {
|
|
||||||
if (entry.getValue().isEmpty()) {
|
|
||||||
LOG.debug("Skipping resolving trx ["+entry.getKey()+"] has no writes.");
|
|
||||||
}
|
|
||||||
TransactionLogger.TransactionStatus transactionStatus = getGlobalTransactionLog()
|
|
||||||
.getStatusForTransaction(entry.getKey());
|
|
||||||
|
|
||||||
if (transactionStatus == null) {
|
|
||||||
throw new RuntimeException("Cannot resolve tranasction ["
|
|
||||||
+ entry.getKey() + "] from global tx log.");
|
|
||||||
}
|
|
||||||
switch (transactionStatus) {
|
|
||||||
case ABORTED:
|
|
||||||
break;
|
|
||||||
case COMMITTED:
|
|
||||||
commitedTransactionsById.put(entry.getKey(), entry.getValue());
|
|
||||||
break;
|
|
||||||
case PENDING:
|
|
||||||
LOG
|
|
||||||
.warn("Transaction ["
|
|
||||||
+ entry.getKey()
|
|
||||||
+ "] is still pending. Asumming it will not commit."
|
|
||||||
+ " If it eventually does commit, then we loose transactional semantics.");
|
|
||||||
// TODO this could possibly be handled by waiting and seeing what happens.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commitedTransactionsById;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TransactionLogger globalTransactionLog = null;
|
|
||||||
|
|
||||||
private synchronized TransactionLogger getGlobalTransactionLog() {
|
|
||||||
if (globalTransactionLog == null) {
|
|
||||||
try {
|
|
||||||
globalTransactionLog = new HBaseBackedTransactionLogger();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return globalTransactionLog;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,522 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.NavigableSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
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.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
|
||||||
import org.apache.hadoop.hbase.client.Scan;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.InternalScanner;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.KeyValueScanner;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the state of a transaction. This includes a buffer of all writes, a
|
|
||||||
* record of all reads / scans, and information about which other transactions
|
|
||||||
* we need to check against.
|
|
||||||
*/
|
|
||||||
class TransactionState {
|
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(TransactionState.class);
|
|
||||||
|
|
||||||
/** Current status. */
|
|
||||||
public enum Status {
|
|
||||||
/** Initial status, still performing operations. */
|
|
||||||
PENDING,
|
|
||||||
/**
|
|
||||||
* Checked if we can commit, and said yes. Still need to determine the
|
|
||||||
* global decision.
|
|
||||||
*/
|
|
||||||
COMMIT_PENDING,
|
|
||||||
/** Committed. */
|
|
||||||
COMMITED,
|
|
||||||
/** Aborted. */
|
|
||||||
ABORTED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple container of the range of the scanners we've opened. Used to check
|
|
||||||
* for conflicting writes.
|
|
||||||
*/
|
|
||||||
private static class ScanRange {
|
|
||||||
protected byte[] startRow;
|
|
||||||
protected byte[] endRow;
|
|
||||||
|
|
||||||
public ScanRange(byte[] startRow, byte[] endRow) {
|
|
||||||
this.startRow = startRow == HConstants.EMPTY_START_ROW ? null : startRow;
|
|
||||||
this.endRow = endRow == HConstants.EMPTY_END_ROW ? null : endRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this scan range contains the given key.
|
|
||||||
*
|
|
||||||
* @param rowKey
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public boolean contains(byte[] rowKey) {
|
|
||||||
if (startRow != null && Bytes.compareTo(rowKey, startRow) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (endRow != null && Bytes.compareTo(endRow, rowKey) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "startRow: "
|
|
||||||
+ (startRow == null ? "null" : Bytes.toString(startRow))
|
|
||||||
+ ", endRow: " + (endRow == null ? "null" : Bytes.toString(endRow));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HRegionInfo regionInfo;
|
|
||||||
private final long hLogStartSequenceId;
|
|
||||||
private final long transactionId;
|
|
||||||
private Status status;
|
|
||||||
private SortedSet<byte[]> readSet = new TreeSet<byte[]>(
|
|
||||||
Bytes.BYTES_COMPARATOR);
|
|
||||||
private List<Put> puts = new LinkedList<Put>();
|
|
||||||
private List<ScanRange> scans = new LinkedList<ScanRange>();
|
|
||||||
private List<Delete> deletes = new LinkedList<Delete>();
|
|
||||||
private Set<TransactionState> transactionsToCheck = new HashSet<TransactionState>();
|
|
||||||
private int startSequenceNumber;
|
|
||||||
private Integer sequenceNumber;
|
|
||||||
private int commitPendingWaits = 0;
|
|
||||||
|
|
||||||
TransactionState(final long transactionId, final long rLogStartSequenceId,
|
|
||||||
HRegionInfo regionInfo) {
|
|
||||||
this.transactionId = transactionId;
|
|
||||||
this.hLogStartSequenceId = rLogStartSequenceId;
|
|
||||||
this.regionInfo = regionInfo;
|
|
||||||
this.status = Status.PENDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addRead(final byte[] rowKey) {
|
|
||||||
readSet.add(rowKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<byte[]> getReadSet() {
|
|
||||||
return readSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addWrite(final Put write) {
|
|
||||||
updateLatestTimestamp(write.getFamilyMap().values());
|
|
||||||
puts.add(write);
|
|
||||||
}
|
|
||||||
|
|
||||||
//FIXME REVIEW not sure about this. Needed for log recovery? but broke other tests.
|
|
||||||
private void updateLatestTimestamp(Collection<List<KeyValue>> kvsCollection) {
|
|
||||||
byte [] now = Bytes.toBytes(System.currentTimeMillis());
|
|
||||||
// HAVE to manually set the KV timestamps
|
|
||||||
for (List<KeyValue> kvs : kvsCollection) {
|
|
||||||
for (KeyValue kv : kvs) {
|
|
||||||
if (kv.isLatestTimestamp()) {
|
|
||||||
kv.updateLatestStamp(now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasWrite() {
|
|
||||||
return puts.size() > 0 || deletes.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Put> getPuts() {
|
|
||||||
return puts;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDelete(final Delete delete) {
|
|
||||||
deletes.add(delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetFull from the writeSet.
|
|
||||||
*
|
|
||||||
* @param row
|
|
||||||
* @param columns
|
|
||||||
* @param timestamp
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Result localGet(Get get) {
|
|
||||||
|
|
||||||
// TODO take deletes into account as well
|
|
||||||
|
|
||||||
List<KeyValue> localKVs = new ArrayList<KeyValue>();
|
|
||||||
List<Put> reversedPuts = new ArrayList<Put>(puts);
|
|
||||||
Collections.reverse(reversedPuts);
|
|
||||||
for (Put put : reversedPuts) {
|
|
||||||
if (!Bytes.equals(get.getRow(), put.getRow())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (put.getTimeStamp() > get.getTimeRange().getMax()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (put.getTimeStamp() < get.getTimeRange().getMin()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Entry<byte [], NavigableSet<byte []>> getFamilyEntry : get.getFamilyMap().entrySet()) {
|
|
||||||
List<KeyValue> familyPuts = put.getFamilyMap().get(getFamilyEntry.getKey());
|
|
||||||
if (familyPuts == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (getFamilyEntry.getValue() == null){
|
|
||||||
localKVs.addAll(familyPuts);
|
|
||||||
} else {
|
|
||||||
for (KeyValue kv : familyPuts) {
|
|
||||||
if (getFamilyEntry.getValue().contains(kv.getQualifier())) {
|
|
||||||
localKVs.add(kv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localKVs.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Result(localKVs);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addTransactionToCheck(final TransactionState transaction) {
|
|
||||||
transactionsToCheck.add(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasConflict() {
|
|
||||||
for (TransactionState transactionState : transactionsToCheck) {
|
|
||||||
if (hasConflict(transactionState)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasConflict(final TransactionState checkAgainst) {
|
|
||||||
if (checkAgainst.getStatus().equals(TransactionState.Status.ABORTED)) {
|
|
||||||
return false; // Cannot conflict with aborted transactions
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Put otherUpdate : checkAgainst.getPuts()) {
|
|
||||||
if (this.getReadSet().contains(otherUpdate.getRow())) {
|
|
||||||
LOG.debug("Transaction [" + this.toString()
|
|
||||||
+ "] has read which conflicts with [" + checkAgainst.toString()
|
|
||||||
+ "]: region [" + regionInfo.getRegionNameAsString() + "], row["
|
|
||||||
+ Bytes.toString(otherUpdate.getRow()) + "]");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (ScanRange scanRange : this.scans) {
|
|
||||||
if (scanRange.contains(otherUpdate.getRow())) {
|
|
||||||
LOG.debug("Transaction [" + this.toString()
|
|
||||||
+ "] has scan which conflicts with [" + checkAgainst.toString()
|
|
||||||
+ "]: region [" + regionInfo.getRegionNameAsString() + "], scanRange[" +
|
|
||||||
scanRange.toString()+"] ,row["
|
|
||||||
+ Bytes.toString(otherUpdate.getRow()) + "]");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the status.
|
|
||||||
*
|
|
||||||
* @return Return the status.
|
|
||||||
*/
|
|
||||||
Status getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the status.
|
|
||||||
*
|
|
||||||
* @param status The status to set.
|
|
||||||
*/
|
|
||||||
void setStatus(final Status status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the startSequenceNumber.
|
|
||||||
*
|
|
||||||
* @return Return the startSequenceNumber.
|
|
||||||
*/
|
|
||||||
int getStartSequenceNumber() {
|
|
||||||
return startSequenceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the startSequenceNumber.
|
|
||||||
*
|
|
||||||
* @param startSequenceNumber
|
|
||||||
*/
|
|
||||||
void setStartSequenceNumber(final int startSequenceNumber) {
|
|
||||||
this.startSequenceNumber = startSequenceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the sequenceNumber.
|
|
||||||
*
|
|
||||||
* @return Return the sequenceNumber.
|
|
||||||
*/
|
|
||||||
Integer getSequenceNumber() {
|
|
||||||
return sequenceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the sequenceNumber.
|
|
||||||
*
|
|
||||||
* @param sequenceNumber The sequenceNumber to set.
|
|
||||||
*/
|
|
||||||
void setSequenceNumber(final Integer sequenceNumber) {
|
|
||||||
this.sequenceNumber = sequenceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
result.append("[transactionId: ");
|
|
||||||
result.append(transactionId);
|
|
||||||
result.append(" status: ");
|
|
||||||
result.append(status.name());
|
|
||||||
result.append(" read Size: ");
|
|
||||||
result.append(readSet.size());
|
|
||||||
result.append(" scan Size: ");
|
|
||||||
result.append(scans.size());
|
|
||||||
result.append(" write Size: ");
|
|
||||||
result.append(puts.size());
|
|
||||||
result.append(" startSQ: ");
|
|
||||||
result.append(startSequenceNumber);
|
|
||||||
if (sequenceNumber != null) {
|
|
||||||
result.append(" commitedSQ:");
|
|
||||||
result.append(sequenceNumber);
|
|
||||||
}
|
|
||||||
result.append("]");
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the transactionId.
|
|
||||||
*
|
|
||||||
* @return Return the transactionId.
|
|
||||||
*/
|
|
||||||
long getTransactionId() {
|
|
||||||
return transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the startSequenceId.
|
|
||||||
*
|
|
||||||
* @return Return the startSequenceId.
|
|
||||||
*/
|
|
||||||
long getHLogStartSequenceId() {
|
|
||||||
return hLogStartSequenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addScan(Scan scan) {
|
|
||||||
ScanRange scanRange = new ScanRange(scan.getStartRow(), scan.getStopRow());
|
|
||||||
LOG.trace(String.format(
|
|
||||||
"Adding scan for transcaction [%s], from [%s] to [%s]", transactionId,
|
|
||||||
scanRange.startRow == null ? "null" : Bytes
|
|
||||||
.toString(scanRange.startRow), scanRange.endRow == null ? "null"
|
|
||||||
: Bytes.toString(scanRange.endRow)));
|
|
||||||
scans.add(scanRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getCommitPendingWaits() {
|
|
||||||
return commitPendingWaits;
|
|
||||||
}
|
|
||||||
|
|
||||||
void incrementCommitPendingWaits() {
|
|
||||||
this.commitPendingWaits++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get deleteSet.
|
|
||||||
* @return deleteSet
|
|
||||||
*/
|
|
||||||
List<Delete> getDeleteSet() {
|
|
||||||
return deletes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get a scanner to go through the puts from this transaction. Used to weave together the local trx puts with the global state.
|
|
||||||
*
|
|
||||||
* @return scanner
|
|
||||||
*/
|
|
||||||
KeyValueScanner getScanner() {
|
|
||||||
return new PutScanner();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Scanner of the puts that occur during this transaction.
|
|
||||||
*
|
|
||||||
* @author clint.morgan
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private class PutScanner implements KeyValueScanner, InternalScanner {
|
|
||||||
|
|
||||||
private List<KeyValue> kvList;
|
|
||||||
private Iterator<KeyValue> iterator;
|
|
||||||
private boolean didHasNext = false;
|
|
||||||
private KeyValue next = null;
|
|
||||||
|
|
||||||
|
|
||||||
PutScanner() {
|
|
||||||
kvList = new ArrayList<KeyValue>();
|
|
||||||
for (Put put : puts) {
|
|
||||||
for (List<KeyValue> putKVs : put.getFamilyMap().values()) {
|
|
||||||
kvList.addAll(putKVs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(kvList, new Comparator<KeyValue>() {
|
|
||||||
|
|
||||||
/** We want to honor the order of the puts in the case where multiple have the same timestamp.
|
|
||||||
*
|
|
||||||
* @param o1
|
|
||||||
* @param o2
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public int compare(KeyValue o1, KeyValue o2) {
|
|
||||||
int result = KeyValue.COMPARATOR.compare(o1, o2);
|
|
||||||
if (result != 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (o1 == o2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int put1Number = getPutNumber(o1);
|
|
||||||
int put2Number = getPutNumber(o2);
|
|
||||||
return put2Number - put1Number;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
iterator = kvList.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPutNumber(KeyValue kv) {
|
|
||||||
for (int i=0; i < puts.size(); i++) {
|
|
||||||
for (List<KeyValue> putKVs : puts.get(i).getFamilyMap().values()) {
|
|
||||||
for (KeyValue putKV : putKVs)
|
|
||||||
if (putKV == kv) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Can not fine put KV in puts");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
// Nothing to close
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValue next() {
|
|
||||||
getNext();
|
|
||||||
didHasNext = false;
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValue peek() {
|
|
||||||
getNext();
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void iteratorFrom(KeyValue key) {
|
|
||||||
iterator = kvList.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
KeyValue next = iterator.next();
|
|
||||||
if (KeyValue.COMPARATOR.compare(next, key) >= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean seek(KeyValue key) {
|
|
||||||
iteratorFrom(key);
|
|
||||||
|
|
||||||
getNext();
|
|
||||||
return next != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyValue getNext() {
|
|
||||||
if (didHasNext) {
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
didHasNext = true;
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
next = iterator.next(); }
|
|
||||||
else {
|
|
||||||
next= null;
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean next(List<KeyValue> results, int limit) throws IOException {
|
|
||||||
KeyValue peek = this.peek();
|
|
||||||
if (peek == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
byte [] row = peek.getRow();
|
|
||||||
results.add(peek);
|
|
||||||
if (limit > 0 && (results.size() == limit)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
while (true){
|
|
||||||
if (this.peek() == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!Bytes.equals(row, this.peek().getRow())) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
results.add(this.next());
|
|
||||||
if (limit > 0 && (results.size() == limit)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean next(List<KeyValue> results) throws IOException {
|
|
||||||
return next(results, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,722 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.LeaseException;
|
|
||||||
import org.apache.hadoop.hbase.LeaseListener;
|
|
||||||
import org.apache.hadoop.hbase.Leases;
|
|
||||||
import org.apache.hadoop.hbase.Leases.LeaseStillHeldException;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
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.client.transactional.HBaseBackedTransactionLogger;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.UnknownTransactionException;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.*;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.transactional.TransactionState.Status;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.*;
|
|
||||||
import org.apache.hadoop.util.Progressable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regionserver which provides transactional support for atomic transactions.
|
|
||||||
* This is achieved with optimistic concurrency control (see
|
|
||||||
* http://www.seas.upenn.edu/~zives/cis650/papers/opt-cc.pdf). We keep track
|
|
||||||
* read and write sets for each transaction, and hold off on processing the
|
|
||||||
* writes. To decide to commit a transaction we check its read sets with all
|
|
||||||
* transactions that have committed while it was running for overlaps.
|
|
||||||
* <p>
|
|
||||||
* Because transactions can span multiple regions, all regions must agree to
|
|
||||||
* commit a transactions. The client side of this commit protocol is encoded in
|
|
||||||
* org.apache.hadoop.hbase.client.transactional.TransactionManger
|
|
||||||
* <p>
|
|
||||||
* In the event of an failure of the client mid-commit, (after we voted yes), we
|
|
||||||
* will have to consult the transaction log to determine the final decision of
|
|
||||||
* the transaction. This is not yet implemented.
|
|
||||||
*/
|
|
||||||
public class TransactionalRegion extends HRegion {
|
|
||||||
|
|
||||||
private static final String OLD_TRANSACTION_FLUSH = "hbase.transaction.flush";
|
|
||||||
private static final int DEFAULT_OLD_TRANSACTION_FLUSH = 100; // Do a flush if
|
|
||||||
// we have this
|
|
||||||
// many old
|
|
||||||
// transactions..
|
|
||||||
|
|
||||||
static final Log LOG = LogFactory.getLog(TransactionalRegion.class);
|
|
||||||
|
|
||||||
// Collection of active transactions (PENDING) keyed by id.
|
|
||||||
protected Map<String, TransactionState> transactionsById = new HashMap<String, TransactionState>();
|
|
||||||
|
|
||||||
// Map of recent transactions that are COMMIT_PENDING or COMMITED keyed by
|
|
||||||
// their sequence number
|
|
||||||
private SortedMap<Integer, TransactionState> commitedTransactionsBySequenceNumber = Collections
|
|
||||||
.synchronizedSortedMap(new TreeMap<Integer, TransactionState>());
|
|
||||||
|
|
||||||
// Collection of transactions that are COMMIT_PENDING
|
|
||||||
private Set<TransactionState> commitPendingTransactions = Collections
|
|
||||||
.synchronizedSet(new HashSet<TransactionState>());
|
|
||||||
|
|
||||||
private AtomicInteger nextSequenceId = new AtomicInteger(0);
|
|
||||||
private Object commitCheckLock = new Object();
|
|
||||||
private THLog hlog;
|
|
||||||
private final int oldTransactionFlushTrigger;
|
|
||||||
private final Leases transactionLeases;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param basedir
|
|
||||||
* @param log
|
|
||||||
* @param fs
|
|
||||||
* @param conf
|
|
||||||
* @param regionInfo
|
|
||||||
* @param flushListener
|
|
||||||
*/
|
|
||||||
public TransactionalRegion(final Path basedir, final HLog log,
|
|
||||||
final FileSystem fs, final Configuration conf,
|
|
||||||
final HRegionInfo regionInfo, final FlushRequester flushListener,
|
|
||||||
final Leases transactionalLeases) {
|
|
||||||
super(basedir, log, fs, conf, regionInfo, flushListener);
|
|
||||||
if (log instanceof THLog) {
|
|
||||||
this.hlog = (THLog) log;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("log is not THLog");
|
|
||||||
}
|
|
||||||
oldTransactionFlushTrigger = conf.getInt(OLD_TRANSACTION_FLUSH,
|
|
||||||
DEFAULT_OLD_TRANSACTION_FLUSH);
|
|
||||||
this.transactionLeases = transactionalLeases;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doReconstructionLog(final Path oldLogFile,
|
|
||||||
final long minSeqId, final long maxSeqId, final Progressable reporter)
|
|
||||||
throws UnsupportedEncodingException, IOException {
|
|
||||||
super.doReconstructionLog(oldLogFile, minSeqId, maxSeqId, reporter);
|
|
||||||
|
|
||||||
// We can ignore doing anything with the Trx Log table, it is not-transactional.
|
|
||||||
if (super.getTableDesc().getNameAsString().equals(HBaseBackedTransactionLogger.TABLE_NAME)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
THLogRecoveryManager recoveryManager = new THLogRecoveryManager(this);
|
|
||||||
Map<Long, List<WALEdit>> commitedTransactionsById = recoveryManager
|
|
||||||
.getCommitsFromLog(oldLogFile, minSeqId, reporter);
|
|
||||||
|
|
||||||
if (commitedTransactionsById != null && commitedTransactionsById.size() > 0) {
|
|
||||||
LOG.debug("found " + commitedTransactionsById.size()
|
|
||||||
+ " COMMITED transactions to recover.");
|
|
||||||
|
|
||||||
for (Entry<Long, List<WALEdit>> entry : commitedTransactionsById
|
|
||||||
.entrySet()) {
|
|
||||||
LOG.debug("Writing " + entry.getValue().size()
|
|
||||||
+ " updates for transaction " + entry.getKey());
|
|
||||||
for (WALEdit b : entry.getValue()) {
|
|
||||||
Put put = null;
|
|
||||||
for (KeyValue kv: b.getKeyValues()) {
|
|
||||||
if (put == null) put = new Put(kv.getRow());
|
|
||||||
put.add(kv);
|
|
||||||
}
|
|
||||||
super.put(put, true); // These are walled so they live forever
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug("Flushing cache"); // We must trigger a cache flush,
|
|
||||||
//otherwise we will would ignore the log on subsequent failure
|
|
||||||
if (!super.flushcache()) {
|
|
||||||
LOG.warn("Did not flush cache");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We need to make sure that we don't complete a cache flush between running
|
|
||||||
* transactions. If we did, then we would not find all log messages needed to
|
|
||||||
* restore the transaction, as some of them would be before the last
|
|
||||||
* "complete" flush id.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected long getCompleteCacheFlushSequenceId(final long currentSequenceId) {
|
|
||||||
LinkedList<TransactionState> transactionStates;
|
|
||||||
synchronized (transactionsById) {
|
|
||||||
transactionStates = new LinkedList<TransactionState>(transactionsById
|
|
||||||
.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
long minPendingStartSequenceId = currentSequenceId;
|
|
||||||
for (TransactionState transactionState : transactionStates) {
|
|
||||||
minPendingStartSequenceId = Math.min(minPendingStartSequenceId,
|
|
||||||
transactionState.getHLogStartSequenceId());
|
|
||||||
}
|
|
||||||
return minPendingStartSequenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void beginTransaction(final long transactionId) throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
String key = String.valueOf(transactionId);
|
|
||||||
if (transactionsById.get(key) != null) {
|
|
||||||
TransactionState alias = getTransactionState(transactionId);
|
|
||||||
if (alias != null) {
|
|
||||||
alias.setStatus(Status.ABORTED);
|
|
||||||
retireTransaction(alias);
|
|
||||||
}
|
|
||||||
LOG.error("Existing trasaction with id [" + key + "] in region ["
|
|
||||||
+ super.getRegionInfo().getRegionNameAsString() + "]");
|
|
||||||
throw new IOException("Already exiting transaction id: " + key);
|
|
||||||
}
|
|
||||||
|
|
||||||
TransactionState state = new TransactionState(transactionId, super.getLog()
|
|
||||||
.getSequenceNumber(), super.getRegionInfo());
|
|
||||||
|
|
||||||
state.setStartSequenceNumber(nextSequenceId.get());
|
|
||||||
List<TransactionState> commitPendingCopy = new ArrayList<TransactionState>(
|
|
||||||
commitPendingTransactions);
|
|
||||||
for (TransactionState commitPending : commitPendingCopy) {
|
|
||||||
state.addTransactionToCheck(commitPending);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (transactionsById) {
|
|
||||||
transactionsById.put(key, state);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
transactionLeases.createLease(getLeaseId(transactionId),
|
|
||||||
new TransactionLeaseListener(key));
|
|
||||||
} catch (LeaseStillHeldException e) {
|
|
||||||
LOG.error("Lease still held for [" + key + "] in region ["
|
|
||||||
+ super.getRegionInfo().getRegionNameAsString() + "]");
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
LOG.debug("Begining transaction " + key + " in region "
|
|
||||||
+ super.getRegionInfo().getRegionNameAsString());
|
|
||||||
|
|
||||||
maybeTriggerOldTransactionFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getLeaseId(long transactionId) {
|
|
||||||
return super.getRegionInfo().getRegionNameAsString() + transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result get(final long transactionId, Get get) throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
|
|
||||||
TransactionState state = getTransactionState(transactionId);
|
|
||||||
|
|
||||||
state.addRead(get.getRow());
|
|
||||||
|
|
||||||
Result superGet = super.get(get, null);
|
|
||||||
Result localGet = state.localGet(get);
|
|
||||||
|
|
||||||
if (localGet != null) {
|
|
||||||
LOG
|
|
||||||
.trace("Transactional get of something we've written in the same transaction "
|
|
||||||
+ transactionId);
|
|
||||||
|
|
||||||
List<KeyValue> mergedGet = new ArrayList<KeyValue>(Arrays.asList(localGet
|
|
||||||
.raw()));
|
|
||||||
|
|
||||||
if (superGet != null && !superGet.isEmpty()) {
|
|
||||||
for (KeyValue kv : superGet.raw()) {
|
|
||||||
if (!localGet.containsColumn(kv.getFamily(), kv.getQualifier())) {
|
|
||||||
mergedGet.add(kv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Result(mergedGet);
|
|
||||||
}
|
|
||||||
|
|
||||||
return superGet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a transactional scanner.
|
|
||||||
*/
|
|
||||||
public InternalScanner getScanner(final long transactionId, Scan scan)
|
|
||||||
throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
|
|
||||||
TransactionState state = getTransactionState(transactionId);
|
|
||||||
state.addScan(scan);
|
|
||||||
List<KeyValueScanner> scanners = new ArrayList<KeyValueScanner>(1);
|
|
||||||
scanners.add(state.getScanner());
|
|
||||||
return super.getScanner(scan, scanners);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a write to the transaction. Does not get applied until commit process.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
* @param put
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void put(final long transactionId, final Put put) throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
|
|
||||||
TransactionState state = getTransactionState(transactionId);
|
|
||||||
state.addWrite(put);
|
|
||||||
this.hlog.writeUpdateToLog(super.getRegionInfo(), transactionId, put);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add multiple writes to the transaction. Does not get applied until commit
|
|
||||||
* process.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
* @param puts
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void put(final long transactionId, final Put[] puts)
|
|
||||||
throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
|
|
||||||
TransactionState state = getTransactionState(transactionId);
|
|
||||||
for (Put put : puts) {
|
|
||||||
state.addWrite(put);
|
|
||||||
this.hlog.writeUpdateToLog(super.getRegionInfo(), transactionId, put);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a delete to the transaction. Does not get applied until commit process.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
* @param delete
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void delete(final long transactionId, Delete delete)
|
|
||||||
throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
TransactionState state = getTransactionState(transactionId);
|
|
||||||
state.addDelete(delete);
|
|
||||||
this.hlog.writeDeleteToLog(super.getRegionInfo(), transactionId, delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param transactionId
|
|
||||||
* @return TransactionRegionInterface commit code
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public int commitRequest(final long transactionId) throws IOException {
|
|
||||||
checkClosing();
|
|
||||||
|
|
||||||
synchronized (commitCheckLock) {
|
|
||||||
TransactionState state = getTransactionState(transactionId);
|
|
||||||
if (state == null) {
|
|
||||||
return TransactionalRegionInterface.COMMIT_UNSUCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasConflict(state)) {
|
|
||||||
state.setStatus(Status.ABORTED);
|
|
||||||
retireTransaction(state);
|
|
||||||
return TransactionalRegionInterface.COMMIT_UNSUCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No conflicts, we can commit.
|
|
||||||
LOG.trace("No conflicts for transaction " + transactionId
|
|
||||||
+ " found in region " + super.getRegionInfo().getRegionNameAsString()
|
|
||||||
+ ". Voting for commit");
|
|
||||||
|
|
||||||
// If there are writes we must keep record off the transaction
|
|
||||||
if (state.hasWrite()) {
|
|
||||||
// Order is important
|
|
||||||
state.setStatus(Status.COMMIT_PENDING);
|
|
||||||
commitPendingTransactions.add(state);
|
|
||||||
state.setSequenceNumber(nextSequenceId.getAndIncrement());
|
|
||||||
commitedTransactionsBySequenceNumber.put(state.getSequenceNumber(),
|
|
||||||
state);
|
|
||||||
return TransactionalRegionInterface.COMMIT_OK;
|
|
||||||
}
|
|
||||||
// Otherwise we were read-only and commitable, so we can forget it.
|
|
||||||
state.setStatus(Status.COMMITED);
|
|
||||||
retireTransaction(state);
|
|
||||||
return TransactionalRegionInterface.COMMIT_OK_READ_ONLY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param transactionId
|
|
||||||
* @return true if commit is successful
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public boolean commitIfPossible(final long transactionId) throws IOException {
|
|
||||||
int status = commitRequest(transactionId);
|
|
||||||
|
|
||||||
if (status == TransactionalRegionInterface.COMMIT_OK) {
|
|
||||||
commit(transactionId);
|
|
||||||
return true;
|
|
||||||
} else if (status == TransactionalRegionInterface.COMMIT_OK_READ_ONLY) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasConflict(final TransactionState state) {
|
|
||||||
// Check transactions that were committed while we were running
|
|
||||||
for (int i = state.getStartSequenceNumber(); i < nextSequenceId.get(); i++) {
|
|
||||||
TransactionState other = commitedTransactionsBySequenceNumber.get(i);
|
|
||||||
if (other == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
state.addTransactionToCheck(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.hasConflict();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit the transaction.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void commit(final long transactionId) throws IOException {
|
|
||||||
TransactionState state;
|
|
||||||
try {
|
|
||||||
state = getTransactionState(transactionId);
|
|
||||||
} catch (UnknownTransactionException e) {
|
|
||||||
LOG.fatal("Asked to commit unknown transaction: " + transactionId
|
|
||||||
+ " in region " + super.getRegionInfo().getRegionNameAsString());
|
|
||||||
// TODO. Anything to handle here?
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.getStatus().equals(Status.COMMIT_PENDING)) {
|
|
||||||
LOG.fatal("Asked to commit a non pending transaction");
|
|
||||||
// TODO. Anything to handle here?
|
|
||||||
throw new IOException("commit failure");
|
|
||||||
}
|
|
||||||
|
|
||||||
commit(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit the transaction.
|
|
||||||
*
|
|
||||||
* @param transactionId
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void abort(final long transactionId) throws IOException {
|
|
||||||
// Not checking closing...
|
|
||||||
TransactionState state;
|
|
||||||
try {
|
|
||||||
state = getTransactionState(transactionId);
|
|
||||||
} catch (UnknownTransactionException e) {
|
|
||||||
LOG.info("Asked to abort unknown transaction [" + transactionId
|
|
||||||
+ "] in region [" + getRegionInfo().getRegionNameAsString()
|
|
||||||
+ "], ignoring");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.setStatus(Status.ABORTED);
|
|
||||||
|
|
||||||
if (state.hasWrite()) {
|
|
||||||
this.hlog.writeAbortToLog(super.getRegionInfo(), state.getTransactionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following removes needed if we have voted
|
|
||||||
if (state.getSequenceNumber() != null) {
|
|
||||||
commitedTransactionsBySequenceNumber.remove(state.getSequenceNumber());
|
|
||||||
}
|
|
||||||
commitPendingTransactions.remove(state);
|
|
||||||
|
|
||||||
retireTransaction(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void commit(final TransactionState state) throws IOException {
|
|
||||||
|
|
||||||
LOG.debug("Commiting transaction: " + state.toString() + " to "
|
|
||||||
+ super.getRegionInfo().getRegionNameAsString());
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME potential mix up here if some deletes should come before the puts.
|
|
||||||
for (Put update : state.getPuts()) {
|
|
||||||
this.put(update, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Delete delete : state.getDeleteSet()) {
|
|
||||||
this.delete(delete, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now the transaction lives in the WAL, we can write a commit to the log
|
|
||||||
// so we don't have to recover it.
|
|
||||||
if (state.hasWrite()) {
|
|
||||||
this.hlog.writeCommitToLog(super.getRegionInfo(), state.getTransactionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
state.setStatus(Status.COMMITED);
|
|
||||||
if (state.hasWrite()
|
|
||||||
&& !commitPendingTransactions.remove(state)) {
|
|
||||||
LOG
|
|
||||||
.fatal("Commiting a non-query transaction that is not in commitPendingTransactions");
|
|
||||||
// Something has gone really wrong.
|
|
||||||
throw new IOException("commit failure");
|
|
||||||
}
|
|
||||||
retireTransaction(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<StoreFile> close(boolean abort) throws IOException {
|
|
||||||
prepareToClose();
|
|
||||||
if (!commitPendingTransactions.isEmpty()) {
|
|
||||||
LOG.warn("Closing transactional region ["
|
|
||||||
+ getRegionInfo().getRegionNameAsString() + "], but still have ["
|
|
||||||
+ commitPendingTransactions.size()
|
|
||||||
+ "] transactions that are pending commit.");
|
|
||||||
// TODO resolve from the Global Trx Log.
|
|
||||||
}
|
|
||||||
return super.close(abort);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void prepareToSplit() {
|
|
||||||
prepareToClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean closing = false;
|
|
||||||
private static final int CLOSE_WAIT_ON_COMMIT_PENDING = 1000;
|
|
||||||
/**
|
|
||||||
* Get ready to close.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void prepareToClose() {
|
|
||||||
LOG.info("Preparing to close region "
|
|
||||||
+ getRegionInfo().getRegionNameAsString());
|
|
||||||
closing = true;
|
|
||||||
|
|
||||||
while (!commitPendingTransactions.isEmpty()) {
|
|
||||||
LOG.info("Preparing to closing transactional region ["
|
|
||||||
+ getRegionInfo().getRegionNameAsString() + "], but still have ["
|
|
||||||
+ commitPendingTransactions.size()
|
|
||||||
+ "] transactions that are pending commit. Sleeping");
|
|
||||||
for (TransactionState s : commitPendingTransactions) {
|
|
||||||
LOG.info("commit pending: " + s.toString());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Thread.sleep(CLOSE_WAIT_ON_COMMIT_PENDING);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkClosing() throws IOException {
|
|
||||||
if (closing) {
|
|
||||||
throw new IOException("closing region, no more transaction allowed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel leases, and removed from lease lookup. This transaction may still
|
|
||||||
// live in commitedTransactionsBySequenceNumber and commitPendingTransactions
|
|
||||||
private void retireTransaction(final TransactionState state) {
|
|
||||||
String key = String.valueOf(state.getTransactionId());
|
|
||||||
try {
|
|
||||||
transactionLeases.cancelLease(getLeaseId(state.getTransactionId()));
|
|
||||||
} catch (LeaseException e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionsById.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TransactionState getTransactionState(final long transactionId)
|
|
||||||
throws UnknownTransactionException {
|
|
||||||
String key = String.valueOf(transactionId);
|
|
||||||
TransactionState state = null;
|
|
||||||
|
|
||||||
state = transactionsById.get(key);
|
|
||||||
|
|
||||||
if (state == null) {
|
|
||||||
LOG.debug("Unknown transaction: [" + key + "], region: ["
|
|
||||||
+ getRegionInfo().getRegionNameAsString() + "]");
|
|
||||||
throw new UnknownTransactionException("transaction: [" + key
|
|
||||||
+ "], region: [" + getRegionInfo().getRegionNameAsString() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
transactionLeases.renewLease(getLeaseId(transactionId));
|
|
||||||
} catch (LeaseException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeTriggerOldTransactionFlush() {
|
|
||||||
if (commitedTransactionsBySequenceNumber.size() > oldTransactionFlushTrigger) {
|
|
||||||
removeUnNeededCommitedTransactions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleanup references to committed transactions that are no longer needed.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
synchronized void removeUnNeededCommitedTransactions() {
|
|
||||||
Integer minStartSeqNumber = getMinStartSequenceNumber();
|
|
||||||
if (minStartSeqNumber == null) {
|
|
||||||
minStartSeqNumber = Integer.MAX_VALUE; // Remove all
|
|
||||||
}
|
|
||||||
|
|
||||||
int numRemoved = 0;
|
|
||||||
// Copy list to avoid conc update exception
|
|
||||||
for (Entry<Integer, TransactionState> entry : new LinkedList<Entry<Integer, TransactionState>>(
|
|
||||||
commitedTransactionsBySequenceNumber.entrySet())) {
|
|
||||||
if (entry.getKey() >= minStartSeqNumber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
numRemoved = numRemoved
|
|
||||||
+ (commitedTransactionsBySequenceNumber.remove(entry.getKey()) == null ? 0
|
|
||||||
: 1);
|
|
||||||
numRemoved++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
StringBuilder debugMessage = new StringBuilder();
|
|
||||||
if (numRemoved > 0) {
|
|
||||||
debugMessage.append("Removed [").append(numRemoved).append(
|
|
||||||
"] commited transactions");
|
|
||||||
|
|
||||||
if (minStartSeqNumber == Integer.MAX_VALUE) {
|
|
||||||
debugMessage.append("with any sequence number");
|
|
||||||
} else {
|
|
||||||
debugMessage.append("with sequence lower than [").append(
|
|
||||||
minStartSeqNumber).append("].");
|
|
||||||
}
|
|
||||||
if (!commitedTransactionsBySequenceNumber.isEmpty()) {
|
|
||||||
debugMessage.append(" Still have [").append(
|
|
||||||
commitedTransactionsBySequenceNumber.size()).append("] left.");
|
|
||||||
} else {
|
|
||||||
debugMessage.append(" None left.");
|
|
||||||
}
|
|
||||||
LOG.debug(debugMessage.toString());
|
|
||||||
} else if (commitedTransactionsBySequenceNumber.size() > 0) {
|
|
||||||
debugMessage.append(
|
|
||||||
"Could not remove any transactions, and still have ").append(
|
|
||||||
commitedTransactionsBySequenceNumber.size()).append(" left");
|
|
||||||
LOG.debug(debugMessage.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer getMinStartSequenceNumber() {
|
|
||||||
LinkedList<TransactionState> transactionStates;
|
|
||||||
synchronized (transactionsById) {
|
|
||||||
transactionStates = new LinkedList<TransactionState>(transactionsById
|
|
||||||
.values());
|
|
||||||
}
|
|
||||||
Integer min = null;
|
|
||||||
for (TransactionState transactionState : transactionStates) {
|
|
||||||
if (min == null || transactionState.getStartSequenceNumber() < min) {
|
|
||||||
min = transactionState.getStartSequenceNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveTransactionFromLog(final TransactionState transactionState)
|
|
||||||
throws IOException {
|
|
||||||
LOG
|
|
||||||
.error("Global transaction log is not Implemented. (Optimisticly) assuming transaction commit!");
|
|
||||||
commit(transactionState);
|
|
||||||
// throw new RuntimeException("Global transaction log is not Implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static final int MAX_COMMIT_PENDING_WAITS = 10;
|
|
||||||
|
|
||||||
private class TransactionLeaseListener implements LeaseListener {
|
|
||||||
private final String transactionName;
|
|
||||||
|
|
||||||
TransactionLeaseListener(final String n) {
|
|
||||||
this.transactionName = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void leaseExpired() {
|
|
||||||
LOG.info("Transaction [" + this.transactionName + "] expired in region ["
|
|
||||||
+ getRegionInfo().getRegionNameAsString() + "]");
|
|
||||||
TransactionState s = null;
|
|
||||||
synchronized (transactionsById) {
|
|
||||||
s = transactionsById.remove(transactionName);
|
|
||||||
}
|
|
||||||
if (s == null) {
|
|
||||||
LOG.warn("Unknown transaction expired " + this.transactionName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (s.getStatus()) {
|
|
||||||
case PENDING:
|
|
||||||
s.setStatus(Status.ABORTED); // Other transactions may have a ref
|
|
||||||
break;
|
|
||||||
case COMMIT_PENDING:
|
|
||||||
LOG.info("Transaction " + s.getTransactionId()
|
|
||||||
+ " expired in COMMIT_PENDING state");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (s.getCommitPendingWaits() > MAX_COMMIT_PENDING_WAITS) {
|
|
||||||
LOG.info("Checking transaction status in transaction log");
|
|
||||||
resolveTransactionFromLog(s);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
LOG.info("renewing lease and hoping for commit");
|
|
||||||
s.incrementCommitPendingWaits();
|
|
||||||
String key = Long.toString(s.getTransactionId());
|
|
||||||
transactionsById.put(key, s);
|
|
||||||
try {
|
|
||||||
transactionLeases.createLease(getLeaseId(s.getTransactionId()),
|
|
||||||
this);
|
|
||||||
} catch (LeaseStillHeldException e) {
|
|
||||||
transactionLeases.renewLease(getLeaseId(s.getTransactionId()));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG.warn("Unexpected status on expired lease");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,248 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.Leases;
|
|
||||||
import org.apache.hadoop.hbase.NotServingRegionException;
|
|
||||||
import org.apache.hadoop.hbase.RemoteExceptionHandler;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
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.ipc.HBaseRPCProtocolVersion;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.HLog;
|
|
||||||
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.util.Threads;
|
|
||||||
import org.apache.hadoop.io.MapWritable;
|
|
||||||
import org.apache.hadoop.util.Progressable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RegionServer with support for transactions. Transactional logic is at the
|
|
||||||
* region level, so we mostly just delegate to the appropriate
|
|
||||||
* TransactionalRegion.
|
|
||||||
*/
|
|
||||||
public class TransactionalRegionServer extends HRegionServer implements
|
|
||||||
TransactionalRegionInterface {
|
|
||||||
|
|
||||||
private static final String LEASE_TIME = "hbase.transaction.leasetime";
|
|
||||||
private static final int DEFAULT_LEASE_TIME = 60 * 1000;
|
|
||||||
private static final int LEASE_CHECK_FREQUENCY = 1000;
|
|
||||||
|
|
||||||
static final Log LOG = LogFactory.getLog(TransactionalRegionServer.class);
|
|
||||||
private final Leases transactionLeases;
|
|
||||||
private final CleanOldTransactionsChore cleanOldTransactionsThread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param conf
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public TransactionalRegionServer(final Configuration conf)
|
|
||||||
throws IOException {
|
|
||||||
super(conf);
|
|
||||||
cleanOldTransactionsThread = new CleanOldTransactionsChore(this,
|
|
||||||
super.stopRequested);
|
|
||||||
transactionLeases = new Leases(conf.getInt(LEASE_TIME, DEFAULT_LEASE_TIME),
|
|
||||||
LEASE_CHECK_FREQUENCY);
|
|
||||||
LOG.error("leases time:"+conf.getInt(LEASE_TIME, DEFAULT_LEASE_TIME));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getProtocolVersion(final String protocol, final long clientVersion)
|
|
||||||
throws IOException {
|
|
||||||
if (protocol.equals(TransactionalRegionInterface.class.getName())) {
|
|
||||||
return HBaseRPCProtocolVersion.versionID;
|
|
||||||
}
|
|
||||||
return super.getProtocolVersion(protocol, clientVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void init(final MapWritable c) throws IOException {
|
|
||||||
super.init(c);
|
|
||||||
String n = Thread.currentThread().getName();
|
|
||||||
UncaughtExceptionHandler handler = new UncaughtExceptionHandler() {
|
|
||||||
public void uncaughtException(final Thread t, final Throwable e) {
|
|
||||||
abort();
|
|
||||||
LOG.fatal("Set stop flag in " + t.getName(), e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Threads.setDaemonThreadRunning(this.cleanOldTransactionsThread, n
|
|
||||||
+ ".oldTransactionCleaner", handler);
|
|
||||||
Threads.setDaemonThreadRunning(this.transactionLeases, "Transactional leases");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HLog instantiateHLog(Path logdir, Path oldLogDir) throws IOException {
|
|
||||||
conf.set("hbase.regionserver.hlog.keyclass",
|
|
||||||
THLogKey.class.getCanonicalName());
|
|
||||||
HLog newlog = new THLog(super.getFileSystem(), logdir, oldLogDir,
|
|
||||||
conf, super.getLogRoller());
|
|
||||||
return newlog;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HRegion instantiateRegion(final HRegionInfo regionInfo)
|
|
||||||
throws IOException {
|
|
||||||
HRegion r = new TransactionalRegion(HTableDescriptor.getTableDir(super
|
|
||||||
.getRootDir(), regionInfo.getTableDesc().getName()), super.hlog, super
|
|
||||||
.getFileSystem(), super.conf, regionInfo, super.getFlushRequester(), this.getTransactionalLeases());
|
|
||||||
r.initialize(null, new Progressable() {
|
|
||||||
public void progress() {
|
|
||||||
addProcessingMessage(regionInfo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TransactionalRegion getTransactionalRegion(final byte[] regionName)
|
|
||||||
throws NotServingRegionException {
|
|
||||||
return (TransactionalRegion) super.getRegion(regionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Leases getTransactionalLeases() {
|
|
||||||
return this.transactionLeases;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** We want to delay the close region for a bit if we have commit pending transactions.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void closeRegion(final HRegionInfo hri, final boolean reportWhenCompleted)
|
|
||||||
throws IOException {
|
|
||||||
getTransactionalRegion(hri.getRegionName()).prepareToClose();
|
|
||||||
super.closeRegion(hri, reportWhenCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void abort(final byte[] regionName, final long transactionId)
|
|
||||||
throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
super.getRequestCount().incrementAndGet();
|
|
||||||
try {
|
|
||||||
getTransactionalRegion(regionName).abort(transactionId);
|
|
||||||
} catch(NotServingRegionException e) {
|
|
||||||
LOG.info("Got not serving region durring abort. Ignoring.");
|
|
||||||
} catch (IOException e) {
|
|
||||||
checkFileSystem();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void commit(final byte[] regionName, final long transactionId)
|
|
||||||
throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
super.getRequestCount().incrementAndGet();
|
|
||||||
try {
|
|
||||||
getTransactionalRegion(regionName).commit(transactionId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
checkFileSystem();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int commitRequest(final byte[] regionName, final long transactionId)
|
|
||||||
throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
super.getRequestCount().incrementAndGet();
|
|
||||||
try {
|
|
||||||
return getTransactionalRegion(regionName).commitRequest(transactionId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
checkFileSystem();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean commitIfPossible(byte[] regionName, long transactionId)
|
|
||||||
throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
super.getRequestCount().incrementAndGet();
|
|
||||||
try {
|
|
||||||
return getTransactionalRegion(regionName).commitIfPossible(transactionId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
checkFileSystem();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long openScanner(final long transactionId, byte [] regionName, Scan scan)
|
|
||||||
throws IOException {
|
|
||||||
checkOpen();
|
|
||||||
NullPointerException npe = null;
|
|
||||||
if (regionName == null) {
|
|
||||||
npe = new NullPointerException("regionName is null");
|
|
||||||
} else if (scan == null) {
|
|
||||||
npe = new NullPointerException("scan is null");
|
|
||||||
}
|
|
||||||
if (npe != null) {
|
|
||||||
throw new IOException("Invalid arguments to openScanner", npe);
|
|
||||||
}
|
|
||||||
super.getRequestCount().incrementAndGet();
|
|
||||||
try {
|
|
||||||
TransactionalRegion r = getTransactionalRegion(regionName);
|
|
||||||
InternalScanner s = r.getScanner(transactionId, scan);
|
|
||||||
long scannerId = addScanner(s);
|
|
||||||
return scannerId;
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Error opening scanner (fsOk: " + this.fsOk + ")",
|
|
||||||
RemoteExceptionHandler.checkIOException(e));
|
|
||||||
checkFileSystem();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void beginTransaction(final long transactionId, final byte[] regionName)
|
|
||||||
throws IOException {
|
|
||||||
getTransactionalRegion(regionName).beginTransaction(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(long transactionId, byte[] regionName, Delete delete)
|
|
||||||
throws IOException {
|
|
||||||
getTransactionalRegion(regionName).delete(transactionId, delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result get(long transactionId, byte[] regionName, Get get)
|
|
||||||
throws IOException {
|
|
||||||
return getTransactionalRegion(regionName).get(transactionId, get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void put(long transactionId, byte[] regionName, Put put)
|
|
||||||
throws IOException {
|
|
||||||
getTransactionalRegion(regionName).put(transactionId, put);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public int put(long transactionId, byte[] regionName, Put[] puts)
|
|
||||||
throws IOException {
|
|
||||||
getTransactionalRegion(regionName).put(transactionId, puts);
|
|
||||||
return puts.length; // ??
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Copyright 2009 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<head />
|
|
||||||
<body bgcolor="white">
|
|
||||||
|
|
||||||
This package provides support for secondary indexing by maintaining a separate, "index", table for each index.
|
|
||||||
|
|
||||||
The IndexSpecification class provides the metadata for the index. This includes:
|
|
||||||
<li> the columns that contribute to the index key,
|
|
||||||
<li> additional columns to put in the index table (and are thus made available to filters on the index table),
|
|
||||||
<br> and
|
|
||||||
<li> an IndexKeyGenerator which constructs the index-row-key from the indexed column(s) and the original row.
|
|
||||||
|
|
||||||
IndexesSpecifications can be added to a table's metadata (HTableDescriptor) before the table is constructed.
|
|
||||||
Afterwards, updates and deletes to the original table will trigger the updates in the index, and
|
|
||||||
the indexes can be scanned using the API on IndexedTable. If you prefer not to use the Java API, you can
|
|
||||||
load IndexedTable.rb to create indexes from within the HBase shell.
|
|
||||||
|
|
||||||
For a simple example, look at the unit test in org.apache.hadoop.hbase.client.tableIndexed.
|
|
||||||
|
|
||||||
<p> To enable the indexing, modify hbase-site.xml to turn on the
|
|
||||||
IndexedRegionServer. This is done by setting
|
|
||||||
<i>hbase.regionserver.class</i> to
|
|
||||||
<i>org.apache.hadoop.hbase.ipc.IndexedRegionInterface</i> and
|
|
||||||
<i>hbase.regionserver.impl </i> to
|
|
||||||
<i>org.apache.hadoop.hbase.regionserver.tableindexed.IndexedRegionServer</i>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Copyright 2009 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<head />
|
|
||||||
<body bgcolor="white">
|
|
||||||
|
|
||||||
This package provides support for atomic transactions. Transactions can
|
|
||||||
span multiple regions. Transaction writes are applied when committing a
|
|
||||||
transaction. At commit time, the transaction is examined to see if it
|
|
||||||
can be applied while still maintaining atomicity. This is done by
|
|
||||||
looking for conflicts with the transactions that committed while the
|
|
||||||
current transaction was running. This technique is known as optimistic
|
|
||||||
concurrency control (OCC) because it relies on the assumption that
|
|
||||||
transactions will mostly not have conflicts with each other.
|
|
||||||
|
|
||||||
<p>
|
|
||||||
For more details on OCC, see the paper <i> On Optimistic Methods for Concurrency Control </i>
|
|
||||||
by Kung and Robinson available
|
|
||||||
<a href=http://www.seas.upenn.edu/~zives/cis650/papers/opt-cc.pdf> here </a>.
|
|
||||||
|
|
||||||
<p> To enable transactions, modify hbase-site.xml to turn on the
|
|
||||||
TransactionalRegionServer. This is done by setting
|
|
||||||
<i>hbase.regionserver.class</i> to
|
|
||||||
<i>org.apache.hadoop.hbase.ipc.TransactionalRegionInterface</i> and
|
|
||||||
<i>hbase.regionserver.impl </i> to
|
|
||||||
<i>org.apache.hadoop.hbase.regionserver.transactional.TransactionalRegionServer</i>
|
|
||||||
Additionally, to properly recover from the write-ahead-log, the transactional log
|
|
||||||
key class must be registered by setting <i>hbase.regionserver.hlog.keyclass</i>
|
|
||||||
to <i>org.apache.hadoop.hbase.regionserver.transactional.THLogKey</i>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The read set claimed by a transactional scanner is determined from the start and
|
|
||||||
end keys which the scanner is opened with.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3> Known Issues </h3>
|
|
||||||
|
|
||||||
Recovery in the face of hregion server failure
|
|
||||||
is not fully implemented. Thus, you cannot rely on the transactional
|
|
||||||
properties in the face of node failure.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,252 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.tableindexed;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.HBaseClusterTestCase;
|
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.PerformanceEvaluation;
|
|
||||||
import org.apache.hadoop.hbase.client.Delete;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
|
||||||
import org.apache.hadoop.hbase.client.ResultScanner;
|
|
||||||
import org.apache.hadoop.hbase.client.RowLock;
|
|
||||||
import org.apache.hadoop.hbase.client.Scan;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.tableindexed.IndexedRegionServer;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
public class TestIndexedTable extends HBaseClusterTestCase {
|
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(TestIndexedTable.class);
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "table1";
|
|
||||||
|
|
||||||
private static final byte[] FAMILY = Bytes.toBytes("family");
|
|
||||||
private static final byte[] QUAL_A = Bytes.toBytes("a");
|
|
||||||
private static final byte[] COL_A = Bytes.toBytes("family:a");
|
|
||||||
private static final String INDEX_COL_A = "A";
|
|
||||||
|
|
||||||
private static final int NUM_ROWS = 10;
|
|
||||||
private static final int MAX_VAL = 10000;
|
|
||||||
|
|
||||||
private IndexedTableAdmin admin;
|
|
||||||
private IndexedTable table;
|
|
||||||
private Random random = new Random();
|
|
||||||
private HTableDescriptor desc;
|
|
||||||
|
|
||||||
/** constructor */
|
|
||||||
public TestIndexedTable() {
|
|
||||||
conf
|
|
||||||
.set(HConstants.REGION_SERVER_IMPL, IndexedRegionServer.class.getName());
|
|
||||||
conf.setInt("hbase.master.info.port", -1);
|
|
||||||
conf.setInt("hbase.regionserver.info.port", -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
desc = new HTableDescriptor(TABLE_NAME);
|
|
||||||
desc.addFamily(new HColumnDescriptor(FAMILY));
|
|
||||||
|
|
||||||
IndexedTableDescriptor indexDesc = new IndexedTableDescriptor(desc);
|
|
||||||
// Create a new index that does lexicographic ordering on COL_A
|
|
||||||
IndexSpecification colAIndex = new IndexSpecification(INDEX_COL_A, COL_A);
|
|
||||||
indexDesc.addIndex(colAIndex);
|
|
||||||
|
|
||||||
admin = new IndexedTableAdmin(conf);
|
|
||||||
admin.createIndexedTable(indexDesc);
|
|
||||||
table = new IndexedTable(conf, desc.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeInitalRows() throws IOException {
|
|
||||||
for (int i = 0; i < NUM_ROWS; i++) {
|
|
||||||
Put update = new Put(PerformanceEvaluation.format(i));
|
|
||||||
byte[] valueA = PerformanceEvaluation.format(random.nextInt(MAX_VAL));
|
|
||||||
update.add(FAMILY, QUAL_A, valueA);
|
|
||||||
table.put(update);
|
|
||||||
LOG.info("Inserted row [" + Bytes.toString(update.getRow()) + "] val: ["
|
|
||||||
+ Bytes.toString(valueA) + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void testInitialWrites() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
assertRowsInOrder(NUM_ROWS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertRowsInOrder(int numRowsExpected)
|
|
||||||
throws IndexNotFoundException, IOException {
|
|
||||||
ResultScanner scanner = table.getIndexedScanner(INDEX_COL_A, null, null,
|
|
||||||
null, null, null);
|
|
||||||
int numRows = 0;
|
|
||||||
byte[] lastColA = null;
|
|
||||||
for (Result rowResult : scanner) {
|
|
||||||
byte[] colA = rowResult.getValue(FAMILY, QUAL_A);
|
|
||||||
LOG.info("index scan : row [" + Bytes.toString(rowResult.getRow())
|
|
||||||
+ "] value [" + Bytes.toString(colA) + "]");
|
|
||||||
if (lastColA != null) {
|
|
||||||
Assert.assertTrue(Bytes.compareTo(lastColA, colA) <= 0);
|
|
||||||
}
|
|
||||||
lastColA = colA;
|
|
||||||
numRows++;
|
|
||||||
}
|
|
||||||
scanner.close();
|
|
||||||
Assert.assertEquals(numRowsExpected, numRows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertRowUpdated(int updatedRow, int expectedRowValue)
|
|
||||||
throws IndexNotFoundException, IOException {
|
|
||||||
ResultScanner scanner = table.getIndexedScanner(INDEX_COL_A, null, null,
|
|
||||||
null, null, null);
|
|
||||||
byte[] persistedRowValue = null;
|
|
||||||
for (Result rowResult : scanner) {
|
|
||||||
byte[] row = rowResult.getRow();
|
|
||||||
byte[] value = rowResult.getValue(FAMILY, QUAL_A);
|
|
||||||
if (Bytes.toString(row).equals(Bytes.toString(PerformanceEvaluation.format(updatedRow)))) {
|
|
||||||
persistedRowValue = value;
|
|
||||||
LOG.info("update found: row [" + Bytes.toString(row)
|
|
||||||
+ "] value [" + Bytes.toString(value) + "]");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LOG.info("updated index scan : row [" + Bytes.toString(row)
|
|
||||||
+ "] value [" + Bytes.toString(value) + "]");
|
|
||||||
}
|
|
||||||
scanner.close();
|
|
||||||
|
|
||||||
Assert.assertEquals(Bytes.toString(PerformanceEvaluation.format(expectedRowValue)),
|
|
||||||
Bytes.toString(persistedRowValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertRowDeleted(int numRowsExpected)
|
|
||||||
throws IndexNotFoundException, IOException {
|
|
||||||
// Check the size of the primary table
|
|
||||||
ResultScanner scanner = table.getScanner(new Scan());
|
|
||||||
int numRows = 0;
|
|
||||||
for (Result rowResult : scanner) {
|
|
||||||
byte[] colA = rowResult.getValue(FAMILY, QUAL_A);
|
|
||||||
LOG.info("primary scan : row [" + Bytes.toString(rowResult.getRow())
|
|
||||||
+ "] value [" + Bytes.toString(colA) + "]");
|
|
||||||
numRows++;
|
|
||||||
}
|
|
||||||
scanner.close();
|
|
||||||
Assert.assertEquals(numRowsExpected, numRows);
|
|
||||||
|
|
||||||
// Check the size of the index tables
|
|
||||||
assertRowsInOrder(numRowsExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRow(int row, int newValue) throws IOException {
|
|
||||||
Put update = new Put(PerformanceEvaluation.format(row));
|
|
||||||
byte[] valueA = PerformanceEvaluation.format(newValue);
|
|
||||||
update.add(FAMILY, QUAL_A, valueA);
|
|
||||||
table.put(update);
|
|
||||||
LOG.info("Updated row [" + Bytes.toString(update.getRow()) + "] val: ["
|
|
||||||
+ Bytes.toString(valueA) + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateLockedRow(int row, int newValue) throws IOException {
|
|
||||||
RowLock lock = table.lockRow(PerformanceEvaluation.format(row));
|
|
||||||
Put update = new Put(PerformanceEvaluation.format(row), lock);
|
|
||||||
byte[] valueA = PerformanceEvaluation.format(newValue);
|
|
||||||
update.add(FAMILY, QUAL_A, valueA);
|
|
||||||
LOG.info("Updating row [" + Bytes.toString(update.getRow()) + "] val: ["
|
|
||||||
+ Bytes.toString(valueA) + "]");
|
|
||||||
table.put(update);
|
|
||||||
LOG.info("Updated row [" + Bytes.toString(update.getRow()) + "] val: ["
|
|
||||||
+ Bytes.toString(valueA) + "]");
|
|
||||||
table.unlockRow(lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateLockedRowNoAutoFlush(int row, int newValue) throws IOException {
|
|
||||||
table.flushCommits();
|
|
||||||
table.setAutoFlush(false);
|
|
||||||
RowLock lock = table.lockRow(PerformanceEvaluation.format(row));
|
|
||||||
Put update = new Put(PerformanceEvaluation.format(row), lock);
|
|
||||||
byte[] valueA = PerformanceEvaluation.format(newValue);
|
|
||||||
update.add(FAMILY, QUAL_A, valueA);
|
|
||||||
LOG.info("Updating row [" + Bytes.toString(update.getRow()) + "] val: ["
|
|
||||||
+ Bytes.toString(valueA) + "]");
|
|
||||||
table.put(update);
|
|
||||||
LOG.info("Updated row [" + Bytes.toString(update.getRow()) + "] val: ["
|
|
||||||
+ Bytes.toString(valueA) + "]");
|
|
||||||
table.flushCommits();
|
|
||||||
table.close();
|
|
||||||
table = new IndexedTable(conf, desc.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testMultipleWrites() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
writeInitalRows(); // Update the rows.
|
|
||||||
assertRowsInOrder(NUM_ROWS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDelete() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
// Delete the first row;
|
|
||||||
table.delete(new Delete(PerformanceEvaluation.format(0)));
|
|
||||||
|
|
||||||
assertRowsInOrder(NUM_ROWS - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRowUpdate() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
int row = NUM_ROWS - 2;
|
|
||||||
int value = MAX_VAL + 111;
|
|
||||||
updateRow(row, value);
|
|
||||||
assertRowUpdated(row, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLockedRowUpdate() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
int row = NUM_ROWS - 2;
|
|
||||||
int value = MAX_VAL + 111;
|
|
||||||
updateLockedRow(row, value);
|
|
||||||
assertRowUpdated(row, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLockedRowUpdateNoAutoFlush() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
int row = NUM_ROWS - 4;
|
|
||||||
int value = MAX_VAL + 2222;
|
|
||||||
updateLockedRowNoAutoFlush(row, value);
|
|
||||||
assertRowUpdated(row, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLockedRowDelete() throws IOException {
|
|
||||||
writeInitalRows();
|
|
||||||
// Delete the first row;
|
|
||||||
byte[] row = PerformanceEvaluation.format(0);
|
|
||||||
RowLock lock = table.lockRow(row);
|
|
||||||
table.delete(new Delete(row, HConstants.LATEST_TIMESTAMP, lock));
|
|
||||||
table.unlockRow(lock);
|
|
||||||
|
|
||||||
assertRowDeleted(NUM_ROWS - 1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,417 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.hbase.HBaseClusterTestCase;
|
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.transactional.TransactionalRegionServer;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stress Test the transaction functionality. This requires to run an
|
|
||||||
* {@link TransactionalRegionServer}. We run many threads doing reads/writes
|
|
||||||
* which may conflict with each other. We have two types of transactions, those
|
|
||||||
* which operate on rows of a single table, and those which operate on rows
|
|
||||||
* across multiple tables. Each transaction type has a modification operation
|
|
||||||
* which changes two values while maintaining the sum. Also each transaction
|
|
||||||
* type has a consistency-check operation which sums all rows and verifies that
|
|
||||||
* the sum is as expected.
|
|
||||||
*/
|
|
||||||
public class StressTestTransactions extends HBaseClusterTestCase {
|
|
||||||
protected static final Log LOG = LogFactory
|
|
||||||
.getLog(StressTestTransactions.class);
|
|
||||||
|
|
||||||
private static final int NUM_TABLES = 3;
|
|
||||||
private static final int NUM_ST_ROWS = 3;
|
|
||||||
private static final int NUM_MT_ROWS = 3;
|
|
||||||
private static final int NUM_TRANSACTIONS_PER_THREAD = 100;
|
|
||||||
private static final int NUM_SINGLE_TABLE_THREADS = 6;
|
|
||||||
private static final int NUM_MULTI_TABLE_THREADS = 6;
|
|
||||||
private static final int PRE_COMMIT_SLEEP = 10;
|
|
||||||
protected static final Random RAND = new Random();
|
|
||||||
|
|
||||||
private static final byte[] FAMILY_COLON = Bytes.toBytes("family:");
|
|
||||||
private static final byte[] FAMILY = Bytes.toBytes("family");
|
|
||||||
private static final byte[] QUAL_A = Bytes.toBytes("a");
|
|
||||||
static final byte[] COL = Bytes.toBytes("family:a");
|
|
||||||
|
|
||||||
private HBaseAdmin admin;
|
|
||||||
protected TransactionalTable[] tables;
|
|
||||||
protected TransactionManager transactionManager;
|
|
||||||
|
|
||||||
/** constructor */
|
|
||||||
public StressTestTransactions() {
|
|
||||||
conf.set(HConstants.REGION_SERVER_CLASS, TransactionalRegionInterface.class
|
|
||||||
.getName());
|
|
||||||
conf.set(HConstants.REGION_SERVER_IMPL, TransactionalRegionServer.class
|
|
||||||
.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
tables = new TransactionalTable[NUM_TABLES];
|
|
||||||
|
|
||||||
for (int i = 0; i < tables.length; i++) {
|
|
||||||
HTableDescriptor desc = new HTableDescriptor(makeTableName(i));
|
|
||||||
desc.addFamily(new HColumnDescriptor(FAMILY_COLON));
|
|
||||||
admin = new HBaseAdmin(conf);
|
|
||||||
admin.createTable(desc);
|
|
||||||
tables[i] = new TransactionalTable(conf, desc.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionManager = new TransactionManager(conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String makeTableName(final int i) {
|
|
||||||
return "table" + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeInitalValues() throws IOException {
|
|
||||||
for (TransactionalTable table : tables) {
|
|
||||||
for (int i = 0; i < NUM_ST_ROWS; i++) {
|
|
||||||
table.put(new Put(makeSTRow(i)).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(SingleTableTransactionThread.INITIAL_VALUE)));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < NUM_MT_ROWS; i++) {
|
|
||||||
table.put(new Put(makeMTRow(i)).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(MultiTableTransactionThread.INITIAL_VALUE)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] makeSTRow(final int i) {
|
|
||||||
return Bytes.toBytes("st" + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] makeMTRow(final int i) {
|
|
||||||
return Bytes.toBytes("mt" + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nextThreadNum = 1;
|
|
||||||
protected static final AtomicBoolean stopRequest = new AtomicBoolean(false);
|
|
||||||
static final AtomicBoolean consistencyFailure = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
// Thread which runs transactions
|
|
||||||
abstract class TransactionThread extends Thread {
|
|
||||||
private int numRuns = 0;
|
|
||||||
private int numAborts = 0;
|
|
||||||
private int numUnknowns = 0;
|
|
||||||
|
|
||||||
public TransactionThread(final String namePrefix) {
|
|
||||||
super.setName(namePrefix + "transaction " + nextThreadNum++);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (int i = 0; i < NUM_TRANSACTIONS_PER_THREAD; i++) {
|
|
||||||
if (stopRequest.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
numRuns++;
|
|
||||||
transaction();
|
|
||||||
} catch (UnknownTransactionException e) {
|
|
||||||
numUnknowns++;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (CommitUnsuccessfulException e) {
|
|
||||||
numAborts++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void transaction() throws IOException,
|
|
||||||
CommitUnsuccessfulException;
|
|
||||||
|
|
||||||
public int getNumAborts() {
|
|
||||||
return numAborts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumUnknowns() {
|
|
||||||
return numUnknowns;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void preCommitSleep() {
|
|
||||||
try {
|
|
||||||
Thread.sleep(PRE_COMMIT_SLEEP);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void consistencyFailure() {
|
|
||||||
LOG.fatal("Consistency failure");
|
|
||||||
stopRequest.set(true);
|
|
||||||
consistencyFailure.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the numRuns.
|
|
||||||
*
|
|
||||||
* @return Return the numRuns.
|
|
||||||
*/
|
|
||||||
public int getNumRuns() {
|
|
||||||
return numRuns;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atomically change the value of two rows rows while maintaining the sum.
|
|
||||||
// This should preserve the global sum of the rows, which is also checked
|
|
||||||
// with a transaction.
|
|
||||||
private class SingleTableTransactionThread extends TransactionThread {
|
|
||||||
private static final int INITIAL_VALUE = 10;
|
|
||||||
public static final int TOTAL_SUM = INITIAL_VALUE * NUM_ST_ROWS;
|
|
||||||
private static final int MAX_TRANSFER_AMT = 100;
|
|
||||||
|
|
||||||
private TransactionalTable table;
|
|
||||||
boolean doCheck = false;
|
|
||||||
|
|
||||||
public SingleTableTransactionThread() {
|
|
||||||
super("single table ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void transaction() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
if (doCheck) {
|
|
||||||
checkTotalSum();
|
|
||||||
} else {
|
|
||||||
doSingleRowChange();
|
|
||||||
}
|
|
||||||
doCheck = !doCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doSingleRowChange() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
table = tables[RAND.nextInt(NUM_TABLES)];
|
|
||||||
int transferAmount = RAND.nextInt(MAX_TRANSFER_AMT * 2)
|
|
||||||
- MAX_TRANSFER_AMT;
|
|
||||||
int row1Index = RAND.nextInt(NUM_ST_ROWS);
|
|
||||||
int row2Index;
|
|
||||||
do {
|
|
||||||
row2Index = RAND.nextInt(NUM_ST_ROWS);
|
|
||||||
} while (row2Index == row1Index);
|
|
||||||
byte[] row1 = makeSTRow(row1Index);
|
|
||||||
byte[] row2 = makeSTRow(row2Index);
|
|
||||||
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
int row1Amount = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(row1).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
int row2Amount = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(row2).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
|
|
||||||
row1Amount -= transferAmount;
|
|
||||||
row2Amount += transferAmount;
|
|
||||||
|
|
||||||
table.put(transactionState, new Put(row1).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(row1Amount)));
|
|
||||||
table.put(transactionState, new Put(row2).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(row2Amount)));
|
|
||||||
|
|
||||||
super.preCommitSleep();
|
|
||||||
|
|
||||||
transactionManager.tryCommit(transactionState);
|
|
||||||
LOG.debug("Commited");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the table we last mutated
|
|
||||||
private void checkTotalSum() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
int totalSum = 0;
|
|
||||||
for (int i = 0; i < NUM_ST_ROWS; i++) {
|
|
||||||
totalSum += Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(makeSTRow(i)).addColumn(FAMILY, QUAL_A)).getValue(FAMILY,
|
|
||||||
QUAL_A));
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionManager.tryCommit(transactionState);
|
|
||||||
if (TOTAL_SUM != totalSum) {
|
|
||||||
super.consistencyFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similar to SingleTable, but this time we maintain consistency across tables
|
|
||||||
// rather than rows
|
|
||||||
private class MultiTableTransactionThread extends TransactionThread {
|
|
||||||
private static final int INITIAL_VALUE = 1000;
|
|
||||||
public static final int TOTAL_SUM = INITIAL_VALUE * NUM_TABLES;
|
|
||||||
private static final int MAX_TRANSFER_AMT = 100;
|
|
||||||
|
|
||||||
private byte[] row;
|
|
||||||
boolean doCheck = false;
|
|
||||||
|
|
||||||
public MultiTableTransactionThread() {
|
|
||||||
super("multi table");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void transaction() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
if (doCheck) {
|
|
||||||
checkTotalSum();
|
|
||||||
} else {
|
|
||||||
doSingleRowChange();
|
|
||||||
}
|
|
||||||
doCheck = !doCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doSingleRowChange() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
row = makeMTRow(RAND.nextInt(NUM_MT_ROWS));
|
|
||||||
int transferAmount = RAND.nextInt(MAX_TRANSFER_AMT * 2)
|
|
||||||
- MAX_TRANSFER_AMT;
|
|
||||||
int table1Index = RAND.nextInt(tables.length);
|
|
||||||
int table2Index;
|
|
||||||
do {
|
|
||||||
table2Index = RAND.nextInt(tables.length);
|
|
||||||
} while (table2Index == table1Index);
|
|
||||||
|
|
||||||
TransactionalTable table1 = tables[table1Index];
|
|
||||||
TransactionalTable table2 = tables[table2Index];
|
|
||||||
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
int table1Amount = Bytes.toInt(table1.get(transactionState,
|
|
||||||
new Get(row).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
int table2Amount = Bytes.toInt(table2.get(transactionState,
|
|
||||||
new Get(row).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
|
|
||||||
table1Amount -= transferAmount;
|
|
||||||
table2Amount += transferAmount;
|
|
||||||
|
|
||||||
table1.put(transactionState, new Put(row).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(table1Amount)));
|
|
||||||
table2.put(transactionState, new Put(row).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(table2Amount)));
|
|
||||||
|
|
||||||
super.preCommitSleep();
|
|
||||||
|
|
||||||
transactionManager.tryCommit(transactionState);
|
|
||||||
|
|
||||||
LOG.trace(Bytes.toString(table1.getTableName()) + ": " + table1Amount);
|
|
||||||
LOG.trace(Bytes.toString(table2.getTableName()) + ": " + table2Amount);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTotalSum() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
int totalSum = 0;
|
|
||||||
int[] amounts = new int[tables.length];
|
|
||||||
for (int i = 0; i < tables.length; i++) {
|
|
||||||
int amount = Bytes.toInt(tables[i].get(transactionState,
|
|
||||||
new Get(row).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
amounts[i] = amount;
|
|
||||||
totalSum += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionManager.tryCommit(transactionState);
|
|
||||||
|
|
||||||
for (int i = 0; i < tables.length; i++) {
|
|
||||||
LOG.trace(Bytes.toString(tables[i].getTableName()) + ": " + amounts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TOTAL_SUM != totalSum) {
|
|
||||||
super.consistencyFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStressTransactions() throws IOException, InterruptedException {
|
|
||||||
writeInitalValues();
|
|
||||||
|
|
||||||
List<TransactionThread> transactionThreads = new LinkedList<TransactionThread>();
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_SINGLE_TABLE_THREADS; i++) {
|
|
||||||
TransactionThread transactionThread = new SingleTableTransactionThread();
|
|
||||||
transactionThread.start();
|
|
||||||
transactionThreads.add(transactionThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_MULTI_TABLE_THREADS; i++) {
|
|
||||||
TransactionThread transactionThread = new MultiTableTransactionThread();
|
|
||||||
transactionThread.start();
|
|
||||||
transactionThreads.add(transactionThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TransactionThread transactionThread : transactionThreads) {
|
|
||||||
transactionThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TransactionThread transactionThread : transactionThreads) {
|
|
||||||
LOG.info(transactionThread.getName() + " done with "
|
|
||||||
+ transactionThread.getNumAborts() + " aborts, and "
|
|
||||||
+ transactionThread.getNumUnknowns() + " unknown transactions of "
|
|
||||||
+ transactionThread.getNumRuns());
|
|
||||||
}
|
|
||||||
|
|
||||||
doFinalConsistencyChecks();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doFinalConsistencyChecks() throws IOException {
|
|
||||||
|
|
||||||
int[] mtSums = new int[NUM_MT_ROWS];
|
|
||||||
for (int i = 0; i < mtSums.length; i++) {
|
|
||||||
mtSums[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TransactionalTable table : tables) {
|
|
||||||
int thisTableSum = 0;
|
|
||||||
for (int i = 0; i < NUM_ST_ROWS; i++) {
|
|
||||||
byte[] row = makeSTRow(i);
|
|
||||||
thisTableSum += Bytes.toInt(table.get(new Get(row).addColumn(FAMILY, QUAL_A))
|
|
||||||
.getValue(FAMILY, QUAL_A));
|
|
||||||
}
|
|
||||||
Assert.assertEquals(SingleTableTransactionThread.TOTAL_SUM, thisTableSum);
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_MT_ROWS; i++) {
|
|
||||||
byte[] row = makeMTRow(i);
|
|
||||||
mtSums[i] += Bytes.toInt(table.get(new Get(row).addColumn(FAMILY, QUAL_A))
|
|
||||||
.getValue(FAMILY, QUAL_A));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int mtSum : mtSums) {
|
|
||||||
Assert.assertEquals(MultiTableTransactionThread.TOTAL_SUM, mtSum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,254 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.client.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.HBaseClusterTestCase;
|
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
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.ipc.TransactionalRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.transactional.TransactionalRegionServer;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the transaction functionality. This requires to run an
|
|
||||||
* {@link TransactionalRegionServer}.
|
|
||||||
*/
|
|
||||||
public class TestTransactions extends HBaseClusterTestCase {
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "table1";
|
|
||||||
|
|
||||||
private static final byte[] FAMILY = Bytes.toBytes("family");
|
|
||||||
private static final byte[] QUAL_A = Bytes.toBytes("a");
|
|
||||||
|
|
||||||
private static final byte[] ROW1 = Bytes.toBytes("row1");
|
|
||||||
private static final byte[] ROW2 = Bytes.toBytes("row2");
|
|
||||||
private static final byte[] ROW3 = Bytes.toBytes("row3");
|
|
||||||
|
|
||||||
private HBaseAdmin admin;
|
|
||||||
private TransactionalTable table;
|
|
||||||
private TransactionManager transactionManager;
|
|
||||||
|
|
||||||
/** constructor */
|
|
||||||
public TestTransactions() {
|
|
||||||
conf.set(HConstants.REGION_SERVER_CLASS, TransactionalRegionInterface.class
|
|
||||||
.getName());
|
|
||||||
conf.set(HConstants.REGION_SERVER_IMPL, TransactionalRegionServer.class
|
|
||||||
.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
HTableDescriptor desc = new HTableDescriptor(TABLE_NAME);
|
|
||||||
desc.addFamily(new HColumnDescriptor(FAMILY));
|
|
||||||
admin = new HBaseAdmin(conf);
|
|
||||||
admin.createTable(desc);
|
|
||||||
table = new TransactionalTable(conf, desc.getName());
|
|
||||||
|
|
||||||
transactionManager = new TransactionManager(conf);
|
|
||||||
writeInitalRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeInitalRow() throws IOException {
|
|
||||||
table.put(new Put(ROW1).add(FAMILY, QUAL_A, Bytes.toBytes(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSimpleTransaction() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
TransactionState transactionState = makeTransaction1();
|
|
||||||
transactionManager.tryCommit(transactionState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testTwoTransactionsWithoutConflict() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
TransactionState transactionState1 = makeTransaction1();
|
|
||||||
TransactionState transactionState2 = makeTransaction2();
|
|
||||||
|
|
||||||
transactionManager.tryCommit(transactionState1);
|
|
||||||
transactionManager.tryCommit(transactionState2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testTwoTransactionsWithConflict() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
TransactionState transactionState1 = makeTransaction1();
|
|
||||||
TransactionState transactionState2 = makeTransaction2();
|
|
||||||
|
|
||||||
transactionManager.tryCommit(transactionState2);
|
|
||||||
|
|
||||||
try {
|
|
||||||
transactionManager.tryCommit(transactionState1);
|
|
||||||
fail();
|
|
||||||
} catch (CommitUnsuccessfulException e) {
|
|
||||||
// Good
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetAfterPut() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
int originalValue = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(ROW1).addColumn(FAMILY, QUAL_A)).value());
|
|
||||||
int newValue = originalValue + 1;
|
|
||||||
|
|
||||||
table.put(transactionState, new Put(ROW1).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(newValue)));
|
|
||||||
|
|
||||||
Result row1_A = table.get(transactionState, new Get(ROW1).addColumn(FAMILY,
|
|
||||||
QUAL_A));
|
|
||||||
Assert.assertEquals(newValue, Bytes.toInt(row1_A.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetAfterPutPut() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
int originalValue = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(ROW1).addColumn(FAMILY, QUAL_A)).value());
|
|
||||||
int newValue = originalValue + 1;
|
|
||||||
|
|
||||||
table.put(transactionState, new Put(ROW1).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(newValue)));
|
|
||||||
|
|
||||||
newValue = newValue + 1;
|
|
||||||
|
|
||||||
table.put(transactionState, new Put(ROW1).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(newValue)));
|
|
||||||
|
|
||||||
Result row1_A = table.get(transactionState, new Get(ROW1).addColumn(FAMILY, QUAL_A));
|
|
||||||
Assert.assertEquals(newValue, Bytes.toInt(row1_A.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testScanAfterUpdatePut() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
int originalValue = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(ROW1).addColumn(FAMILY, QUAL_A)).value());
|
|
||||||
int newValue = originalValue + 1;
|
|
||||||
table.put(transactionState, new Put(ROW1).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(newValue)));
|
|
||||||
|
|
||||||
ResultScanner scanner = table.getScanner(transactionState, new Scan()
|
|
||||||
.addFamily(FAMILY));
|
|
||||||
|
|
||||||
Result result = scanner.next();
|
|
||||||
Assert.assertNotNull(result);
|
|
||||||
|
|
||||||
Assert.assertEquals(Bytes.toString(ROW1), Bytes.toString(result.getRow()));
|
|
||||||
Assert.assertEquals(newValue, Bytes.toInt(result.value()));
|
|
||||||
|
|
||||||
result = scanner.next();
|
|
||||||
Assert.assertNull(result);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testScanAfterNewPut() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
int row2Value = 199;
|
|
||||||
table.put(transactionState, new Put(ROW2).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(row2Value)));
|
|
||||||
|
|
||||||
ResultScanner scanner = table.getScanner(transactionState, new Scan()
|
|
||||||
.addFamily(FAMILY));
|
|
||||||
|
|
||||||
Result result = scanner.next();
|
|
||||||
Assert.assertNotNull(result);
|
|
||||||
Assert.assertEquals(Bytes.toString(ROW1), Bytes.toString(result.getRow()));
|
|
||||||
|
|
||||||
result = scanner.next();
|
|
||||||
Assert.assertNotNull(result);
|
|
||||||
Assert.assertEquals(Bytes.toString(ROW2), Bytes.toString(result.getRow()));
|
|
||||||
Assert.assertEquals(row2Value, Bytes.toInt(result.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPutPutScan() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
int row2Value = 199;
|
|
||||||
table.put(transactionState, new Put(ROW2).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(row2Value)));
|
|
||||||
|
|
||||||
row2Value = 299;
|
|
||||||
table.put(transactionState, new Put(ROW2).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(row2Value)));
|
|
||||||
|
|
||||||
ResultScanner scanner = table.getScanner(transactionState, new Scan()
|
|
||||||
.addFamily(FAMILY));
|
|
||||||
|
|
||||||
Result result = scanner.next();
|
|
||||||
Assert.assertNotNull(result);
|
|
||||||
Assert.assertEquals(Bytes.toString(ROW1), Bytes.toString(result.getRow()));
|
|
||||||
|
|
||||||
result = scanner.next();
|
|
||||||
Assert.assertNotNull(result);
|
|
||||||
Assert.assertEquals(Bytes.toString(ROW2), Bytes.toString(result.getRow()));
|
|
||||||
Assert.assertEquals(row2Value, Bytes.toInt(result.value()));
|
|
||||||
|
|
||||||
// TODO commit and verifty that we see second put.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPutPutScanOverAndOver() throws IOException {
|
|
||||||
// Do this test many times to try and hit two puts in the same millisecond
|
|
||||||
for (int i=0 ; i < 100; i++) {
|
|
||||||
testPutPutScan();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from ROW1,COL_A and put it in ROW2_COLA and ROW3_COLA
|
|
||||||
private TransactionState makeTransaction1() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
Result row1_A = table.get(transactionState, new Get(ROW1).addColumn(FAMILY,
|
|
||||||
QUAL_A));
|
|
||||||
|
|
||||||
table.put(transactionState, new Put(ROW2).add(FAMILY, QUAL_A, row1_A
|
|
||||||
.getValue(FAMILY, QUAL_A)));
|
|
||||||
table.put(transactionState, new Put(ROW3).add(FAMILY, QUAL_A, row1_A
|
|
||||||
.getValue(FAMILY, QUAL_A)));
|
|
||||||
|
|
||||||
return transactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read ROW1,COL_A, increment its (integer) value, write back
|
|
||||||
private TransactionState makeTransaction2() throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
Result row1_A = table.get(transactionState, new Get(ROW1).addColumn(FAMILY,
|
|
||||||
QUAL_A));
|
|
||||||
|
|
||||||
int value = Bytes.toInt(row1_A.getValue(FAMILY, QUAL_A));
|
|
||||||
|
|
||||||
table.put(transactionState, new Put(ROW1).add(FAMILY, QUAL_A, Bytes
|
|
||||||
.toBytes(value + 1)));
|
|
||||||
|
|
||||||
return transactionState;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,260 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.hbase.HBaseTestCase;
|
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
|
||||||
import org.apache.hadoop.hbase.HRegionInfo;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.KeyValue;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
import org.apache.hadoop.hdfs.MiniDFSCluster;
|
|
||||||
|
|
||||||
/** JUnit test case for HLog */
|
|
||||||
public class TestTHLog extends HBaseTestCase implements
|
|
||||||
HConstants {
|
|
||||||
private Path dir;
|
|
||||||
private Path oldLogdir;
|
|
||||||
private MiniDFSCluster cluster;
|
|
||||||
|
|
||||||
final byte[] tableName = Bytes.toBytes("tablename");
|
|
||||||
final HTableDescriptor tableDesc = new HTableDescriptor(tableName);
|
|
||||||
final HRegionInfo regionInfo = new HRegionInfo(tableDesc,
|
|
||||||
HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
|
|
||||||
final byte[] row1 = Bytes.toBytes("row1");
|
|
||||||
final byte[] val1 = Bytes.toBytes("val1");
|
|
||||||
final byte[] row2 = Bytes.toBytes("row2");
|
|
||||||
final byte[] val2 = Bytes.toBytes("val2");
|
|
||||||
final byte[] row3 = Bytes.toBytes("row3");
|
|
||||||
final byte[] val3 = Bytes.toBytes("val3");
|
|
||||||
final byte[] family = Bytes.toBytes("family");
|
|
||||||
final byte[] column = Bytes.toBytes("a");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
cluster = new MiniDFSCluster(conf, 2, true, (String[]) null);
|
|
||||||
// Set the hbase.rootdir to be the home directory in mini dfs.
|
|
||||||
this.conf.set(HConstants.HBASE_DIR, this.cluster.getFileSystem()
|
|
||||||
.getHomeDirectory().toString());
|
|
||||||
this.conf.set("hbase.regionserver.hlog.keyclass",
|
|
||||||
THLogKey.class.getCanonicalName());
|
|
||||||
super.setUp();
|
|
||||||
this.dir = new Path("/hbase", getName());
|
|
||||||
this.oldLogdir = new Path("/hbase", getName()+"_old");
|
|
||||||
|
|
||||||
if (fs.exists(dir)) {
|
|
||||||
fs.delete(dir, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
if (this.fs.exists(this.dir)) {
|
|
||||||
this.fs.delete(this.dir, true);
|
|
||||||
}
|
|
||||||
shutdownDfs(cluster);
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void testSingleCommit() throws IOException {
|
|
||||||
|
|
||||||
THLog log = new THLog(fs, dir, oldLogdir, this.conf, null);
|
|
||||||
THLogRecoveryManager logRecoveryMangaer = new THLogRecoveryManager(fs,
|
|
||||||
regionInfo, conf);
|
|
||||||
|
|
||||||
// Write columns named 1, 2, 3, etc. and then values of single byte
|
|
||||||
// 1, 2, 3...
|
|
||||||
long transactionId = 1;
|
|
||||||
log.writeUpdateToLog(regionInfo, transactionId, new Put(row1).add(family,
|
|
||||||
column, val1));
|
|
||||||
log.writeUpdateToLog(regionInfo, transactionId, new Put(row2).add(family,
|
|
||||||
column, val2));
|
|
||||||
log.writeUpdateToLog(regionInfo, transactionId, new Put(row3).add(family,
|
|
||||||
column, val3));
|
|
||||||
|
|
||||||
log.writeCommitToLog(regionInfo, transactionId);
|
|
||||||
|
|
||||||
// log.completeCacheFlush(regionName, tableName, logSeqId);
|
|
||||||
|
|
||||||
log.close();
|
|
||||||
Path filename = log.computeFilename(log.getFilenum());
|
|
||||||
|
|
||||||
Map<Long, List<WALEdit>> commits = logRecoveryMangaer.getCommitsFromLog(
|
|
||||||
filename, -1, null);
|
|
||||||
|
|
||||||
assertNull(commits);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void testSingleAbort() throws IOException {
|
|
||||||
|
|
||||||
THLog log = new THLog(fs, dir, oldLogdir, this.conf, null);
|
|
||||||
THLogRecoveryManager logRecoveryMangaer = new THLogRecoveryManager(fs,
|
|
||||||
regionInfo, conf);
|
|
||||||
|
|
||||||
long transactionId = 1;
|
|
||||||
log.writeUpdateToLog(regionInfo, transactionId, new Put(row1).add(family,
|
|
||||||
column, val1));
|
|
||||||
log.writeUpdateToLog(regionInfo, transactionId, new Put(row2).add(family,
|
|
||||||
column, val2));
|
|
||||||
log.writeUpdateToLog(regionInfo, transactionId, new Put(row3).add(family,
|
|
||||||
column, val3));
|
|
||||||
|
|
||||||
log.writeAbortToLog(regionInfo, transactionId);
|
|
||||||
// log.completeCacheFlush(regionName, tableName, logSeqId);
|
|
||||||
|
|
||||||
log.close();
|
|
||||||
Path filename = log.computeFilename(log.getFilenum());
|
|
||||||
|
|
||||||
Map<Long, List<WALEdit>> commits = logRecoveryMangaer.getCommitsFromLog(
|
|
||||||
filename, -1, null);
|
|
||||||
|
|
||||||
assertNull(commits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void testInterlievedCommits() throws IOException {
|
|
||||||
|
|
||||||
THLog log = new THLog(fs, dir, oldLogdir, this.conf, null);
|
|
||||||
THLogRecoveryManager logMangaer = new THLogRecoveryManager(fs, regionInfo,
|
|
||||||
conf);
|
|
||||||
|
|
||||||
long transaction1Id = 1;
|
|
||||||
long transaction2Id = 2;
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction1Id, new Put(row1).add(family,
|
|
||||||
column, val1));
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction2Id, new Put(row2).add(family,
|
|
||||||
column, val2));
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction1Id, new Put(row3).add(family,
|
|
||||||
column, val3));
|
|
||||||
|
|
||||||
log.writeCommitToLog(regionInfo, transaction1Id);
|
|
||||||
log.writeCommitToLog(regionInfo, transaction2Id);
|
|
||||||
|
|
||||||
// log.completeCacheFlush(regionName, tableName, logSeqId);
|
|
||||||
|
|
||||||
log.close();
|
|
||||||
Path filename = log.computeFilename(log.getFilenum());
|
|
||||||
|
|
||||||
Map<Long, List<WALEdit>> commits = logMangaer.getCommitsFromLog(filename,
|
|
||||||
-1, null);
|
|
||||||
|
|
||||||
assertNull(commits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void testInterlievedAbortCommit() throws IOException {
|
|
||||||
|
|
||||||
THLog log = new THLog(fs, dir, oldLogdir, this.conf, null);
|
|
||||||
THLogRecoveryManager logMangaer = new THLogRecoveryManager(fs, regionInfo,
|
|
||||||
conf);
|
|
||||||
|
|
||||||
long transaction1Id = 1;
|
|
||||||
long transaction2Id = 2;
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction1Id, new Put(row1).add(family,
|
|
||||||
column, val1));
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction2Id, new Put(row2).add(family,
|
|
||||||
column, val2));
|
|
||||||
log.writeAbortToLog(regionInfo, transaction2Id);
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction1Id, new Put(row3).add(family,
|
|
||||||
column, val3));
|
|
||||||
|
|
||||||
log.writeCommitToLog(regionInfo, transaction1Id);
|
|
||||||
|
|
||||||
// log.completeCacheFlush(regionName, tableName, logSeqId);
|
|
||||||
|
|
||||||
log.close();
|
|
||||||
Path filename = log.computeFilename(log.getFilenum());
|
|
||||||
|
|
||||||
Map<Long, List<WALEdit>> commits = logMangaer.getCommitsFromLog(filename,
|
|
||||||
-1, null);
|
|
||||||
|
|
||||||
assertNull(commits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void testInterlievedCommitAbort() throws IOException {
|
|
||||||
|
|
||||||
THLog log = new THLog(fs, dir, oldLogdir, this.conf, null);
|
|
||||||
THLogRecoveryManager logMangaer = new THLogRecoveryManager(fs, regionInfo,
|
|
||||||
conf);
|
|
||||||
|
|
||||||
long transaction1Id = 1;
|
|
||||||
long transaction2Id = 2;
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction1Id, new Put(row1).add(family,
|
|
||||||
column, val1));
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction2Id, new Put(row2).add(family,
|
|
||||||
column, val2));
|
|
||||||
log.writeCommitToLog(regionInfo, transaction2Id);
|
|
||||||
|
|
||||||
log.writeUpdateToLog(regionInfo, transaction1Id, new Put(row3).add(family,
|
|
||||||
column, val3));
|
|
||||||
|
|
||||||
log.writeAbortToLog(regionInfo, transaction1Id);
|
|
||||||
|
|
||||||
// log.completeCacheFlush(regionName, tableName, logSeqId);
|
|
||||||
|
|
||||||
log.close();
|
|
||||||
Path filename = log.computeFilename(log.getFilenum());
|
|
||||||
|
|
||||||
Map<Long, List<WALEdit>> commits = logMangaer.getCommitsFromLog(filename,
|
|
||||||
-1, null);
|
|
||||||
|
|
||||||
assertNull(commits);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME Cannot do this test without a global transacton manager
|
|
||||||
// public void testMissingCommit() {
|
|
||||||
// fail();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// FIXME Cannot do this test without a global transacton manager
|
|
||||||
// public void testMissingAbort() {
|
|
||||||
// fail();
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,290 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2009 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.regionserver.transactional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.apache.hadoop.hbase.HBaseClusterTestCase;
|
|
||||||
import org.apache.hadoop.hbase.HColumnDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
|
||||||
import org.apache.hadoop.hbase.HTableDescriptor;
|
|
||||||
import org.apache.hadoop.hbase.LocalHBaseCluster;
|
|
||||||
import org.apache.hadoop.hbase.client.Get;
|
|
||||||
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
|
||||||
import org.apache.hadoop.hbase.client.HTable;
|
|
||||||
import org.apache.hadoop.hbase.client.Put;
|
|
||||||
import org.apache.hadoop.hbase.client.ResultScanner;
|
|
||||||
import org.apache.hadoop.hbase.client.Scan;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.CommitUnsuccessfulException;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.HBaseBackedTransactionLogger;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.TransactionManager;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.TransactionState;
|
|
||||||
import org.apache.hadoop.hbase.client.transactional.TransactionalTable;
|
|
||||||
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.HRegion;
|
|
||||||
import org.apache.hadoop.hbase.regionserver.HRegionServer;
|
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
|
||||||
import org.apache.hadoop.hbase.util.JVMClusterUtil;
|
|
||||||
|
|
||||||
public class TestTHLogRecovery extends HBaseClusterTestCase {
|
|
||||||
private static final Log LOG = LogFactory.getLog(TestTHLogRecovery.class);
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "table1";
|
|
||||||
|
|
||||||
private static final byte[] FAMILY = Bytes.toBytes("family");
|
|
||||||
private static final byte[] QUAL_A = Bytes.toBytes("a");
|
|
||||||
|
|
||||||
private static final byte[] ROW1 = Bytes.toBytes("row1");
|
|
||||||
private static final byte[] ROW2 = Bytes.toBytes("row2");
|
|
||||||
private static final byte[] ROW3 = Bytes.toBytes("row3");
|
|
||||||
private static final int TOTAL_VALUE = 10;
|
|
||||||
|
|
||||||
private HBaseAdmin admin;
|
|
||||||
private TransactionManager transactionManager;
|
|
||||||
private TransactionalTable table;
|
|
||||||
|
|
||||||
/** constructor */
|
|
||||||
public TestTHLogRecovery() {
|
|
||||||
super(2, false);
|
|
||||||
|
|
||||||
conf.set(HConstants.REGION_SERVER_CLASS, TransactionalRegionInterface.class
|
|
||||||
.getName());
|
|
||||||
conf.set(HConstants.REGION_SERVER_IMPL, TransactionalRegionServer.class
|
|
||||||
.getName());
|
|
||||||
|
|
||||||
// Set flush params so we don't get any
|
|
||||||
// FIXME (defaults are probably fine)
|
|
||||||
|
|
||||||
// Copied from TestRegionServerExit
|
|
||||||
conf.setInt("ipc.client.connect.max.retries", 5); // reduce ipc retries
|
|
||||||
conf.setInt("ipc.client.timeout", 10000); // and ipc timeout
|
|
||||||
conf.setInt("hbase.client.pause", 10000); // increase client timeout
|
|
||||||
conf.setInt("hbase.client.retries.number", 10); // increase HBase retries
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
FileSystem lfs = FileSystem.getLocal(conf);
|
|
||||||
Path p = new Path(conf.get(HConstants.HBASE_DIR));
|
|
||||||
if (lfs.exists(p)) lfs.delete(p, true);
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
HTableDescriptor desc = new HTableDescriptor(TABLE_NAME);
|
|
||||||
desc.addFamily(new HColumnDescriptor(FAMILY));
|
|
||||||
admin = new HBaseAdmin(conf);
|
|
||||||
admin.createTable(desc);
|
|
||||||
table = new TransactionalTable(conf, desc.getName());
|
|
||||||
HBaseBackedTransactionLogger.createTable();
|
|
||||||
|
|
||||||
transactionManager = new TransactionManager(
|
|
||||||
new HBaseBackedTransactionLogger(), conf);
|
|
||||||
writeInitalRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeInitalRows() throws IOException {
|
|
||||||
|
|
||||||
table.put(new Put(ROW1).add(FAMILY, QUAL_A, Bytes.toBytes(TOTAL_VALUE)));
|
|
||||||
table.put(new Put(ROW2).add(FAMILY, QUAL_A, Bytes.toBytes(0)));
|
|
||||||
table.put(new Put(ROW3).add(FAMILY, QUAL_A, Bytes.toBytes(0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testWithoutFlush() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
writeInitalRows();
|
|
||||||
TransactionState state1 = makeTransaction(false);
|
|
||||||
transactionManager.tryCommit(state1);
|
|
||||||
stopOrAbortRegionServer(true);
|
|
||||||
|
|
||||||
Thread t = startVerificationThread(1);
|
|
||||||
t.start();
|
|
||||||
threadDumpingJoin(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testWithFlushBeforeCommit() throws IOException,
|
|
||||||
CommitUnsuccessfulException {
|
|
||||||
writeInitalRows();
|
|
||||||
TransactionState state1 = makeTransaction(false);
|
|
||||||
flushRegionServer();
|
|
||||||
transactionManager.tryCommit(state1);
|
|
||||||
stopOrAbortRegionServer(true);
|
|
||||||
|
|
||||||
Thread t = startVerificationThread(1);
|
|
||||||
t.start();
|
|
||||||
threadDumpingJoin(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME, TODO
|
|
||||||
// public void testWithFlushBetweenTransactionWrites() {
|
|
||||||
// fail();
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void flushRegionServer() {
|
|
||||||
List<JVMClusterUtil.RegionServerThread> regionThreads = cluster
|
|
||||||
.getRegionServerThreads();
|
|
||||||
|
|
||||||
HRegion region = null;
|
|
||||||
int server = -1;
|
|
||||||
for (int i = 0; i < regionThreads.size() && server == -1; i++) {
|
|
||||||
HRegionServer s = regionThreads.get(i).getRegionServer();
|
|
||||||
Collection<HRegion> regions = s.getOnlineRegions();
|
|
||||||
for (HRegion r : regions) {
|
|
||||||
if (Bytes.equals(r.getTableDesc().getName(), Bytes.toBytes(TABLE_NAME))) {
|
|
||||||
server = i;
|
|
||||||
region = r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server == -1) {
|
|
||||||
LOG.fatal("could not find region server serving table region");
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
((TransactionalRegionServer) regionThreads.get(server).getRegionServer())
|
|
||||||
.getFlushRequester().request(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the region server serving TABLE_NAME.
|
|
||||||
*
|
|
||||||
* @param abort set to true if region server should be aborted, if false it is
|
|
||||||
* just shut down.
|
|
||||||
*/
|
|
||||||
private void stopOrAbortRegionServer(final boolean abort) {
|
|
||||||
List<JVMClusterUtil.RegionServerThread> regionThreads = cluster
|
|
||||||
.getRegionServerThreads();
|
|
||||||
|
|
||||||
int server = -1;
|
|
||||||
for (int i = 0; i < regionThreads.size(); i++) {
|
|
||||||
HRegionServer s = regionThreads.get(i).getRegionServer();
|
|
||||||
Collection<HRegion> regions = s.getOnlineRegions();
|
|
||||||
LOG.info("server: " + regionThreads.get(i).getName());
|
|
||||||
for (HRegion r : regions) {
|
|
||||||
LOG.info("region: " + r.getRegionInfo().getRegionNameAsString());
|
|
||||||
if (Bytes.equals(r.getTableDesc().getName(), Bytes.toBytes(TABLE_NAME))) {
|
|
||||||
server = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server == -1) {
|
|
||||||
LOG.fatal("could not find region server serving table region");
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
if (abort) {
|
|
||||||
this.cluster.abortRegionServer(server);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.cluster.stopRegionServer(server, false);
|
|
||||||
}
|
|
||||||
LOG.info(this.cluster.waitOnRegionServer(server) + " has been "
|
|
||||||
+ (abort ? "aborted" : "shut down"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verify(final int numRuns) throws IOException {
|
|
||||||
// Reads
|
|
||||||
int row1 = Bytes.toInt(table.get(new Get(ROW1).addColumn(FAMILY, QUAL_A))
|
|
||||||
.getValue(FAMILY, QUAL_A));
|
|
||||||
int row2 = Bytes.toInt(table.get(new Get(ROW2).addColumn(FAMILY, QUAL_A))
|
|
||||||
.getValue(FAMILY, QUAL_A));
|
|
||||||
int row3 = Bytes.toInt(table.get(new Get(ROW3).addColumn(FAMILY, QUAL_A))
|
|
||||||
.getValue(FAMILY, QUAL_A));
|
|
||||||
|
|
||||||
assertEquals(TOTAL_VALUE - 2 * numRuns, row1);
|
|
||||||
assertEquals(numRuns, row2);
|
|
||||||
assertEquals(numRuns, row3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move 2 out of ROW1 and 1 into ROW2 and 1 into ROW3
|
|
||||||
private TransactionState makeTransaction(final boolean flushMidWay)
|
|
||||||
throws IOException {
|
|
||||||
TransactionState transactionState = transactionManager.beginTransaction();
|
|
||||||
|
|
||||||
// Reads
|
|
||||||
int row1 = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(ROW1).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
int row2 = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(ROW2).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
int row3 = Bytes.toInt(table.get(transactionState,
|
|
||||||
new Get(ROW3).addColumn(FAMILY, QUAL_A)).getValue(FAMILY, QUAL_A));
|
|
||||||
|
|
||||||
row1 -= 2;
|
|
||||||
row2 += 1;
|
|
||||||
row3 += 1;
|
|
||||||
|
|
||||||
if (flushMidWay) {
|
|
||||||
flushRegionServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes
|
|
||||||
Put write = new Put(ROW1);
|
|
||||||
write.add(FAMILY, QUAL_A, Bytes.toBytes(row1));
|
|
||||||
table.put(transactionState, write);
|
|
||||||
|
|
||||||
write = new Put(ROW2);
|
|
||||||
write.add(FAMILY, QUAL_A, Bytes.toBytes(row2));
|
|
||||||
table.put(transactionState, write);
|
|
||||||
|
|
||||||
write = new Put(ROW3);
|
|
||||||
write.add(FAMILY, QUAL_A, Bytes.toBytes(row3));
|
|
||||||
table.put(transactionState, write);
|
|
||||||
|
|
||||||
return transactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Run verification in a thread so I can concurrently run a thread-dumper
|
|
||||||
* while we're waiting (because in this test sometimes the meta scanner looks
|
|
||||||
* to be be stuck). @param tableName Name of table to find. @param row Row we
|
|
||||||
* expect to find. @return Verification thread. Caller needs to calls start on
|
|
||||||
* it.
|
|
||||||
*/
|
|
||||||
private Thread startVerificationThread(final int numRuns) {
|
|
||||||
Runnable runnable = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
// Now try to open a scanner on the meta table. Should stall until
|
|
||||||
// meta server comes back up.
|
|
||||||
HTable t = new HTable(conf, TABLE_NAME);
|
|
||||||
Scan s = new Scan();
|
|
||||||
s.addColumn(FAMILY, QUAL_A);
|
|
||||||
ResultScanner scanner = t.getScanner(s);
|
|
||||||
scanner.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.fatal("could not re-open meta table because", e);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
verify(numRuns);
|
|
||||||
LOG.info("Success!");
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return new Thread(runnable);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue