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;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.List;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.client.RegionInfo;
028import org.apache.hadoop.hbase.client.RegionLocator;
029import org.apache.hadoop.hbase.client.Table;
030import org.apache.hadoop.hbase.regionserver.HRegion;
031import org.apache.hadoop.hbase.regionserver.HRegionServer;
032import org.apache.hadoop.hbase.testclassification.MediumTests;
033import org.apache.hadoop.hbase.testclassification.MiscTests;
034import org.apache.hadoop.hbase.util.Bytes;
035import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
036import org.apache.hadoop.hbase.util.JVMClusterUtil;
037import org.apache.hadoop.hbase.util.Threads;
038import org.junit.jupiter.api.AfterEach;
039import org.junit.jupiter.api.BeforeEach;
040import org.junit.jupiter.api.Tag;
041import org.junit.jupiter.api.Test;
042import org.junit.jupiter.api.TestInfo;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
047
048/**
049 * Test HBASE-3694 whether the GlobalMemStoreSize is the same as the summary of all the online
050 * region's MemStoreSize
051 */
052@Tag(MiscTests.TAG)
053@Tag(MediumTests.TAG)
054public class TestGlobalMemStoreSize {
055
056  private static final Logger LOG = LoggerFactory.getLogger(TestGlobalMemStoreSize.class);
057  private static int regionServerNum = 4;
058  private static int regionNum = 16;
059  // total region num = region num + meta regions
060  private static int totalRegionNum = regionNum + 1;
061
062  private HBaseTestingUtil TEST_UTIL;
063  private SingleProcessHBaseCluster cluster;
064
065  @BeforeEach
066  public void setUp() throws Exception {
067    // Start the cluster
068    LOG.info("Starting cluster");
069    Configuration conf = HBaseConfiguration.create();
070    TEST_UTIL = new HBaseTestingUtil(conf);
071    TEST_UTIL.startMiniCluster(regionServerNum);
072    cluster = TEST_UTIL.getHBaseCluster();
073    LOG.info("Waiting for active/ready master");
074    cluster.waitForActiveAndReadyMaster();
075  }
076
077  @AfterEach
078  public void tearDown() throws Exception {
079    TEST_UTIL.shutdownMiniCluster();
080  }
081
082  /**
083   * Test the global mem store size in the region server is equal to sum of each region's mem store
084   * size
085   */
086  @Test
087  public void testGlobalMemStore(TestInfo testInfo) throws Exception {
088    // Create a table with regions
089    final TableName table = TableName.valueOf(testInfo.getTestMethod().get().getName());
090    byte[] family = Bytes.toBytes("family");
091    LOG.info("Creating table with " + regionNum + " regions");
092    Table ht = TEST_UTIL.createMultiRegionTable(table, family, regionNum);
093    int numRegions = -1;
094    try (RegionLocator r = TEST_UTIL.getConnection().getRegionLocator(table)) {
095      numRegions = r.getStartKeys().length;
096    }
097    assertEquals(regionNum, numRegions);
098    waitForAllRegionsAssigned();
099
100    for (HRegionServer server : getOnlineRegionServers()) {
101      long globalMemStoreSize = 0;
102      for (RegionInfo regionInfo : ProtobufUtil.getOnlineRegions(null, server.getRSRpcServices())) {
103        globalMemStoreSize += server.getRegion(regionInfo.getEncodedName()).getMemStoreDataSize();
104      }
105      assertEquals(server.getRegionServerAccounting().getGlobalMemStoreDataSize(),
106        globalMemStoreSize);
107    }
108
109    // check the global memstore size after flush
110    int i = 0;
111    for (HRegionServer server : getOnlineRegionServers()) {
112      LOG.info("Starting flushes on " + server.getServerName() + ", size="
113        + server.getRegionServerAccounting().getGlobalMemStoreDataSize());
114
115      for (RegionInfo regionInfo : ProtobufUtil.getOnlineRegions(null, server.getRSRpcServices())) {
116        HRegion r = server.getRegion(regionInfo.getEncodedName());
117        flush(r, server);
118      }
119      LOG.info("Post flush on " + server.getServerName());
120      long now = EnvironmentEdgeManager.currentTime();
121      long timeout = now + 1000;
122      while (
123        server.getRegionServerAccounting().getGlobalMemStoreDataSize() != 0
124          && timeout < EnvironmentEdgeManager.currentTime()
125      ) {
126        Threads.sleep(10);
127      }
128      long size = server.getRegionServerAccounting().getGlobalMemStoreDataSize();
129      if (size > 0) {
130        // If size > 0, see if its because the meta region got edits while
131        // our test was running....
132        for (RegionInfo regionInfo : ProtobufUtil.getOnlineRegions(null,
133          server.getRSRpcServices())) {
134          HRegion r = server.getRegion(regionInfo.getEncodedName());
135          long l = r.getMemStoreDataSize();
136          if (l > 0) {
137            // Only meta could have edits at this stage. Give it another flush
138            // clear them.
139            assertTrue(regionInfo.isMetaRegion());
140            LOG.info(r.toString() + " " + l + ", reflushing");
141            r.flush(true);
142          }
143        }
144      }
145      size = server.getRegionServerAccounting().getGlobalMemStoreDataSize();
146      assertEquals(0, size, "Server=" + server.getServerName() + ", i=" + i++);
147    }
148
149    ht.close();
150  }
151
152  /**
153   * Flush and log stats on flush
154   */
155  private void flush(final HRegion r, final HRegionServer server) throws IOException {
156    LOG.info("Flush " + r.toString() + " on " + server.getServerName() + ", " + r.flush(true)
157      + ", size=" + server.getRegionServerAccounting().getGlobalMemStoreDataSize());
158  }
159
160  private List<HRegionServer> getOnlineRegionServers() {
161    List<HRegionServer> list = new ArrayList<>();
162    for (JVMClusterUtil.RegionServerThread rst : cluster.getRegionServerThreads()) {
163      if (rst.getRegionServer().isOnline()) {
164        list.add(rst.getRegionServer());
165      }
166    }
167    return list;
168  }
169
170  /**
171   * Wait until all the regions are assigned.
172   */
173  private void waitForAllRegionsAssigned() throws IOException {
174    while (true) {
175      int regionCount = HBaseTestingUtil.getAllOnlineRegions(cluster).size();
176      if (regionCount >= totalRegionNum) break;
177      LOG.debug("Waiting for there to be " + totalRegionNum + " regions, but there are "
178        + regionCount + " right now.");
179      try {
180        Thread.sleep(100);
181      } catch (InterruptedException e) {
182      }
183    }
184  }
185}