🌟 Enterprise Feature 🌟 This feature is bundled with GraphQL-Enterprise.

Active Operation Limiter

GraphQL::Enterprise::ActiveOperationLimiter prevents clients from running too many GraphQL operations at the same time. It uses Redis to track currently-running operations.

Why?

Some clients may suddently swamp a server with tons of requests, occupying all available Ruby processes and therefore interrupting service for other clients. This limiter aims to prevent that at the GraphQL level by halting queries when a client already has lots of queries running. That way, server processes will remain available for other clients’ requests.

Setup

To use this limiter, update the schema configuration and include context[:limiter_key] in your queries.

Schema Setup

To setup the schema, add use GraphQL::Enterprise::ActiveOperationLimiter with a default limit: value:

class MySchema < GraphQL::Schema
  # ...
  use GraphQL::Enterprise::ActiveOperationLimiter,
    redis: Redis.new(...),
    limit: 5
end

limit: false may also be given, which defaults to no limit for this limiter.

It also accepts a stale_request_seconds: option. The limiter uses that value to clean up request data in case of a crash or other unexpected scenario.

Before requests will actually be halted, “soft mode” must be disabled as described below.

Query Setup

In order to limit clients, the limiter needs a client identifier for each GraphQL operation. By default, it checks context[:limiter_key] to find it:

context = {
  viewer: current_user,
  # for example:
  limiter_key: logged_in? ? "user:#{current_user.id}" : "anon-ip:#{request.remote_ip}",
  # ...
}

result = MySchema.execute(query_str, context: context)

Operations with the same context[:limiter_key] will rate limited in the same buckets. A limiter key is required; if a query is run without one, the limiter will raise an error.

To provide a client identifier another way, see Customization.

Soft Limits

By default, the limiter doesn’t actually halt queries; instead, it starts out in “soft mode”. In this mode:

This mode is for assessing the impact of the limiter before it’s applied to production traffic. Additionally, if you release the limiter but find that it’s affecting production traffic adversely, you can re-enable “soft mode” to stop blocking traffic.

To disable “soft mode” and start limiting, use the Dashboard or customize the limiter. You can also disable “soft mode” in Ruby:

# Turn "soft mode" off for the ActiveOperationLimiter
MySchema.enterprise_active_operation_limiter.set_soft_limit(false)

Dashboard

Once installed, your GraphQL-Pro dashboard will include a simple metrics view:

GraphQL Active Operation Limiter Dashboard

See Instrumentation below for more details on limiter metrics.

Also, the dashboard includes a link to enable or disable “soft mode”:

GraphQL Rate Limiter Soft Mode Button

When “soft mode” is enabled, limited requests are not actually halted (although they are counted). When “soft mode” is disabled, any over-limit requests are halted.

Customization

GraphQL::Enterprise::ActiveOperationLimiter provides several hooks for customizing its behavior. To use these, make a subclass of the limiter and override methods as described:

# app/graphql/limiters/active_operations.rb
class Limiters::ActiveOperations < GraphQL::Enterprise::ActiveOperationsLimiter
  # override methods here
end

The hooks are:

Instrumentation

While the limiter is installed, it adds some information to the query context about its operation. It can be acccessed at context[:active_operation_limiter]:

result = MySchema.execute(...)

pp result.context[:active_operation_limiter]
# {:key=>"user:123", :limit=>2, :soft=>false, :limited=>true}

It returns a Hash containing:

You could use this to add detailed metrics to your application monitoring system, for example:

MyMetrics.increment("graphql.active_operation_limiter", tags: result.context[:active_operation_limiter])