HBASE-18347 Implement a BufferedMutator for async client
This commit is contained in:
parent
45b20da23b
commit
1ae9a39011
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.hbase.TableName;
|
||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||
|
||||
/**
|
||||
* Used to communicate with a single HBase table in batches. Obtain an instance from a
|
||||
* {@link AsyncConnection} and call {@link #close()} afterwards.
|
||||
* <p>
|
||||
* The implementation is required to be thread safe.
|
||||
*/
|
||||
@InterfaceAudience.Public
|
||||
public interface AsyncBufferedMutator extends Closeable {
|
||||
|
||||
/**
|
||||
* Gets the fully qualified table name instance of the table that this
|
||||
* {@code AsyncBufferedMutator} writes to.
|
||||
*/
|
||||
TableName getName();
|
||||
|
||||
/**
|
||||
* Returns the {@link org.apache.hadoop.conf.Configuration} object used by this instance.
|
||||
* <p>
|
||||
* The reference returned is not a copy, so any change made to it will affect this instance.
|
||||
*/
|
||||
Configuration getConfiguration();
|
||||
|
||||
/**
|
||||
* Sends a {@link Mutation} to the table. The mutations will be buffered and sent over the wire as
|
||||
* part of a batch. Currently only supports {@link Put} and {@link Delete} mutations.
|
||||
* @param mutation The data to send.
|
||||
*/
|
||||
CompletableFuture<Void> mutate(Mutation mutation);
|
||||
|
||||
/**
|
||||
* Send some {@link Mutation}s to the table. The mutations will be buffered and sent over the wire
|
||||
* as part of a batch. There is no guarantee of sending entire content of {@code mutations} in a
|
||||
* single batch, the implementations are free to break it up according to the write buffer
|
||||
* capacity.
|
||||
* @param mutations The data to send.
|
||||
*/
|
||||
List<CompletableFuture<Void>> mutate(List<? extends Mutation> mutations);
|
||||
|
||||
/**
|
||||
* Executes all the buffered, asynchronous operations.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* Performs a {@link #flush()} and releases any resources held.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Returns the maximum size in bytes of the write buffer.
|
||||
* <p>
|
||||
* The default value comes from the configuration parameter {@code hbase.client.write.buffer}.
|
||||
* @return The size of the write buffer in bytes.
|
||||
*/
|
||||
long getWriteBufferSize();
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
import static org.apache.hadoop.hbase.client.ConnectionUtils.retries2Attempts;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||
|
||||
/**
|
||||
* For creating {@link AsyncBufferedMutator}.
|
||||
*/
|
||||
@InterfaceAudience.Public
|
||||
public interface AsyncBufferedMutatorBuilder {
|
||||
|
||||
/**
|
||||
* Set timeout for the background flush operation.
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder setOperationTimeout(long timeout, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Set timeout for each rpc request when doing background flush.
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder setRpcTimeout(long timeout, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Set the base pause time for retrying. We use an exponential policy to generate sleep time when
|
||||
* retrying.
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder setRetryPause(long pause, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Set the max retry times for an operation. Usually it is the max attempt times minus 1.
|
||||
* <p>
|
||||
* Operation timeout and max attempt times(or max retry times) are both limitations for retrying,
|
||||
* we will stop retrying when we reach any of the limitations.
|
||||
* @see #setMaxAttempts(int)
|
||||
* @see #setOperationTimeout(long, TimeUnit)
|
||||
*/
|
||||
default AsyncBufferedMutatorBuilder setMaxRetries(int maxRetries) {
|
||||
return setMaxAttempts(retries2Attempts(maxRetries));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max attempt times for an operation. Usually it is the max retry times plus 1. Operation
|
||||
* timeout and max attempt times(or max retry times) are both limitations for retrying, we will
|
||||
* stop retrying when we reach any of the limitations.
|
||||
* @see #setMaxRetries(int)
|
||||
* @see #setOperationTimeout(long, TimeUnit)
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder setMaxAttempts(int maxAttempts);
|
||||
|
||||
/**
|
||||
* Set the number of retries that are allowed before we start to log.
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder setStartLogErrorsCnt(int startLogErrorsCnt);
|
||||
|
||||
/**
|
||||
* Override the write buffer size specified by the provided {@link AsyncConnection}'s
|
||||
* {@link org.apache.hadoop.conf.Configuration} instance, via the configuration key
|
||||
* {@code hbase.client.write.buffer}.
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder setWriteBufferSize(long writeBufferSize);
|
||||
|
||||
/**
|
||||
* Create the {@link AsyncBufferedMutator} instance.
|
||||
*/
|
||||
AsyncBufferedMutator build();
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||
|
||||
/**
|
||||
* The implementation of {@link AsyncBufferedMutatorBuilder}.
|
||||
*/
|
||||
@InterfaceAudience.Private
|
||||
class AsyncBufferedMutatorBuilderImpl implements AsyncBufferedMutatorBuilder {
|
||||
|
||||
private final AsyncTableBuilder<? extends AsyncTableBase> tableBuilder;
|
||||
|
||||
private long writeBufferSize;
|
||||
|
||||
public AsyncBufferedMutatorBuilderImpl(AsyncConnectionConfiguration connConf,
|
||||
AsyncTableBuilder<? extends AsyncTableBase> tableBuilder) {
|
||||
this.tableBuilder = tableBuilder;
|
||||
this.writeBufferSize = connConf.getWriteBufferSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder setOperationTimeout(long timeout, TimeUnit unit) {
|
||||
tableBuilder.setOperationTimeout(timeout, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder setRpcTimeout(long timeout, TimeUnit unit) {
|
||||
tableBuilder.setRpcTimeout(timeout, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder setRetryPause(long pause, TimeUnit unit) {
|
||||
tableBuilder.setRetryPause(pause, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder setMaxAttempts(int maxAttempts) {
|
||||
tableBuilder.setMaxAttempts(maxAttempts);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder setStartLogErrorsCnt(int startLogErrorsCnt) {
|
||||
tableBuilder.setStartLogErrorsCnt(startLogErrorsCnt);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder setWriteBufferSize(long writeBufferSize) {
|
||||
Preconditions.checkArgument(writeBufferSize > 0, "writeBufferSize %d must be >= 0",
|
||||
writeBufferSize);
|
||||
this.writeBufferSize = writeBufferSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutator build() {
|
||||
return new AsyncBufferedMutatorImpl(tableBuilder.build(), writeBufferSize);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.hbase.TableName;
|
||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||
|
||||
/**
|
||||
* The implementation of {@link AsyncBufferedMutator}. Simply wrap an {@link AsyncTableBase}.
|
||||
*/
|
||||
@InterfaceAudience.Private
|
||||
class AsyncBufferedMutatorImpl implements AsyncBufferedMutator {
|
||||
|
||||
private final AsyncTableBase table;
|
||||
|
||||
private final long writeBufferSize;
|
||||
|
||||
private List<Mutation> mutations = new ArrayList<>();
|
||||
|
||||
private List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
private long bufferedSize;
|
||||
|
||||
private boolean closed;
|
||||
|
||||
AsyncBufferedMutatorImpl(AsyncTableBase table, long writeBufferSize) {
|
||||
this.table = table;
|
||||
this.writeBufferSize = writeBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableName getName() {
|
||||
return table.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration getConfiguration() {
|
||||
return table.getConfiguration();
|
||||
}
|
||||
|
||||
private void internalFlush() {
|
||||
List<Mutation> toSend = this.mutations;
|
||||
if (toSend.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<CompletableFuture<Void>> toComplete = this.futures;
|
||||
assert toSend.size() == toComplete.size();
|
||||
this.mutations = new ArrayList<>();
|
||||
this.futures = new ArrayList<>();
|
||||
bufferedSize = 0L;
|
||||
Iterator<CompletableFuture<Void>> toCompleteIter = toComplete.iterator();
|
||||
for (CompletableFuture<?> future : table.batch(toSend)) {
|
||||
future.whenComplete((r, e) -> {
|
||||
CompletableFuture<Void> f = toCompleteIter.next();
|
||||
if (e != null) {
|
||||
f.completeExceptionally(e);
|
||||
} else {
|
||||
f.complete(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> mutate(Mutation mutation) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<Void>();
|
||||
long heapSize = mutation.heapSize();
|
||||
synchronized (this) {
|
||||
if (closed) {
|
||||
future.completeExceptionally(new IOException("Already closed"));
|
||||
return future;
|
||||
}
|
||||
mutations.add(mutation);
|
||||
futures.add(future);
|
||||
bufferedSize += heapSize;
|
||||
if (bufferedSize >= writeBufferSize) {
|
||||
internalFlush();
|
||||
}
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletableFuture<Void>> mutate(List<? extends Mutation> mutations) {
|
||||
List<CompletableFuture<Void>> futures =
|
||||
Stream.<CompletableFuture<Void>> generate(CompletableFuture::new).limit(mutations.size())
|
||||
.collect(Collectors.toList());
|
||||
long heapSize = mutations.stream().mapToLong(m -> m.heapSize()).sum();
|
||||
synchronized (this) {
|
||||
if (closed) {
|
||||
IOException ioe = new IOException("Already closed");
|
||||
futures.forEach(f -> f.completeExceptionally(ioe));
|
||||
return futures;
|
||||
}
|
||||
this.mutations.addAll(mutations);
|
||||
this.futures.addAll(futures);
|
||||
bufferedSize += heapSize;
|
||||
if (bufferedSize >= writeBufferSize) {
|
||||
internalFlush();
|
||||
}
|
||||
}
|
||||
return futures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush() {
|
||||
internalFlush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
internalFlush();
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getWriteBufferSize() {
|
||||
return writeBufferSize;
|
||||
}
|
||||
}
|
|
@ -136,4 +136,43 @@ public interface AsyncConnection extends Closeable {
|
|||
* @param pool the thread pool to use for executing callback
|
||||
*/
|
||||
AsyncAdminBuilder getAdminBuilder(ExecutorService pool);
|
||||
|
||||
/**
|
||||
* Retrieve an {@link AsyncBufferedMutator} for performing client-side buffering of writes.
|
||||
* <p>
|
||||
* The returned instance will use default configs. Use
|
||||
* {@link #getBufferedMutatorBuilder(TableName)} if you want to customize some configs.
|
||||
* @param tableName the name of the table
|
||||
* @return an {@link AsyncBufferedMutator} for the supplied tableName.
|
||||
*/
|
||||
default AsyncBufferedMutator getBufferedMutator(TableName tableName) {
|
||||
return getBufferedMutatorBuilder(tableName).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link AsyncBufferedMutatorBuilder} for creating {@link AsyncBufferedMutator}.
|
||||
* @param tableName the name of the table
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder getBufferedMutatorBuilder(TableName tableName);
|
||||
|
||||
/**
|
||||
* Retrieve an {@link AsyncBufferedMutator} for performing client-side buffering of writes.
|
||||
* <p>
|
||||
* The returned instance will use default configs. Use
|
||||
* {@link #getBufferedMutatorBuilder(TableName, ExecutorService)} if you want to customize some
|
||||
* configs.
|
||||
* @param tableName the name of the table
|
||||
* @param pool the thread pool to use for executing callback
|
||||
* @return an {@link AsyncBufferedMutator} for the supplied tableName.
|
||||
*/
|
||||
default AsyncBufferedMutator getBufferedMutator(TableName tableName, ExecutorService pool) {
|
||||
return getBufferedMutatorBuilder(tableName, pool).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link AsyncBufferedMutatorBuilder} for creating {@link AsyncBufferedMutator}.
|
||||
* @param tableName the name of the table
|
||||
* @param pool the thread pool to use for executing callback
|
||||
*/
|
||||
AsyncBufferedMutatorBuilder getBufferedMutatorBuilder(TableName tableName, ExecutorService pool);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ import static org.apache.hadoop.hbase.HConstants.HBASE_RPC_TIMEOUT_KEY;
|
|||
import static org.apache.hadoop.hbase.HConstants.HBASE_RPC_WRITE_TIMEOUT_KEY;
|
||||
import static org.apache.hadoop.hbase.client.AsyncProcess.DEFAULT_START_LOG_ERRORS_AFTER_COUNT;
|
||||
import static org.apache.hadoop.hbase.client.AsyncProcess.START_LOG_ERRORS_AFTER_COUNT_KEY;
|
||||
import static org.apache.hadoop.hbase.client.ConnectionConfiguration.WRITE_BUFFER_SIZE_DEFAULT;
|
||||
import static org.apache.hadoop.hbase.client.ConnectionConfiguration.WRITE_BUFFER_SIZE_KEY;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -87,6 +89,8 @@ class AsyncConnectionConfiguration {
|
|||
|
||||
private final long scannerMaxResultSize;
|
||||
|
||||
private final long writeBufferSize;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
AsyncConnectionConfiguration(Configuration conf) {
|
||||
this.metaOperationTimeoutNs = TimeUnit.MILLISECONDS.toNanos(
|
||||
|
@ -112,6 +116,7 @@ class AsyncConnectionConfiguration {
|
|||
this.metaScannerCaching = conf.getInt(HBASE_META_SCANNER_CACHING, DEFAULT_HBASE_META_SCANNER_CACHING);
|
||||
this.scannerMaxResultSize = conf.getLong(HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY,
|
||||
DEFAULT_HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE);
|
||||
this.writeBufferSize = conf.getLong(WRITE_BUFFER_SIZE_KEY, WRITE_BUFFER_SIZE_DEFAULT);
|
||||
}
|
||||
|
||||
long getMetaOperationTimeoutNs() {
|
||||
|
@ -161,4 +166,8 @@ class AsyncConnectionConfiguration {
|
|||
long getScannerMaxResultSize() {
|
||||
return scannerMaxResultSize;
|
||||
}
|
||||
|
||||
long getWriteBufferSize() {
|
||||
return writeBufferSize;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,4 +297,15 @@ class AsyncConnectionImpl implements AsyncConnection {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder getBufferedMutatorBuilder(TableName tableName) {
|
||||
return new AsyncBufferedMutatorBuilderImpl(connConf, getRawTableBuilder(tableName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncBufferedMutatorBuilder getBufferedMutatorBuilder(TableName tableName,
|
||||
ExecutorService pool) {
|
||||
return new AsyncBufferedMutatorBuilderImpl(connConf, getTableBuilder(tableName, pool));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||
import org.apache.hadoop.hbase.TableName;
|
||||
import org.apache.hadoop.hbase.testclassification.ClientTests;
|
||||
import org.apache.hadoop.hbase.testclassification.MediumTests;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
@Category({ MediumTests.class, ClientTests.class })
|
||||
public class TestAsyncBufferMutator {
|
||||
|
||||
private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
|
||||
|
||||
private static TableName TABLE_NAME = TableName.valueOf("async");
|
||||
|
||||
private static byte[] CF = Bytes.toBytes("cf");
|
||||
|
||||
private static byte[] CQ = Bytes.toBytes("cq");
|
||||
|
||||
private static int COUNT = 100;
|
||||
|
||||
private static byte[] VALUE = new byte[1024];
|
||||
|
||||
private static AsyncConnection CONN;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws Exception {
|
||||
TEST_UTIL.startMiniCluster(1);
|
||||
TEST_UTIL.createTable(TABLE_NAME, CF);
|
||||
CONN = ConnectionFactory.createAsyncConnection(TEST_UTIL.getConfiguration()).get();
|
||||
ThreadLocalRandom.current().nextBytes(VALUE);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() throws Exception {
|
||||
CONN.close();
|
||||
TEST_UTIL.shutdownMiniCluster();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws InterruptedException {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
try (AsyncBufferedMutator mutator =
|
||||
CONN.getBufferedMutatorBuilder(TABLE_NAME).setWriteBufferSize(16 * 1024).build()) {
|
||||
List<CompletableFuture<Void>> fs = mutator.mutate(IntStream.range(0, COUNT / 2)
|
||||
.mapToObj(i -> new Put(Bytes.toBytes(i)).addColumn(CF, CQ, VALUE))
|
||||
.collect(Collectors.toList()));
|
||||
// exceeded the write buffer size, a flush will be called directly
|
||||
fs.forEach(f -> f.join());
|
||||
IntStream.range(COUNT / 2, COUNT).forEach(i -> {
|
||||
futures.add(mutator.mutate(new Put(Bytes.toBytes(i)).addColumn(CF, CQ, VALUE)));
|
||||
});
|
||||
// the first future should have been sent out.
|
||||
futures.get(0).join();
|
||||
Thread.sleep(2000);
|
||||
// the last one should still be in write buffer
|
||||
assertFalse(futures.get(futures.size() - 1).isDone());
|
||||
}
|
||||
// mutator.close will call mutator.flush automatically so all tasks should have been done.
|
||||
futures.forEach(f -> f.join());
|
||||
RawAsyncTable table = CONN.getRawTable(TABLE_NAME);
|
||||
IntStream.range(0, COUNT).mapToObj(i -> new Get(Bytes.toBytes(i))).map(g -> table.get(g).join())
|
||||
.forEach(r -> {
|
||||
assertArrayEquals(VALUE, r.getValue(CF, CQ));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClosedMutate() throws InterruptedException {
|
||||
AsyncBufferedMutator mutator = CONN.getBufferedMutator(TABLE_NAME);
|
||||
mutator.close();
|
||||
Put put = new Put(Bytes.toBytes(0)).addColumn(CF, CQ, VALUE);
|
||||
try {
|
||||
mutator.mutate(put).get();
|
||||
fail("Close check failed");
|
||||
} catch (ExecutionException e) {
|
||||
assertThat(e.getCause(), instanceOf(IOException.class));
|
||||
assertTrue(e.getCause().getMessage().startsWith("Already closed"));
|
||||
}
|
||||
for (CompletableFuture<Void> f : mutator.mutate(Arrays.asList(put))) {
|
||||
try {
|
||||
f.get();
|
||||
fail("Close check failed");
|
||||
} catch (ExecutionException e) {
|
||||
assertThat(e.getCause(), instanceOf(IOException.class));
|
||||
assertTrue(e.getCause().getMessage().startsWith("Already closed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue