Add autotune and #getBytesPerSecond() to RocksJava RateLimiter

Summary: Closes https://github.com/facebook/rocksdb/pull/3332

Differential Revision: D6667680

Pulled By: ajkr

fbshipit-source-id: b2bb6889257850a4eb6f6cbd7106f62df7b82730
This commit is contained in:
Adam Retter 2018-01-08 12:20:48 -08:00 committed by Facebook Github Bot
parent 30a017feca
commit 398d72fa61
5 changed files with 263 additions and 31 deletions

View File

@ -21,6 +21,7 @@
#include "rocksdb/db.h"
#include "rocksdb/filter_policy.h"
#include "rocksdb/rate_limiter.h"
#include "rocksdb/status.h"
#include "rocksdb/utilities/backupable_db.h"
#include "rocksdb/utilities/write_batch_with_index.h"
@ -2934,6 +2935,45 @@ class StatsLevelJni {
}
};
// The portal class for org.rocksdb.RateLimiterMode
class RateLimiterModeJni {
public:
// Returns the equivalent org.rocksdb.RateLimiterMode for the provided
// C++ rocksdb::RateLimiter::Mode enum
static jbyte toJavaRateLimiterMode(
const rocksdb::RateLimiter::Mode& rate_limiter_mode) {
switch(rate_limiter_mode) {
case rocksdb::RateLimiter::Mode::kReadsOnly:
return 0x0;
case rocksdb::RateLimiter::Mode::kWritesOnly:
return 0x1;
case rocksdb::RateLimiter::Mode::kAllIo:
return 0x2;
default:
// undefined/default
return 0x1;
}
}
// Returns the equivalent C++ rocksdb::RateLimiter::Mode enum for the
// provided Java org.rocksdb.RateLimiterMode
static rocksdb::RateLimiter::Mode toCppRateLimiterMode(jbyte jrate_limiter_mode) {
switch(jrate_limiter_mode) {
case 0x0:
return rocksdb::RateLimiter::Mode::kReadsOnly;
case 0x1:
return rocksdb::RateLimiter::Mode::kWritesOnly;
case 0x2:
return rocksdb::RateLimiter::Mode::kAllIo;
default:
// undefined/default
return rocksdb::RateLimiter::Mode::kWritesOnly;
}
}
};
// various utility functions for working with RocksDB and JNI
class JniUtil {
public:

View File

@ -12,16 +12,21 @@
/*
* Class: org_rocksdb_RateLimiter
* Method: newRateLimiterHandle
* Signature: (JJI)J
* Signature: (JJIBZ)J
*/
jlong Java_org_rocksdb_RateLimiter_newRateLimiterHandle(
JNIEnv* env, jclass jclazz, jlong jrate_bytes_per_second,
jlong jrefill_period_micros, jint jfairness) {
jlong jrefill_period_micros, jint jfairness, jbyte jrate_limiter_mode,
jboolean jauto_tune) {
auto rate_limiter_mode = rocksdb::RateLimiterModeJni::toCppRateLimiterMode(
jrate_limiter_mode);
auto * sptr_rate_limiter =
new std::shared_ptr<rocksdb::RateLimiter>(rocksdb::NewGenericRateLimiter(
static_cast<int64_t>(jrate_bytes_per_second),
static_cast<int64_t>(jrefill_period_micros),
static_cast<int32_t>(jfairness)));
static_cast<int32_t>(jfairness),
rate_limiter_mode,
jauto_tune));
return reinterpret_cast<jlong>(sptr_rate_limiter);
}
@ -50,6 +55,17 @@ void Java_org_rocksdb_RateLimiter_setBytesPerSecond(
SetBytesPerSecond(jbytes_per_second);
}
/*
* Class: org_rocksdb_RateLimiter
* Method: getBytesPerSecond
* Signature: (J)J
*/
jlong Java_org_rocksdb_RateLimiter_getBytesPerSecond(
JNIEnv* env, jobject jobj, jlong handle) {
return reinterpret_cast<std::shared_ptr<rocksdb::RateLimiter> *>(handle)->get()->
GetBytesPerSecond();
}
/*
* Class: org_rocksdb_RateLimiter
* Method: request

View File

@ -12,8 +12,11 @@ package org.rocksdb;
* @since 3.10.0
*/
public class RateLimiter extends RocksObject {
private static final long DEFAULT_REFILL_PERIOD_MICROS = (100 * 1000);
private static final int DEFAULT_FAIRNESS = 10;
public static final long DEFAULT_REFILL_PERIOD_MICROS = 100 * 1000;
public static final int DEFAULT_FAIRNESS = 10;
public static final RateLimiterMode DEFAULT_MODE =
RateLimiterMode.WRITES_ONLY;
public static final boolean DEFAULT_AUTOTUNE = false;
/**
* RateLimiter constructor
@ -21,24 +24,12 @@ public class RateLimiter extends RocksObject {
* @param rateBytesPerSecond this is the only parameter you want to set
* most of the time. It controls the total write rate of compaction
* and flush in bytes per second. Currently, RocksDB does not enforce
* rate limit for anything other than flush and compaction, e.g. write to WAL.
* @param refillPeriodMicros this controls how often tokens are refilled. For example,
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
* 100ms, then 1MB is refilled every 100ms internally. Larger value can lead to
* burstier writes while smaller value introduces more CPU overhead.
* The default should work for most cases.
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
* A low-pri request is usually blocked in favor of hi-pri request. Currently,
* RocksDB assigns low-pri to request from compaction and high-pri to request
* from flush. Low-pri requests can get blocked if flush requests come in
* continuously. This fairness parameter grants low-pri requests permission by
* fairness chance even though high-pri requests exist to avoid starvation.
* You should be good by leaving it at default 10.
* rate limit for anything other than flush and compaction, e.g. write to
* WAL.
*/
public RateLimiter(final long rateBytesPerSecond,
final long refillPeriodMicros, final int fairness) {
super(newRateLimiterHandle(rateBytesPerSecond,
refillPeriodMicros, fairness));
public RateLimiter(final long rateBytesPerSecond) {
this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS,
DEFAULT_MODE, DEFAULT_AUTOTUNE);
}
/**
@ -47,10 +38,115 @@ public class RateLimiter extends RocksObject {
* @param rateBytesPerSecond this is the only parameter you want to set
* most of the time. It controls the total write rate of compaction
* and flush in bytes per second. Currently, RocksDB does not enforce
* rate limit for anything other than flush and compaction, e.g. write to WAL.
* rate limit for anything other than flush and compaction, e.g. write to
* WAL.
* @param refillPeriodMicros this controls how often tokens are refilled. For
* example,
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
* lead to burstier writes while smaller value introduces more CPU
* overhead. The default of 100,000ms should work for most cases.
*/
public RateLimiter(final long rateBytesPerSecond) {
this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS);
public RateLimiter(final long rateBytesPerSecond,
final long refillPeriodMicros) {
this(rateBytesPerSecond, refillPeriodMicros, DEFAULT_FAIRNESS, DEFAULT_MODE,
DEFAULT_AUTOTUNE);
}
/**
* RateLimiter constructor
*
* @param rateBytesPerSecond this is the only parameter you want to set
* most of the time. It controls the total write rate of compaction
* and flush in bytes per second. Currently, RocksDB does not enforce
* rate limit for anything other than flush and compaction, e.g. write to
* WAL.
* @param refillPeriodMicros this controls how often tokens are refilled. For
* example,
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
* lead to burstier writes while smaller value introduces more CPU
* overhead. The default of 100,000ms should work for most cases.
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
* A low-pri request is usually blocked in favor of hi-pri request.
* Currently, RocksDB assigns low-pri to request from compaction and
* high-pri to request from flush. Low-pri requests can get blocked if
* flush requests come in continuously. This fairness parameter grants
* low-pri requests permission by fairness chance even though high-pri
* requests exist to avoid starvation.
* You should be good by leaving it at default 10.
*/
public RateLimiter(final long rateBytesPerSecond,
final long refillPeriodMicros, final int fairness) {
this(rateBytesPerSecond, refillPeriodMicros, fairness, DEFAULT_MODE,
DEFAULT_AUTOTUNE);
}
/**
* RateLimiter constructor
*
* @param rateBytesPerSecond this is the only parameter you want to set
* most of the time. It controls the total write rate of compaction
* and flush in bytes per second. Currently, RocksDB does not enforce
* rate limit for anything other than flush and compaction, e.g. write to
* WAL.
* @param refillPeriodMicros this controls how often tokens are refilled. For
* example,
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
* lead to burstier writes while smaller value introduces more CPU
* overhead. The default of 100,000ms should work for most cases.
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
* A low-pri request is usually blocked in favor of hi-pri request.
* Currently, RocksDB assigns low-pri to request from compaction and
* high-pri to request from flush. Low-pri requests can get blocked if
* flush requests come in continuously. This fairness parameter grants
* low-pri requests permission by fairness chance even though high-pri
* requests exist to avoid starvation.
* You should be good by leaving it at default 10.
* @param rateLimiterMode indicates which types of operations count against
* the limit.
*/
public RateLimiter(final long rateBytesPerSecond,
final long refillPeriodMicros, final int fairness,
final RateLimiterMode rateLimiterMode) {
this(rateBytesPerSecond, refillPeriodMicros, fairness, rateLimiterMode,
DEFAULT_AUTOTUNE);
}
/**
* RateLimiter constructor
*
* @param rateBytesPerSecond this is the only parameter you want to set
* most of the time. It controls the total write rate of compaction
* and flush in bytes per second. Currently, RocksDB does not enforce
* rate limit for anything other than flush and compaction, e.g. write to
* WAL.
* @param refillPeriodMicros this controls how often tokens are refilled. For
* example,
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
* lead to burstier writes while smaller value introduces more CPU
* overhead. The default of 100,000ms should work for most cases.
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
* A low-pri request is usually blocked in favor of hi-pri request.
* Currently, RocksDB assigns low-pri to request from compaction and
* high-pri to request from flush. Low-pri requests can get blocked if
* flush requests come in continuously. This fairness parameter grants
* low-pri requests permission by fairness chance even though high-pri
* requests exist to avoid starvation.
* You should be good by leaving it at default 10.
* @param rateLimiterMode indicates which types of operations count against
* the limit.
* @param autoTune Enables dynamic adjustment of rate limit within the range
* {@code [rate_bytes_per_sec / 20, rate_bytes_per_sec]}, according to
* the recent demand for background I/O.
*/
public RateLimiter(final long rateBytesPerSecond,
final long refillPeriodMicros, final int fairness,
final RateLimiterMode rateLimiterMode, final boolean autoTune) {
super(newRateLimiterHandle(rateBytesPerSecond,
refillPeriodMicros, fairness, rateLimiterMode.getValue(), autoTune));
}
/**
@ -64,6 +160,16 @@ public class RateLimiter extends RocksObject {
setBytesPerSecond(nativeHandle_, bytesPerSecond);
}
/**
* Returns the bytes per second.
*
* @return bytes per second.
*/
public long getBytesPerSecond() {
assert(isOwningHandle());
return getBytesPerSecond(nativeHandle_);
}
/**
* <p>Request for token to write bytes. If this request can not be satisfied,
* the call is blocked. Caller is responsible to make sure
@ -107,11 +213,13 @@ public class RateLimiter extends RocksObject {
}
private static native long newRateLimiterHandle(final long rateBytesPerSecond,
final long refillPeriodMicros, final int fairness);
final long refillPeriodMicros, final int fairness,
final byte rateLimiterMode, final boolean autoTune);
@Override protected final native void disposeInternal(final long handle);
private native void setBytesPerSecond(final long handle,
final long bytesPerSecond);
private native long getBytesPerSecond(final long handle);
private native void request(final long handle, final long bytes);
private native long getSingleBurstBytes(final long handle);
private native long getTotalBytesThrough(final long handle);

View File

@ -0,0 +1,52 @@
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
package org.rocksdb;
/**
* Mode for {@link RateLimiter#RateLimiter(long, long, int, RateLimiterMode)}.
*/
public enum RateLimiterMode {
READS_ONLY((byte)0x0),
WRITES_ONLY((byte)0x1),
ALL_IO((byte)0x2);
private final byte value;
RateLimiterMode(final byte value) {
this.value = value;
}
/**
* <p>Returns the byte value of the enumerations value.</p>
*
* @return byte representation
*/
public byte getValue() {
return value;
}
/**
* <p>Get the RateLimiterMode enumeration value by
* passing the byte identifier to this method.</p>
*
* @param byteIdentifier of RateLimiterMode.
*
* @return AccessHint instance.
*
* @throws IllegalArgumentException if the access hint for the byteIdentifier
* cannot be found
*/
public static RateLimiterMode getRateLimiterMode(final byte byteIdentifier) {
for (final RateLimiterMode rateLimiterMode : RateLimiterMode.values()) {
if (rateLimiterMode.getValue() == byteIdentifier) {
return rateLimiterMode;
}
}
throw new IllegalArgumentException(
"Illegal value provided for RateLimiterMode.");
}
}

View File

@ -8,6 +8,7 @@ import org.junit.ClassRule;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.rocksdb.RateLimiter.*;
public class RateLimiterTest {
@ -16,17 +17,21 @@ public class RateLimiterTest {
new RocksMemoryResource();
@Test
public void setBytesPerSecond() {
public void bytesPerSecond() {
try(final RateLimiter rateLimiter =
new RateLimiter(1000, 100 * 1000, 1)) {
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0);
rateLimiter.setBytesPerSecond(2000);
assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0);
}
}
@Test
public void getSingleBurstBytes() {
try(final RateLimiter rateLimiter =
new RateLimiter(1000, 100 * 1000, 1)) {
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
assertThat(rateLimiter.getSingleBurstBytes()).isEqualTo(100);
}
}
@ -34,7 +39,8 @@ public class RateLimiterTest {
@Test
public void getTotalBytesThrough() {
try(final RateLimiter rateLimiter =
new RateLimiter(1000, 100 * 1000, 1)) {
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
assertThat(rateLimiter.getTotalBytesThrough()).isEqualTo(0);
}
}
@ -42,8 +48,18 @@ public class RateLimiterTest {
@Test
public void getTotalRequests() {
try(final RateLimiter rateLimiter =
new RateLimiter(1000, 100 * 1000, 1)) {
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
assertThat(rateLimiter.getTotalRequests()).isEqualTo(0);
}
}
@Test
public void autoTune() {
try(final RateLimiter rateLimiter =
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
DEFAULT_FAIRNESS, DEFAULT_MODE, true)) {
assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0);
}
}
}