forked from prestodb/presto
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature Toggles should allow teams to modify system behavior without changing code. Feature Toggles are configured using google guice. Basic definition of toggles are crated using FeatureToggleBinder. FeatureToggleBinder creates FeatureToggle and additional configuration can be done using feature configuration. In current stage Feature Toggles supports: - if / else based feature toggles - Dependency Injection based Hot reloading implementation without restart require code refactoring to add an interface when injecting the new implementation/class - using various toggle strategies along with simple on / off toggles configuration: to allow feature toggle configuration four lines are needed in config.properties file ``` features.config-source-type=file features.config-source=/etc/feature-config.properties features.config-type=properties features.refresh-period=30s ``` `configuration-source-type` is source type for Feature Toggles configuration `features.config-source` is a source (file) of the configuration `features.config-type` format in which configuration is stored (json or properties) `features.refresh-period` configuration refresh period Defining Feature Toggles Feature toggle definition is done in google guice module using `FeatureToggleBinder` simple feature toggle definition ``` featureToggleBinder(binder) .featureId("featureXX") .bind() ``` This example creates bindings for @Inject ``` @Inject public Runner(@FeatureToggle("featureXX") Supplier<Boolean> isFeatureXXEnabled) { this.isFeatureXXEnabled = isFeatureXXEnabled; } ``` `isFeatureXXEnabled` can be used to test if feature is enabled or disabled: ``` boolean testFeatureXXEnabled() { return isFeatureXXEnabled.get(); } ``` hot reloadable feature toggle definition ``` featureToggleBinder(binder, Feature01.class) .featureId("feature01") .baseClass(Feature01.class) .defaultClass(Feature01Impl01.class) .allOf(Feature01Impl01.class, Feature01Impl02.class) .bind() ``` adding Feature Toggle switching strategy ``` featureToggleBinder(binder) .featureId("feature04") .toggleStrategy("AllowAll") .toggleStrategyConfig(ImmutableMap.of("key", "value", "key2", "value2")) ``` feature-config.properties file example ``` # feature query-logger feature.query-logger.enabled=true feature.query-logger.strategy=OsToggle feature.query-logger.strategy.os_name=.*Linux.* #feature.query-rate-limiter feature.query-rate-limiter.currentInstance=com.facebook.presto.server.protocol.QueryBlockingRateLimiter # feature.query-cancel feature.query-cancel.strategy=AllowList feature.query-cancel.strategy.allow-list-source=.*IDEA.* feature.query-cancel.strategy.allow-list-user=.*prestodb ``` in this example for first feature `query-logger` changing value of feature.query-logger.enabled to `false` will 'disable' this feature. Changes will be effective within refresh period. Pass column delimiter info to reader (prestodb#6338) Summary: Pull Request resolved: facebookincubator/velox#6338 Reviewed By: Yuhta Differential Revision: D48457913 fbshipit-source-id: 57d76dfa229de3801bf3181f780a485b628427ad
- Loading branch information
1 parent
759a30e
commit d5af879
Showing
22 changed files
with
421 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
features.config-source-type=file | ||
features.config-source=/home/bane/java/etc/feature-config.properties | ||
features.config-type=properties |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
...in/src/main/java/com/facebook/presto/server/protocol/AnotherQueryBlockingRateLimiter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* Licensed 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 com.facebook.presto.server.protocol; | ||
|
||
import com.facebook.airlift.stats.CounterStat; | ||
import com.facebook.airlift.stats.TimeStat; | ||
import com.facebook.presto.execution.QueryManagerConfig; | ||
import com.facebook.presto.spi.QueryId; | ||
import com.google.common.cache.CacheBuilder; | ||
import com.google.common.cache.CacheLoader; | ||
import com.google.common.cache.LoadingCache; | ||
import com.google.common.util.concurrent.ListenableFuture; | ||
import com.google.common.util.concurrent.ListeningExecutorService; | ||
import com.google.common.util.concurrent.RateLimiter; | ||
import com.google.inject.Inject; | ||
import io.airlift.units.Duration; | ||
import org.weakref.jmx.Managed; | ||
import org.weakref.jmx.Nested; | ||
|
||
import javax.annotation.PreDestroy; | ||
|
||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.LinkedBlockingQueue; | ||
import java.util.concurrent.ThreadPoolExecutor; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static com.facebook.airlift.concurrent.Threads.daemonThreadsNamed; | ||
import static com.google.common.util.concurrent.Futures.immediateFailedFuture; | ||
import static com.google.common.util.concurrent.Futures.immediateFuture; | ||
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; | ||
import static java.util.Objects.requireNonNull; | ||
|
||
/* | ||
* Rate Limiting per query with token bucket | ||
* Rate = rateLimitBucketMaxSize/second | ||
* When having sufficient tokens, Request will be responded immediately. | ||
* When not having enough tokens available, it uses the delayed processing method. | ||
*/ | ||
public class AnotherQueryBlockingRateLimiter | ||
implements QueryRateLimiter | ||
{ | ||
private final long rateLimiterBucketMaxSize; | ||
private final ListeningExecutorService rateLimiterExecutorService; | ||
private final LoadingCache<QueryId, RateLimiter> rateLimiterCache; | ||
private final CounterStat rateLimiterTriggeredCounter = new CounterStat(); | ||
private final TimeStat rateLimiterBlockTime = new TimeStat(); | ||
|
||
@Inject | ||
public AnotherQueryBlockingRateLimiter(QueryManagerConfig queryManagerConfig) | ||
{ | ||
requireNonNull(queryManagerConfig, "queryManagerConfig is null"); | ||
this.rateLimiterBucketMaxSize = queryManagerConfig.getRateLimiterBucketMaxSize(); | ||
// Using a custom thread pool with size 1-10 to reduce initial thread resources | ||
ExecutorService executorService = new ThreadPoolExecutor(1, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), daemonThreadsNamed("rate-limiter-listener")); | ||
rateLimiterExecutorService = listeningDecorator(executorService); | ||
rateLimiterCache = CacheBuilder.newBuilder().maximumSize(queryManagerConfig.getRateLimiterCacheLimit()).expireAfterAccess(queryManagerConfig.getRateLimiterCacheWindowMinutes(), TimeUnit.MINUTES).build(CacheLoader.from(key -> RateLimiter.create(rateLimiterBucketMaxSize))); | ||
} | ||
|
||
/* | ||
* For accidental bug-caused DoS, we will use delayed processing method to reduce the requests, even when user do not have back-off logic implemented | ||
* Optimized to avoid blocking for normal usages with TryRequire first | ||
* Fall back to delayed processing method to acquire a permit, in a separate thread pool | ||
* Internal guava rate limiter returns time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited, we use a future to wrap around that. | ||
*/ | ||
@Override | ||
public ListenableFuture<Double> acquire(QueryId queryId) | ||
{ // if rateLimitBucketMaxSize < 0, we disable rate limiting by returning immediately | ||
if (rateLimiterBucketMaxSize < 0) { | ||
return immediateFuture(0.0); | ||
} | ||
if (queryId == null) { | ||
return immediateFailedFuture(new IllegalArgumentException("queryId should not be null")); | ||
} | ||
RateLimiter rateLimiter = rateLimiterCache.getUnchecked(queryId); | ||
if (rateLimiter.tryAcquire()) { | ||
return immediateFuture(0.0); | ||
} | ||
ListenableFuture<Double> asyncTask = rateLimiterExecutorService.submit(() -> rateLimiter.acquire()); | ||
rateLimiterTriggeredCounter.update(1); | ||
return asyncTask; | ||
} | ||
|
||
@Managed | ||
@Nested | ||
public CounterStat getRateLimiterTriggeredCounter() | ||
{ | ||
return rateLimiterTriggeredCounter; | ||
} | ||
|
||
@Override | ||
public TimeStat getRateLimiterBlockTime() | ||
{ | ||
return rateLimiterBlockTime; | ||
} | ||
|
||
@Override | ||
public void addRateLimiterBlockTime(Duration duration) | ||
{ | ||
rateLimiterBlockTime.add(duration); | ||
} | ||
|
||
@PreDestroy | ||
public void destroy() | ||
{ | ||
rateLimiterExecutorService.shutdownNow(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.