Rate limits 

To ensure fair use across the platform, Gadget limits the number of requests an app can run in a given time window. Each environment of each application has independent rate limits. Rate limits can be raised by upgrading your plan, or contacting Gadget support to discuss custom limits.

Each environment is subject to two limits:

  • a request rate limit, which limits the total number of requests processed by an environment per 10-second window
  • a database rate limit, which limits the total number of seconds spent running database queries by an environment per 10-second window

Rate limits are different for each environment, and production environments have much higher rate limits than development environments.

Request rate limit 

The request rate limit is enforced at your app's API layer, and is enforced for all requests to the API. This includes requests made from the outside world, like from your app's frontend, as well as requests made from your app's backend code.

The request rate limit is enforced using a leaky bucket algorithm, which means that the environment can burst up to the request rate limit, but will be throttled if it exceeds the request rate limit.

Each request your application makes to your app's routes, Public API, or Internal API will consume one unit of the request rate limit. This includes internal operations made within your app's code. For example, if you run this model action:

JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ api, record, params }) => { await applyParams(record, params); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ api, record, params }) => { // find and delete all children of the record const children = await api.child.findMany({ parent: record.id, }); for (const child of children) { await api.child.delete(child.id); } };
import { applyParams, save, ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ api, record, params }) => { await applyParams(record, params); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ api, record, params }) => { // find and delete all children of the record const children = await api.child.findMany({ parent: record.id, }); for (const child of children) { await api.child.delete(child.id); } };

This action will consume:

  • 1 unit of the request rate limit for the request to start the action
  • 1 unit of the request rate limit for the save call to persist the record, as it calls the Internal API to persist the record
  • 1 unit of the request rate limit for the findMany call to find the child records, as it calls the Public API to find the child records
  • 1 unit of the request rate limit for each child record that is deleted, as it calls the Public API to delete the child record

Request rate limit exemptions 

Some requests are not subject to the request rate limit, and have no direct impact on your app's rate limit usage.

These requests are not rate-limited:

  • webhook requests made by external systems like Shopify or BigCommerce to Gadget's plugins. This includes:
    • requests to the /api/webhooks/shopify endpoint
    • requests to the /api/webhooks/bigcommerce endpoint
  • requests made to enqueue background actions via api.enqueue.
  • requests to content served via your app's CDN. This includes:
    • all requests to app-assets.gadget.dev for frontend JS and CSS assets
    • all requests to storage.gadget.dev for files stored in file fields
    • all requests to paths like /api/assets/... for frontend JS and CSS assets
  • for development environments, requests to content served via your app's Vite dev server. This includes:
    • vite JS and CSS assets served at paths like /.vite/... and /@vite/...

Other requests are subject to the request rate limit, and will be throttled if the environment exceeds the request rate limit.

Background action rate limit consumption 

Background actions consume one unit of request rate limit when they start, and then consume request rate limit units for each request they make to the Public or Internal API for your application. This means actions have the same rate limit consumption regardless of whether they are run in the foreground (like api.someAction()) or background (like api.enqueue(api.someAction)).

Background action throttling 

Gadget automatically throttles background actions so they don't consume your environment's entire rate limit. If you enqueue more actions than your environment's rate limit allows, Gadget will adjust the max concurrency for your app to consume most, but not all, of your rate limit. This way, your queues make the most progress possible, but you shouldn't see extra 429 errors or excessive retries.

Occasionally, if the nature of your app's running background actions changes abruptly, Gadget's rate limit system may not be able to adjust your background concurrency immediately to compensate. When this happens, running background actions may temporarily exhaust the full rate limit. All clients will get 429 errors when this happens. Gadget's built-in API client (the api object) will automatically retry these 429 errors from rate limit exhaustion up to 5 times, with a delay between retries, so there will be a short delay in action completion, but no errors. Other clients, like foreground requests or requests to HTTP routes may need to implement the same retry logic.

Note that the request rate limit impacts the execution of background actions, not the enqueuing of background actions. Enqueueing background actions is not rate-limited.

JavaScript
// will charge 1 unit of the request rate limit for running the action await api.someAction(); // will not charge any rate limit for enqueueing the action await api.enqueue(api.someAction);
// will charge 1 unit of the request rate limit for running the action await api.someAction(); // will not charge any rate limit for enqueueing the action await api.enqueue(api.someAction);

Database rate limit 

The database rate limit is enforced at the database layer, and is enforced for all database queries run by an environment. The database rate limit ensures no one application can use more than it's fair share of the available database resources. It is enforced using a leaky bucket algorithm, which means that the environment can burst up to the database rate limit, but will be throttled if it exceeds the database rate limit.

Database rate limit consumption is measured in milliseconds, and is charged for the duration of the database query. For each database query that your application runs, Gadget times the query, and deducts that duration from the database rate limit.

Dedicated databases are available on enterprise Gadget plans, and are exempt from any database rate limits.

There are no queries that are exempt from the database rate limit.

Limiting 429 errors from rate limit exhaustion 

If you are seeing too many 429 errors from rate limit exhaustion, you must reduce the load on your application to lower your rate limit consumption, or upgrade your plan to raise your rate limits.

You can reduce the request rate limit load on your application by:

  • reducing the number of requests your frontend application makes to your app's routes, Public API, or Internal API
  • merging multiple requests into a single request by using the bulk actions, custom global actions, or HTTP routes
  • enqueueing actions to run in the background, where they are throttled to consume only a portion of your rate limit
  • enqueueing actions using a concurrency queue, where you can control the maximum concurrency of actions that can run at the same time

You can reduce the database rate limit load on your application by:

  • excluding computed fields from your select: parameters when loading records
  • reducing the number of computed fields selected or computed view queries your application makes, as these are often the biggest consumers of database rate limit
  • pre-aggregating data in model fields to reduce the number of read-time computations your application makes

Leaky bucket algorithm 

All Gadget rate limits use a leaky bucket algorithm to define capacity. The leaky bucket is a metaphor for a bucket that has a fixed size, is filled by requests, and is drained at a fixed rate. More requests can only be made if there is room in the bucket.

For example:

  • Your environment's app has access to a bucket. It can hold, say, 60 "marbles" (requests).
  • Each API request that arrives adds a marble into the bucket.
  • Each second, a marble is removed from the bucket (if there are any). This is the drain rate -- it restores capacity for more marbles.
  • If the bucket gets full, you get a 429 Rate Limit Exceeded error, and you must wait for more bucket capacity to become available.

This model ensures that apps that manage API calls responsibly can maintain capacity to make bursts of requests when needed. For example, if you average 20 requests ("marbles") per second but suddenly need to make 30 requests all at once, you can still do so without hitting your rate limit.

Was this page helpful?