Limits
Every Lakebed deploy runs with resource limits. This page lists the values a default deploy gets, the error you see when you cross one, and what to change in your capsule.
The numbers below are free-plan defaults. Claiming a deploy does not raise the free-plan limits. Paid plans can have higher values.
Default limits
| Limit | Default | When you hit it | What to do |
|---|---|---|---|
| Requests per deploy per day | 10000 | HTTP 429 with Retry-After and X-RateLimit-* headers. Body has code: "lakebed_quota_exceeded" and bucket: "requests". | Wait for the reset at 00:00 UTC, or cut round trips. One subscription that stays open beats polling. |
| Mutations per deploy per day | 1000 | HTTP 429, bucket: "mutations". Mutations and actions use this quota and also count as requests. | Batch writes into one mutation instead of one call per row. |
| Rows returned per handler | 1000 | Error is collect() exceeded the 1000 row return limit. Use paginate(). for one collection, or Database rowsReturned limit exceeded (1000). for combined reads. | Use paginate({ cursor, numItems }) instead of collect(), or take(n) with a small n. |
| Rows read per handler | 5000 | Handler throws Database rowsRead limit exceeded (5000). | Add an index and use withIndex so the query stops scanning rows it throws away. |
| Bytes read per handler | 4 MiB | Handler throws Database bytesRead limit exceeded (4194304). | Return fewer rows, or move large text into object storage. |
Direct get calls per handler | 1000 | Handler throws Database directGets limit exceeded (1000). | Replace per-row get loops with one indexed query. |
| Index scans per handler | 100 | Handler throws Database scanCalls limit exceeded (100). | Fold repeated queries into a single indexed range. |
| Writes per handler | 1000 | Handler throws Database writes limit exceeded (1000). | Split the work across several mutations. |
| Bytes per field value | 64 KiB | Write throws Value for <table>.<field> exceeds 65536 bytes. | Upload the payload with client.storage.upload and keep the returned key in the row. |
| Rows stored per deploy | 16384 | Mutation commit fails with a 429, bucket: "state_rows". | Delete rows you no longer need. |
| Bytes stored per deploy | 1 MiB | Mutation commit fails with a 429, bucket: "state_bytes". | Delete rows, or shorten stored values. |
| Transaction time | 5000 ms | The transaction is cancelled and rolls back. | Do less per handler. Move slow work to an action. |
| Single statement time | 4500 ms | The statement is cancelled and the transaction rolls back. | Add an index for the query. |
| Row lock wait | 1000 ms | The write fails and rolls back when another write holds the row. | Retry, and avoid many clients writing the same row at once. |
| Action arguments | 16 KiB | Deploy log shows Action arguments exceed 16384 bytes. | Pass an id and read the data inside the action. |
| Action result | 48 KiB | Deploy log shows Action result exceeds 49152 bytes. | Return only the fields the client needs. |
| Action runtime | 5000 ms | The action is cancelled. Deploy log shows Action exceeded 5000ms. | Keep outbound calls short. Split long work into several actions. |
| Stored file size | 5 MiB | Upload fails with HTTP 413 and code: "too_large". | Compress or resize before upload. |
| Stored bytes per developer | 100 MiB | Upload fails with HTTP 413 and code: "quota_exceeded". | Delete objects you no longer serve. |
| Log entries kept | 1000 | The oldest entries are dropped. No error. | Log less per request, or read logs sooner. |
| Log bytes kept | 256 KiB | The oldest entries are dropped. No error. | Log less per request. |
| Bytes per log entry | 16 KiB | Long messages are cut. If the entry is still too large, data is replaced with an object that has truncated: true. | Log a summary, not the whole object. |
| Artifact size | 1 MiB | npx lakebed build fails and reports the artifact's byte size and the 1048576 byte deploy limit. | Remove unused code and large inline data from the capsule. |
| Server env keys | 64 | Deploy fails with serverEnv.values may include at most 64 keys. | Combine related values into one JSON key. |
| Server env key length | 128 bytes | Deploy fails with serverEnv.values.<key> exceeds 128 bytes. | Use a shorter name. |
| Server env value length | 16 KiB | Deploy fails with serverEnv.values.<key> exceeds 16384 bytes. | Keep long payloads out of env. |
| Server env total size | 64 KiB | Deploy fails with serverEnv.values exceeds 65536 bytes. Keys and values both count. | Remove keys you do not read. |
Reading a quota error
HTTP quota errors use this body:
{
"code": "lakebed_quota_exceeded",
"bucket": "requests",
"current": 10001,
"error": "requests quota exceeded. Limit: 10000.",
"limit": 10000,
"resetAt": "2026-01-02T00:00:00.000Z",
"retryAfterSeconds": 41230,
"suggestion": "Retry after reset or reduce request frequency."
}
App WebSocket errors put this body in quota. The outer message has op: "error", ok: false, the request id, and the same code and error. Read message.quota.bucket, message.quota.current, and message.quota.retryAfterSeconds for WebSocket quota details.
HTTP daily quota responses also carry Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Read bucket to find the row in the table above. Do not retry a daily quota in a tight loop. Wait for retryAfterSeconds. Stored row and byte quotas have no timed reset. Their resetAt and retryAfterSeconds are null.
Per-handler database limits fail the handler and roll back its transaction. Stored row and byte limits use the quota errors in the table.
Action size and time limits fail the action. Hosted clients receive Lakebed action failed.. Read the deploy log for the detailed limit message. Local calls include the detailed message. An action failure does not undo outbound side effects. For example, Lakebed checks the result size after the action runs. Do not assume a failed action is safe to retry.
Checking usage
npx lakebed deploy prints the artifact, state, request, and mutation limits for the deploy it just published.
Run npx lakebed inspect <deploy-id-or-url> --usage to see stored rows and bytes, and today's request and mutation counts, beside their limits. Daily counts use UTC. Stored rows and bytes have no timed reset. Use --usage --json for the complete usage response, including recorded daily counters. For local development, omit the deploy target and use --port if needed. Local daily counts are not enforced.
npx lakebed inspect <deploy-id-or-url> prints per-read database metrics for the deployed capsule:
read by_room: returned=20 read=20 bytes=4096
read direct: returned=1 read=812 bytes=190334 full-scan
returned, read, and bytes are the counters behind the rowsReturned, rowsRead, and bytesRead rows in the table. A full-scan marker means the query had no usable index, and it is the usual cause of a rowsRead failure.
Notes
The hosted cloud also applies a short-window burst limit per client and a separate daily cap per client IP. Normal app traffic stays well under both. A load test does not.
npx lakebed dev does not apply the daily request and mutation quotas, so local iteration is not throttled. The per-handler database limits, action limits, value size limits, and stored row and byte limits apply locally, so local tests can catch these failures. Local wording differs: lakebed dev reports the stored limits as State row limit exceeded (16384). and State byte limit exceeded (1048576). instead of a 429.
See database.md for collect() and paginate(), storage.md for object storage errors, and reference.md for deploy behavior.