Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rate limiter #278

Merged
merged 22 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d34ac04
Add a rate limiter
andrew4699 Sep 10, 2024
48d0f49
More javadoc
andrew4699 Sep 10, 2024
1734c78
ratelimiting => ratelimiter, general clean up
andrew4699 Sep 10, 2024
00d08c1
Add new line EOFs, add RateLimiterConfig javadoc
andrew4699 Sep 10, 2024
194d754
Update javadoc, tryAcquire
andrew4699 Sep 12, 2024
a73ff08
More docs, remove Thread.sleep, Caffeine => Map, CompletableFuture =>…
andrew4699 Sep 13, 2024
6a22321
Change Map value to be a Future itself, use System.nanoTime
andrew4699 Sep 16, 2024
617e891
Implement our own rate limiter
andrew4699 Sep 16, 2024
303c63c
Delete unused OpenTelemetryClock
andrew4699 Sep 16, 2024
b43d68b
Add javadoc for TokenBucketRateLimiter
andrew4699 Sep 16, 2024
514a2ab
Change NoOpRateLimiterFactory to use completedFuture
andrew4699 Sep 18, 2024
7ce9662
Update to use PolarisRealm annotation after rebase
andrew4699 Sep 18, 2024
57576ab
Update comment
andrew4699 Sep 18, 2024
d25f5d7
Update comment and change some doubles to longs
andrew4699 Sep 19, 2024
bc455bb
Apply spotless, make RateLimitResultAsserter re-usable
andrew4699 Sep 19, 2024
1652fe2
Use InstantSource, RealmContext key, general cleanup
andrew4699 Sep 21, 2024
4be8f12
Various cleanup such as overflow guards, RateLimiterKey
andrew4699 Sep 23, 2024
f00b9fc
lastAcquireMillis => lastTokenGenerationMillis
andrew4699 Sep 23, 2024
a10a18e
Remove debug line
andrew4699 Sep 23, 2024
c6a3283
Greatly simplify by removing RateLimiterFactory/async stuff
andrew4699 Sep 23, 2024
9ff2a68
Clean up
andrew4699 Sep 23, 2024
948683b
Remove sleep from tests
andrew4699 Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions polaris-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,9 @@ logging:

# Limits the size of request bodies sent to Polaris. -1 means no limit.
maxRequestBodyBytes: -1

rateLimiter:
flyrain marked this conversation as resolved.
Show resolved Hide resolved
constructionTimeoutMillis: 2000
allowRequestOnConstructionTimeout: true
factory:
type: no-op
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
import org.apache.polaris.service.context.PolarisCallContextCatalogFactory;
import org.apache.polaris.service.context.RealmContextResolver;
import org.apache.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory;
import org.apache.polaris.service.ratelimiter.RateLimiterFilter;
import org.apache.polaris.service.storage.PolarisStorageIntegrationProviderImpl;
import org.apache.polaris.service.task.ManifestFileCleanupTaskHandler;
import org.apache.polaris.service.task.TableCleanupTaskHandler;
Expand Down Expand Up @@ -252,6 +253,12 @@ public void run(PolarisApplicationConfig configuration, Environment environment)
.servlets()
.addFilter("tracing", new TracingFilter(openTelemetry))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

environment
.servlets()
.addFilter("ratelimiter", new RateLimiterFilter(configuration.getRateLimiterConfig()))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved

DiscoverableAuthenticator<String, AuthenticatedPolarisPrincipal> authenticator =
configuration.getPolarisAuthenticator();
authenticator.setEntityManagerFactory(entityManagerFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.polaris.service.catalog.FileIOFactory;
import org.apache.polaris.service.context.CallContextResolver;
import org.apache.polaris.service.context.RealmContextResolver;
import org.apache.polaris.service.ratelimiter.RateLimiterConfig;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
Expand All @@ -55,6 +56,7 @@ public class PolarisApplicationConfig extends Configuration {
private String awsAccessKey;
private String awsSecretKey;
private FileIOFactory fileIOFactory;
private RateLimiterConfig rateLimiterConfig;

public static final long REQUEST_BODY_BYTES_NO_LIMIT = -1;
private long maxRequestBodyBytes = REQUEST_BODY_BYTES_NO_LIMIT;
Expand Down Expand Up @@ -137,6 +139,16 @@ public void setCorsConfiguration(CorsConfiguration corsConfiguration) {
this.corsConfiguration = corsConfiguration;
}

@JsonProperty("rateLimiter")
public RateLimiterConfig getRateLimiterConfig() {
return rateLimiterConfig;
}

@JsonProperty("rateLimiter")
public void setRateLimiterConfig(RateLimiterConfig rateLimiterConfig) {
this.rateLimiterConfig = rateLimiterConfig;
}

public void setTaskHandler(TaskHandlerConfiguration taskHandler) {
this.taskHandler = taskHandler;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.polaris.service.ratelimiter;

/** An interface to specify how to retrieve the current wall clock time */
public interface Clock {
/**
* @return the current time in nanoseconds
*/
long nanoTime();
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.polaris.service.ratelimiter;

/** An implementation of our Clock interface that just uses System.nanoTime() */
public class ClockImpl implements Clock {
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
public ClockImpl() {}

@Override
public long nanoTime() {
return System.nanoTime();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.polaris.service.ratelimiter;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

/**
* Simple rate limiter factory that just constructs a TokenBucketRateLimiter and is indiscriminate
* of the key
*/
@JsonTypeName("default")
public class DefaultRateLimiterFactory implements RateLimiterFactory {
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
@JsonProperty("requestsPerSecond")
private long requestsPerSecond;

@JsonProperty("windowSeconds")
private long windowSeconds;

@Override
public Future<RateLimiter> createRateLimiter(String key) {
return CompletableFuture.completedFuture(
new TokenBucketRateLimiter(
requestsPerSecond, requestsPerSecond * windowSeconds, new ClockImpl()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.polaris.service.ratelimiter;

/** Rate limiter that always allows the request */
public class NoOpRateLimiter implements RateLimiter {
@Override
public boolean tryAcquire() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.polaris.service.ratelimiter;

import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

/** Rate limiter factory that constructs a no-op rate limiter */
@JsonTypeName("no-op")
public class NoOpRateLimiterFactory implements RateLimiterFactory {
@Override
public Future<RateLimiter> createRateLimiter(String key) {
return CompletableFuture.completedFuture(new NoOpRateLimiter());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.polaris.service.ratelimiter;

/** Interface for rate limiting requests */
public interface RateLimiter {
/**
* This signifies that a request is being made. That is, the rate limiter should count the request
* at this point.
*
* @return Whether the request is allowed to proceed by the rate limiter
*/
boolean tryAcquire();
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.polaris.service.ratelimiter;

import com.fasterxml.jackson.annotation.JsonProperty;

/** Configuration for the rate limiter */
public class RateLimiterConfig {
private RateLimiterFactory rateLimiterFactory;

/**
* Rate limiters can be constructed asynchronously, so this config determines the construction
* timeout before we default to a NoOpRateLimiter.
*/
private long constructionTimeoutMillis;

/**
* Since rate limiter construction is asynchronous and has a timeout, construction may fail. If
* this option is enabled, the request will still be allowed when construction fails.
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
*/
private boolean allowRequestOnConstructionTimeout;

@JsonProperty("factory")
public void setRateLimiterFactory(RateLimiterFactory rateLimiterFactory) {
this.rateLimiterFactory = rateLimiterFactory;
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
}

@JsonProperty("factory")
public RateLimiterFactory getRateLimiterFactory() {
return rateLimiterFactory;
}

@JsonProperty("constructionTimeoutMillis")
public void setConstructionTimeoutMillis(long constructionTimeoutMillis) {
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
this.constructionTimeoutMillis = constructionTimeoutMillis;
}

@JsonProperty("constructionTimeoutMillis")
public long getConstructionTimeoutMillis() {
return constructionTimeoutMillis;
}

@JsonProperty("allowRequestOnConstructionTimeout")
public void setAllowRequestOnConstructionTimeout(boolean allowRequestOnConstructionTimeout) {
this.allowRequestOnConstructionTimeout = allowRequestOnConstructionTimeout;
}

@JsonProperty("allowRequestOnConstructionTimeout")
public boolean getAllowRequestOnConstructionTimeout() {
return allowRequestOnConstructionTimeout;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.polaris.service.ratelimiter;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.dropwizard.jackson.Discoverable;
import java.util.concurrent.Future;

/**
* Interface for constructing a rate limiter given the rate limiting key and clock. Notably, rate
* limiter construction may be asynchronous. This allows fetching information related to the key.
* For example, implementors may wish to fetch per-account rate limit values, which require lookup
* in an external database.
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
public interface RateLimiterFactory extends Discoverable {
/**
* Constructs a rate limiter asynchronously. Callers may choose to set a timeout on construction.
*
* @param key The rate limiting key. Rate limiters may optionally choose to discriminate their
* behavior by the key.
* @return a Future with the constructed RateLimiter
*/
Future<RateLimiter> createRateLimiter(String key);
andrew4699 marked this conversation as resolved.
Show resolved Hide resolved
}
Loading