001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.client; 019 020import static org.apache.hadoop.hbase.HConstants.HBASE_CLIENT_META_OPERATION_TIMEOUT; 021import static org.apache.hadoop.hbase.io.ByteBuffAllocator.MAX_BUFFER_COUNT_KEY; 022import static org.junit.jupiter.api.Assertions.assertEquals; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.List; 029import java.util.concurrent.ExecutionException; 030import java.util.concurrent.ExecutorService; 031import java.util.concurrent.Executors; 032import java.util.concurrent.Future; 033import java.util.concurrent.ThreadLocalRandom; 034import java.util.concurrent.TimeUnit; 035import java.util.concurrent.atomic.AtomicBoolean; 036import java.util.stream.Collectors; 037import java.util.stream.IntStream; 038import org.apache.hadoop.hbase.HBaseTestingUtil; 039import org.apache.hadoop.hbase.MemoryCompactionPolicy; 040import org.apache.hadoop.hbase.ServerName; 041import org.apache.hadoop.hbase.TableName; 042import org.apache.hadoop.hbase.Waiter.ExplainingPredicate; 043import org.apache.hadoop.hbase.regionserver.CompactingMemStore; 044import org.apache.hadoop.hbase.regionserver.HRegion; 045import org.apache.hadoop.hbase.testclassification.ClientTests; 046import org.apache.hadoop.hbase.testclassification.LargeTests; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.apache.hadoop.hbase.util.RetryCounter; 049import org.apache.hadoop.hbase.util.Threads; 050import org.junit.jupiter.api.AfterAll; 051import org.junit.jupiter.api.BeforeAll; 052import org.junit.jupiter.api.Tag; 053import org.junit.jupiter.api.Test; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 058import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder; 059 060/** 061 * Will split the table, and move region randomly when testing. 062 */ 063@Tag(LargeTests.TAG) 064@Tag(ClientTests.TAG) 065public class TestAsyncTableGetMultiThreaded { 066 067 private static final Logger LOG = LoggerFactory.getLogger(TestAsyncTableGetMultiThreaded.class); 068 069 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 070 071 private static final TableName TABLE_NAME = TableName.valueOf("async"); 072 private static final byte[] FAMILY = Bytes.toBytes("cf"); 073 private static final byte[] QUALIFIER = Bytes.toBytes("cq"); 074 private static final int COUNT = 1000; 075 076 private static AsyncConnection CONN; 077 078 private static AsyncTable<?> TABLE; 079 080 private static byte[][] SPLIT_KEYS; 081 082 @BeforeAll 083 public static void setUp() throws Exception { 084 setUp(MemoryCompactionPolicy.NONE); 085 } 086 087 protected static void setUp(MemoryCompactionPolicy memoryCompaction) throws Exception { 088 TEST_UTIL.getConfiguration().setLong(HBASE_CLIENT_META_OPERATION_TIMEOUT, 60000L); 089 TEST_UTIL.getConfiguration().setInt(MAX_BUFFER_COUNT_KEY, 100); 090 TEST_UTIL.getConfiguration().set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY, 091 String.valueOf(memoryCompaction)); 092 TEST_UTIL.getConfiguration().setBoolean("hbase.master.balancer.decision.buffer.enabled", true); 093 094 TEST_UTIL.startMiniCluster(3); 095 SPLIT_KEYS = new byte[8][]; 096 for (int i = 111; i < 999; i += 111) { 097 SPLIT_KEYS[i / 111 - 1] = Bytes.toBytes(String.format("%03d", i)); 098 } 099 TEST_UTIL.createTable(TABLE_NAME, FAMILY); 100 TEST_UTIL.waitTableAvailable(TABLE_NAME); 101 CONN = ConnectionFactory.createAsyncConnection(TEST_UTIL.getConfiguration()).get(); 102 TABLE = CONN.getTableBuilder(TABLE_NAME).setReadRpcTimeout(1, TimeUnit.SECONDS) 103 .setMaxRetries(1000).build(); 104 TABLE.putAll( 105 IntStream.range(0, COUNT).mapToObj(i -> new Put(Bytes.toBytes(String.format("%03d", i))) 106 .addColumn(FAMILY, QUALIFIER, Bytes.toBytes(i))).collect(Collectors.toList())) 107 .get(); 108 } 109 110 @AfterAll 111 public static void tearDown() throws Exception { 112 Closeables.close(CONN, true); 113 TEST_UTIL.shutdownMiniCluster(); 114 } 115 116 private void run(AtomicBoolean stop) throws InterruptedException, ExecutionException { 117 while (!stop.get()) { 118 for (int i = 0; i < COUNT; i++) { 119 assertEquals(i, Bytes.toInt(TABLE.get(new Get(Bytes.toBytes(String.format("%03d", i)))) 120 .get().getValue(FAMILY, QUALIFIER))); 121 } 122 // sleep a bit so we do not add to much load to the test machine as we have 20 threads here 123 Thread.sleep(10); 124 } 125 } 126 127 @Test 128 public void test() throws Exception { 129 LOG.info("====== Test started ======"); 130 int numThreads = 7; 131 AtomicBoolean stop = new AtomicBoolean(false); 132 ExecutorService executor = Executors.newFixedThreadPool(numThreads, 133 new ThreadFactoryBuilder().setNameFormat("TestAsyncGet-pool-%d").setDaemon(true) 134 .setUncaughtExceptionHandler(Threads.LOGGING_EXCEPTION_HANDLER).build()); 135 List<Future<?>> futures = new ArrayList<>(); 136 IntStream.range(0, numThreads).forEach(i -> futures.add(executor.submit(() -> { 137 run(stop); 138 return null; 139 }))); 140 LOG.info("====== Scheduled {} read threads ======", numThreads); 141 Collections.shuffle(Arrays.asList(SPLIT_KEYS), ThreadLocalRandom.current()); 142 Admin admin = TEST_UTIL.getAdmin(); 143 for (byte[] splitPoint : SPLIT_KEYS) { 144 int oldRegionCount = admin.getRegions(TABLE_NAME).size(); 145 LOG.info("====== Splitting at {} ======, region count before splitting is {}", 146 Bytes.toStringBinary(splitPoint), oldRegionCount); 147 admin.split(TABLE_NAME, splitPoint); 148 TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() { 149 @Override 150 public boolean evaluate() throws Exception { 151 return TEST_UTIL.getMiniHBaseCluster().getRegions(TABLE_NAME).size() > oldRegionCount; 152 } 153 154 @Override 155 public String explainFailure() throws Exception { 156 return "Split has not finished yet"; 157 } 158 }); 159 List<HRegion> regions = TEST_UTIL.getMiniHBaseCluster().getRegions(TABLE_NAME); 160 LOG.info("====== Split at {} ======, region count after splitting is {}", 161 Bytes.toStringBinary(splitPoint), regions.size()); 162 for (HRegion region : regions) { 163 LOG.info("====== Compact {} ======", region.getRegionInfo()); 164 region.compact(true); 165 } 166 for (HRegion region : regions) { 167 // Waiting for compaction to complete and references are cleaned up 168 LOG.info("====== Waiting for compaction on {} ======", region.getRegionInfo()); 169 RetryCounter retrier = new RetryCounter(30, 1, TimeUnit.SECONDS); 170 for (;;) { 171 try { 172 if ( 173 admin.getCompactionStateForRegion(region.getRegionInfo().getRegionName()) 174 == CompactionState.NONE 175 ) { 176 break; 177 } 178 } catch (IOException e) { 179 LOG.warn("Failed to query"); 180 } 181 if (!retrier.shouldRetry()) { 182 throw new IOException("Can not finish compaction in time after attempt " 183 + retrier.getAttemptTimes() + " times"); 184 } 185 retrier.sleepUntilNextRetry(); 186 } 187 LOG.info("====== Compaction on {} finished, close and archive compacted files ======", 188 region.getRegionInfo()); 189 region.getStores().get(0).closeAndArchiveCompactedFiles(); 190 LOG.info("====== Close and archive compacted files on {} done ======", 191 region.getRegionInfo()); 192 } 193 Thread.sleep(5000); 194 LOG.info("====== Balancing cluster ======"); 195 admin.balance(BalanceRequest.newBuilder().setIgnoreRegionsInTransition(true).build()); 196 LOG.info("====== Balance cluster done ======"); 197 Thread.sleep(5000); 198 ServerName metaServer = TEST_UTIL.getHBaseCluster().getServerHoldingMeta(); 199 ServerName newMetaServer = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream() 200 .map(t -> t.getRegionServer().getServerName()).filter(s -> !s.equals(metaServer)).findAny() 201 .get(); 202 LOG.info("====== Moving meta from {} to {} ======", metaServer, newMetaServer); 203 admin.move(RegionInfoBuilder.FIRST_META_REGIONINFO.getEncodedNameAsBytes(), newMetaServer); 204 LOG.info("====== Move meta done ======"); 205 Thread.sleep(5000); 206 } 207 List<LogEntry> balancerDecisionRecords = 208 admin.getLogEntries(null, "BALANCER_DECISION", ServerType.MASTER, 2, null); 209 assertEquals(balancerDecisionRecords.size(), 2); 210 LOG.info("====== Read test finished, shutdown thread pool ======"); 211 stop.set(true); 212 executor.shutdown(); 213 for (int i = 0; i < numThreads; i++) { 214 LOG.info("====== Waiting for {} threads to finish, remaining {} ======", numThreads, 215 numThreads - i); 216 futures.get(i).get(); 217 } 218 LOG.info("====== Test test finished ======"); 219 } 220}