All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.regionserver.throttle.StoreHotnessProtector Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show newest version
/*
 * 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.throttle;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.RegionTooBusyException;
import org.apache.hadoop.hbase.regionserver.Region;
import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.ClassSize;
import org.apache.yetus.audience.InterfaceAudience;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;

/**
 * StoreHotnessProtector is designed to help limit the concurrency of puts with dense columns, it
 * does best-effort to avoid exhausting all RS's handlers. When a lot of clients write requests with
 * dense (hundreds) columns to a Store at the same time, it will lead to blocking of RS because CSLM
 * degrades when concurrency goes up. It's not a kind of throttling. Throttling is user-oriented,
 * while StoreHotnessProtector is system-oriented, RS-self-protected mechanism.
 * 

* There are three key parameters: *

* 1. parallelPutToStoreThreadLimitCheckMinColumnCount: If the amount of columns exceed this * threshold, the HotProtector will work, 100 by default *

* 2. parallelPutToStoreThreadLimit: The amount of concurrency allowed to write puts to a Store at * the same time. *

* 3. parallelPreparePutToStoreThreadLimit: The amount of concurrency allowed to * prepare writing puts to a Store at the same time. *

* Notice that our writing pipeline includes three key process: MVCC acquire, writing MemStore, and * WAL. Only limit the concurrency of writing puts to Store(parallelPutToStoreThreadLimit) is not * enough since the actual concurrency of puts may still exceed the limit when MVCC contention or * slow WAL sync happens. This is why parallelPreparePutToStoreThreadLimit is needed. *

* This protector is enabled by default and could be turned off by setting * hbase.region.store.parallel.put.limit to 0, supporting online configuration change. */ @InterfaceAudience.Private public class StoreHotnessProtector { private static final Logger LOG = LoggerFactory.getLogger(StoreHotnessProtector.class); private volatile int parallelPutToStoreThreadLimit; private volatile int parallelPreparePutToStoreThreadLimit; public final static String PARALLEL_PUT_STORE_THREADS_LIMIT = "hbase.region.store.parallel.put.limit"; public final static String PARALLEL_PREPARE_PUT_STORE_MULTIPLIER = "hbase.region.store.parallel.prepare.put.multiplier"; private final static int DEFAULT_PARALLEL_PUT_STORE_THREADS_LIMIT = 10; private volatile int parallelPutToStoreThreadLimitCheckMinColumnCount; public final static String PARALLEL_PUT_STORE_THREADS_LIMIT_MIN_COLUMN_COUNT = "hbase.region.store.parallel.put.limit.min.column.count"; private final static int DEFAULT_PARALLEL_PUT_STORE_THREADS_LIMIT_MIN_COLUMN_NUM = 100; private final static int DEFAULT_PARALLEL_PREPARE_PUT_STORE_MULTIPLIER = 2; private final Map preparePutToStoreMap = new ConcurrentSkipListMap<>(Bytes.BYTES_RAWCOMPARATOR); private final Region region; public StoreHotnessProtector(Region region, Configuration conf) { init(conf); this.region = region; } public void init(Configuration conf) { this.parallelPutToStoreThreadLimit = conf.getInt(PARALLEL_PUT_STORE_THREADS_LIMIT, DEFAULT_PARALLEL_PUT_STORE_THREADS_LIMIT); this.parallelPreparePutToStoreThreadLimit = conf.getInt(PARALLEL_PREPARE_PUT_STORE_MULTIPLIER, DEFAULT_PARALLEL_PREPARE_PUT_STORE_MULTIPLIER) * parallelPutToStoreThreadLimit; this.parallelPutToStoreThreadLimitCheckMinColumnCount = conf.getInt(PARALLEL_PUT_STORE_THREADS_LIMIT_MIN_COLUMN_COUNT, DEFAULT_PARALLEL_PUT_STORE_THREADS_LIMIT_MIN_COLUMN_NUM); } public void update(Configuration conf) { init(conf); preparePutToStoreMap.clear(); LOG.debug("update config: " + toString()); } public void start(Map> familyMaps) throws RegionTooBusyException { if (!isEnable()) { return; } String tooBusyStore = null; for (Map.Entry> e : familyMaps.entrySet()) { Store store = this.region.getStore(e.getKey()); if (store == null || e.getValue() == null) { continue; } if (e.getValue().size() > this.parallelPutToStoreThreadLimitCheckMinColumnCount) { //we need to try to add #preparePutCount at first because preparePutToStoreMap will be //cleared when changing the configuration. preparePutToStoreMap.putIfAbsent(e.getKey(), new AtomicInteger()); AtomicInteger preparePutCounter = preparePutToStoreMap.get(e.getKey()); if (preparePutCounter == null) { preparePutCounter = new AtomicInteger(); preparePutToStoreMap.putIfAbsent(e.getKey(), preparePutCounter); } int preparePutCount = preparePutCounter.incrementAndGet(); if (store.getCurrentParallelPutCount() > this.parallelPutToStoreThreadLimit || preparePutCount > this.parallelPreparePutToStoreThreadLimit) { tooBusyStore = (tooBusyStore == null ? store.getColumnFamilyName() : tooBusyStore + "," + store.getColumnFamilyName()); } if (LOG.isTraceEnabled()) { LOG.trace(store.getColumnFamilyName() + ": preparePutCount=" + preparePutCount + "; currentParallelPutCount=" + store.getCurrentParallelPutCount()); } } } if (tooBusyStore != null) { String msg = "StoreTooBusy," + this.region.getRegionInfo().getRegionNameAsString() + ":" + tooBusyStore + " Above parallelPutToStoreThreadLimit(" + this.parallelPutToStoreThreadLimit + ")"; if (LOG.isTraceEnabled()) { LOG.trace(msg); } throw new RegionTooBusyException(msg); } } public void finish(Map> familyMaps) { if (!isEnable()) { return; } for (Map.Entry> e : familyMaps.entrySet()) { Store store = this.region.getStore(e.getKey()); if (store == null || e.getValue() == null) { continue; } if (e.getValue().size() > this.parallelPutToStoreThreadLimitCheckMinColumnCount) { AtomicInteger counter = preparePutToStoreMap.get(e.getKey()); // preparePutToStoreMap will be cleared when changing the configuration, so it may turn // into a negative value. It will be not accuracy in a short time, it's a trade-off for // performance. if (counter != null && counter.decrementAndGet() < 0) { counter.incrementAndGet(); } } } } public String toString() { return "StoreHotnessProtector, parallelPutToStoreThreadLimit=" + this.parallelPutToStoreThreadLimit + " ; minColumnNum=" + this.parallelPutToStoreThreadLimitCheckMinColumnCount + " ; preparePutThreadLimit=" + this.parallelPreparePutToStoreThreadLimit + " ; hotProtect now " + (this.isEnable() ? "enable" : "disable"); } public boolean isEnable() { // feature is enabled when parallelPutToStoreThreadLimit > 0 return this.parallelPutToStoreThreadLimit > 0; } @VisibleForTesting Map getPreparePutToStoreMap() { return preparePutToStoreMap; } public static final long FIXED_SIZE = ClassSize.align(ClassSize.OBJECT + 2 * ClassSize.REFERENCE + 3 * Bytes.SIZEOF_INT); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy