Skip to content

Limits and ceilings

Find the numeric limits for recipes, sources, Readers, runs, queries, downloads, streams and credentials. Each value comes from a schema, worker constant or --help string.

Source limits and table limits apply separately. Ordinary builds can seal at most 10,000,000 output rows, even when several sources each fit their own row limit. Increasing a source limit does not increase the finished-table limit. Joins can multiply rows, so estimate the output grain and row count before acquiring a large corpus.

A table manifest can describe more rows across retained parts, but that does not mean an arbitrary full SQL build can produce them. Existing incremental execution requires a supported continuation or proven partition-local update. Do not split a full dataset into successive snapshot runs and assume those versions append.

Limit Value Where enforced At the limit
Sources per recipe 256 recipe.schema.json sources.maxItems and worker MAX_V4_SOURCES registration refuses
Checks per recipe 128 recipe.schema.json checks.maxItems registration refuses
Declared units per recipe 512 recipe.schema.json units.maxItems registration refuses
Columns per table 512 table_plan.columns.maxItems registration refuses
Grain columns 32 table_plan.grain.maxItems registration refuses
Transform steps 64 transform_plan.steps.maxItems registration refuses
One step’s SQL 65,536 characters transform_step.sql.maxLength registration refuses
Columns named by one check 64 at registration. A key_uniqueness check may name at most 16, which the worker enforces. declared_check.columns.maxItems, MAX_KEY_COLUMNS registration refuses, or DECLARED_CHECK_INVALID
max_null_ppm 0 to 1,000,000 declared_check.max_null_ppm registration refuses
Histogram / timeline bins 4 to 12 column_presentation.bins registration refuses

Text lengths:

Field Max characters
dataset.name 200
dataset.description 4,000
question.text 4,000
table.description 2,000
column.description 2,000
column.presentation.story 240
source.description 240
source.locator.display_locator 2,048
rights_claim.claim_note 2,000
declared_unit.unit 64
transform_step.description 2,000
declared_check.description 2,000
Dataset topics 8 topics, 40 characters each

dataset.name is the document’s own bound, from common.schema.json’s display_name. mr-data dataset --name stops shorter, at 160 characters, and says so at the terminal before it assembles the request.

Limit Value Where enforced At the limit
limits.max_source_bytes required, positive source_limits exceeding it fails every mode, including sample. The run cannot truncate bytes it already transferred
limits.max_rows optional, positive source_limits clamps the merged relation
limits.max_requests optional, and required on collections source_limits the run stops and reports the budget that ended it
Connector parameters 64 connector_configuration.parameters.maxItems registration refuses
Parameter name 128 characters connector_parameter.name registration refuses
Parameter value 2,048 characters connector_parameter.value registration refuses
reader.decode_options value 262,144 characters (256 KiB) connector_parameter conditional registration refuses
Connector origin 255 characters connector_configuration.origin registration refuses
Source manifest request 8,192 bytes Studio 413 SOURCE_MANIFEST_TOO_LARGE
Limit Value Applies to
request.path_template 1,024 characters both generic adapters
request.static_query 32 pairs both
request.static_headers 24 headers both
request.parameters 32 declarations both
A template value 2,048 characters both
body_template array / object 64 items / 64 properties both
pagination.max_pages 1 to 100 both
pagination.page_start 0 to 1,000,000 page
pagination.page_size 1 to 10,000 offset
pagination.offset_start 0 to 1,000,000,000 offset
response.records_pointer 512 characters both
reader.decode_options 64 properties both
parquet.tabular column projection 512 names, 512 characters each both
limits.max_pages 1 to 1,000 @3.0.0 only. Counts every request: submit, each poll, each chunk
limits.max_records 1 to 10,000,000 @3.0.0 only
limits.max_bytes 1 to 268,435,456 (256 MiB) @3.0.0 only
limits.max_seconds 1 to 900 @3.0.0 only
job.status.succeeded_values 1 to 8 @3.0.0 only
job.status.failed_values / pending_values 8 each @3.0.0 only
job.status.pending_http_statuses 4, each 200 to 299 @3.0.0 only
job.status.poll_interval_seconds 1 to 60 @3.0.0 only
job.status.max_wait_seconds 1 to 900 @3.0.0 only
job.chunking.max_chunks 1 to 1,000 @3.0.0 only
auth.extra_static_headers 8 @3.0.0 only
JWT lifetime_seconds 60 to 3,600 @3.0.0 only

Everything on pages except allowed_origins has a default the worker applies. Hitting a budget is not a failure. The run succeeds with pages.complete: false, and budget_exhausted names the budget that ended it.

Limit Range Default At the limit
discovery.members (explicit) 1 to 50,000 none registration refuses
discovery.max_requests 1 to 1,024 64 discovery stops
pages.allowed_origins 1 to 8 none, and required registration refuses
pages.max_pages 1 to 50,000 50,000 the collection knows about no more pages
pages.max_fetches_per_run 1 to 10,000 10,000 the run stops fetching and succeeds incomplete
pages.concurrency 1 to 10,000 admitted capacity requested page-fetch ceiling; the worker admits at most 25 and still obeys provider and egress budgets
pages.min_interval_seconds 1 to 3,600 1 1 is also the hosted limiter’s own floor
pages.request_timeout_seconds 5 to 120 30 the worker gives up on the page fetch
pages.retries 0 to 3 2 the ledger records the page as failed
revisit.seconds (interval) 3,600 to 31,536,000 none required on interval
page_id 1 to 256 characters none registration refuses
A discovery selector 512 characters none registration refuses
A collection URL 2,048 characters none registration refuses
budget_exhausted values max_fetches_per_run, max_requests, max_source_bytes, deadline, max_pages none the run’s coverage block reports it

limits.max_requests counts one per HTTP request, redirects and retries included. Registration checks only that discovery.max_requests + pages.max_fetches_per_run fits inside it.

Readers carries the default budgets per family and the family-specific ones. The bounds a recipe interacts with:

Limit Value Where enforced At the limit
Certified Readers 48 Studio READER_COORDINATES, worker _CERTIFIED_FAMILIES registration rejects an unlisted Reader
reader.decode_options canonical bytes 262,144 (256 KiB) Studio READER_OPTIONS_MAX_BYTES registration rejects
max_nesting_depth fixed at 1 ReaderBudgets the worker refuses any other value because this is a security invariant rather than a budget
Bulk families’ max_input_bytes 256 MiB bulk_default_budgets() READER_BUDGET
Bulk families’ max_output_bytes 4 GiB bulk_default_budgets() READER_BUDGET
Bulk families’ max_rows 10,000,000 bulk_default_budgets() READER_BUDGET
Bulk families’ max_declared_cells 400,000,000 bulk_default_budgets() READER_BUDGET
Contract default max_container_members 1,024 ReaderBudgets READER_BUDGET
Contract default max_expansion_ratio 100 ReaderBudgets READER_BUDGET
Decoded output from one source the lowest of the family’s max_output_bytes, 1,073,741,824 (1 GiB), and one third of the decode child’s address space worker Sandbox.decode_and_parse READER_BUDGET, naming both sizes

A recipe may narrow any budget per field and never widen it. The schema refuses a cap that names an unknown field and identifies that field in the response.

The decoded output ceiling is taken once, in Sandbox.decode_and_parse, so every caller gets the same one. It is what the request carries, what the confined child enforces, and the file-size limit on the private file the child writes its output to. The gibibyte is this worker holding the whole decoded relation in memory while it classifies and seals it. So a bulk family’s 4 GiB max_output_bytes is the family’s own number, and 1 GiB is what a run reaches.

How far a source fetches depends on how the run will read it. A Reader-pinned source is handed to its family. An unpinned source takes one of three direct reads.

Source Fetches up to Where enforced At the limit
Reader-pinned, any sealed format the family’s max_input_bytes, 256 MiB at most worker _confined_read_source_ceiling READER_BUDGET
unpinned csv 268,435,456 bytes (256 MiB) worker, the hosted fetch cap the run fails on max_source_bytes
unpinned ndjson 37,745,664 bytes worker, derived from one confined request’s budget the run fails on max_source_bytes. Pin a Reader to go past it
unpinned json or parquet 16,777,216 bytes (16 MiB) worker DRAINED_READ_SOURCE_BYTES the run fails on max_source_bytes. Pin a Reader to go past it

csv and ndjson have a row-at-a-time reader and json and parquet do not. A json or parquet source is read whole inside the clean room, which is why it keeps the tighter ceiling. Pinning a Reader is the way past it. A pinned source is read by its family and sealed as that family’s canonical csv.

mr-data recipe warns on the receipt when a source declares a limits.max_source_bytes larger than a fetch can reach. The warning is not a refusal. The declared number is where fetching stops, not a measurement of the source.

mr-data run takes two ceilings and nothing else.

Flag What it bounds At the limit
--max-rows N rows from each source. A recipe with eight sources can deliver eight times this many the run truncates
--max-source-bytes N bytes fetched from the sources the run fails in every mode

mr-data run has no --max-seconds and no size flag for the whole run. Studio projects runtime and size rather than taking them from the caller. A run over the projection threshold waits in awaiting_confirmation, and the projection (projected_bytes, projected_runtime_s, projected_cost) rides on the 409 CONFIRM_REQUIRED refusal. --confirm authorizes it. A workspace over its daily budget draws 409 DAILY_SPEND_CEILING_EXCEEDED, whose detail names recorded_cost, requested_cost and ceiling_cost.

The plan is flat. projected_cost and the ceiling figures are the platform’s own infrastructure accounting. No run is billed to you.

A sample run states at least one clamp, or Studio refuses 422 CLAMPS_REQUIRED. The clamp names are max_rows, max_source_bytes and window. max_rows and window truncate, and max_source_bytes refuses.

Limit Value Where enforced
Check results on one run 256 worker MAX_CHECK_RESULTS
Coverage entries 256 (one per source) worker MAX_COVERAGE_ENTRIES
Parts in one commit batch 64 worker MAX_COMMIT_PARTS
Evidence items in one commit 64 worker MAX_COMMIT_EVIDENCE
Artifact reservations in one batch 64 worker MAX_RESERVE_BATCH
Predecessor parts read 256 worker MAX_PREDECESSOR_PARTS
Progress message 1,024 characters worker MAX_PROGRESS_MESSAGE_CHARS
Progress identifier 63 characters worker MAX_PROGRESS_IDENTIFIER_CHARS
Signed URL 4,096 characters worker MAX_SIGNED_URL_CHARS
Credential handle 512 characters worker MAX_CREDENTIAL_HANDLE_CHARS
Bounded-direct input 4 GiB worker BOUNDED_DIRECT_MAX_BYTES
Consecutive unchanged refreshes tracked 1,000,000 worker MAX_SETTLED_UNCHANGED_REFRESHES
Limit Value Where enforced At the limit
lookback_seconds 0 to 2,592,000 (30 days) source_window registration rejects
max_span_seconds 3,600 to 315,360,000 (10 years) source_window a derived span over it clips forward from its start rather than drawing an error
merge.row_identity 1 to 8 columns window_merge registration rejects
Partitions one merge may replace 4,096 worker MAX_WINDOW_PARTITIONS WINDOW_SPAN_TOO_MANY_PARTITIONS
snapshot.max_partitions 1 to 3,660 (ten years of days) window_snapshot SNAPSHOT_PARTITIONS_EXHAUSTED, before any upload starts
A snapshot’s merged relation 2 GiB worker MAX_SNAPSHOT_MERGED_BYTES SNAPSHOT_RELATION_TOO_LARGE
Snapshot lookback_seconds fixed 0 worker SNAPSHOT_SPAN_FIXED
Snapshot max_span_seconds fixed 86,400 worker SNAPSHOT_SPAN_FIXED
physical_scopes.bindings 1 to 4,096 source_physical_scopes registration refuses
table_partition_fields / table_sort_fields 1 to 16 each source_physical_scope_binding registration refuses
physical_partition_values 4,096 rows × 16 values × 256 characters source_physical_scope_binding registration refuses
Scope max_input_bytes / max_output_bytes 1 to 4,294,967,296 (4 GiB) source_physical_scope_binding registration refuses

Studio may record a legacy settled-source status after unchanged observations and re-verifies it on a cadence. That status does not authorize reuse_predecessor in a strict refresh plan. These numbers are deployment-configurable, not contract numbers.

Limit Default Environment variable
Unchanged observations before a source settles 2 STUDIO_V4_SETTLED_AFTER
Least-recently-verified settled sources re-verified per run 1 STUDIO_V4_SETTLED_VERIFY_PER_RUN
Re-verification age max(cadence interval × 8, 24 h) server policy, not configurable per table
Cadence observations retained 500 rows, 200 to a page Studio
Limit Value Where enforced At the limit
Parts in one table version 4,096 worker MAX_PARTS_PER_VERSION and table_manifest.parts.maxItems the version cannot take another part
Distinct partition values in one table 4,096 worker MAX_TABLE_PARTITIONS the run refuses, naming the count
Parts one run may write 1,024 worker MAX_PARTS_PER_RUN the run cannot write another
One part’s bytes 256 MiB worker PART_MAX_BYTES TABLE_PART_OVER_MAX
Part roll target 128 MiB, never below 16 MiB worker PART_TARGET_BYTES, PART_MIN_BYTES the writer rolls to a new part
Rows in one part 4,194,304 worker PART_MAX_ROWS the writer rolls to a new part
Part writer memory ceiling 512 MiB worker PART_WRITER_MEMORY_CEILING TABLE_PART_MEMORY_CEILING
One stream part’s bytes 64 MiB worker MAX_TABLE_PART_BYTES the writer rejects
Manifest schema fields 4,096 table.schema.json validation refuses the manifest
partition_fields / sort_fields 8 each table.schema.json validation refuses the manifest
stats_fields 16 table.schema.json validation refuses the manifest
Checks recorded on one version 256 table_version.checks.maxItems none
Table version listing page 500 table_version_listing.maxItems page with a cursor
Part download sessions per request 1 to 256 part_download_sessions_command.limit page with next_cursor
Part download session TTL 60 to 3,600 seconds part_download_sessions_command.session_ttl_seconds the session expires
Preview rows written per run 200 worker MAX_TABLE_PREVIEW_ROWS mr-data peek --rows narrows, never widens
Tables per refresh target set 200 refresh_targets.tables.maxItems none
Tables listed on a dataset 200 dataset.schema.json none
Parts one compaction may group 64 Studio _COMPACT_MAX_PARTS TABLE_COMPACTION_INELIGIBLE
Legacy-recovery part bytes 8 MiB Studio MAX_PART_BYTES Studio refuses the recovery
Manual refreshes per workspace per hour 1,200 Studio 429 MANUAL_REFRESH_RATE_LIMITED

mr-data query submits to Studio and polls. It never holds a long poll.

Limit Value Where enforced At the limit
Statement bytes, client side 65,536 (64 KiB) CLI MAX_STATEMENT_BYTES THIN_QUERY_STATEMENT_TOO_LARGE, and the command sends nothing
Statement characters, Studio side 131,072 query.schema.json bounded_statement Studio refuses
Polls 40 CLI MAX_QUERY_POLLS the command prints the question id. Run it again to read the same one back
Wall time 120 seconds CLI MAX_QUERY_WAIT_SECONDS the command prints the question id
Poll interval 0.2 s, doubling to 2.0 s CLI none
Parts one bounded read may open 512 Studio MAX_QUERY_PARTS QUERY_TOO_MANY_PARTS, naming both the count and the ceiling
Result rows 1,000,000 query.schema.json result_rows.maxItems Studio refuses
--max-rows over the server ceiling none Studio refused rather than lowered

The statement is read-only, one statement, and begins with SELECT, WITH, EXPLAIN or DESCRIBE. Studio’s gate blanks comments and quoted text, then scans the rest on word boundaries. It refuses a column literally named system, copy, set, load, call, glob, import or export. Writing that identifier in "double quotes" puts it outside the scan.

The JSON query grammar, served by cloud. The anonymous column inherits every bound it does not name from the credentialled tier. A stranger’s query is the same grammar with a smaller page.

Limit Credentialled Anonymous At the limit
Columns in one query 20 20 400
Filters 8 8 400
order_by entries 2 2 400
Aggregate expressions 4 4 400
Values in one in filter 20 20 400
Rows per page 10,000 100 a request above the tier’s ceiling fails before anything resolves a table
Result bytes 8 MiB 64 KiB 413
offset reach 100,000,000 1,000,000 400
Identifier pattern ^[A-Za-z_][A-Za-z0-9_]*$, 128 characters same a column the Parquet spells otherwise shows in a preview but never in a query
Filter scalar 2,048 characters same 400
Queryable artifact bytes 256 MiB same 413
Execution 15 seconds same timeout
Request wall clock 30 seconds same timeout
Parts one ticket may list 4,096 same refusal
Composable total bytes 2 GiB same the largest single file /compose writes
Compose worker budget 60 seconds same timeout
Compose request wall clock 90 seconds same timeout
Native download transfer 900 seconds same timeout

Rate limits:

Limit Value Where
Credentialled queries per minute per principal 60 PUBLIC_QUERY_LIMITS.perMinute
Credentialled queries per workspace per day 5,000 PUBLIC_QUERY_LIMITS.perWorkspaceDay
Scanned bytes per workspace per day 50 GiB PUBLIC_QUERY_LIMITS.admittedBytesPerWorkspaceDay, reserved at admission and settled to the bytes the worker reports it read
Anonymous queries per address per minute 30 PUBLIC_QUERY_ANONYMOUS_PER_ADDRESS_PER_MINUTE
Anonymous queries per address per day 600 PUBLIC_QUERY_ANONYMOUS_PER_ADDRESS_PER_DAY
Anonymous queries fleet-wide per minute 600 PUBLIC_QUERY_ANONYMOUS_GLOBAL_PER_MINUTE
Anonymous queries fleet-wide per day 30,000 PUBLIC_QUERY_ANONYMOUS_GLOBAL_PER_DAY
Public route requests per window 120 per 60 s PUBLIC_ROUTE_RATE_LIMIT_MAX
Catalog listing page 20 by default, 100 at most /api/v2/public/datasets?limit=

A 429 carries Retry-After. A 413 means the artifact or the result is too large: narrow the query, or take the Parquet.

These numbers are one hard-coded plan rather than a law of the system. When per-tier entitlements exist, the credentialled numbers become the free tier’s row of a table.

Limit Value Where enforced At the limit
One artifact 16 GiB CLI MAX_ARTIFACT_BYTES THIN_ARTIFACT_TOO_LARGE, and the command fetches nothing
JSON response on a control-plane route 1 MiB CLI MAX_JSON_RESPONSE_BYTES THIN_RESPONSE_TOO_LARGE
Request timeout 30 seconds CLI REQUEST_TIMEOUT_SECONDS the client abandons the request

mr-data download never overwrites a local file. A destination holding different bytes returns THIN_DESTINATION_EXISTS. A multi-part version arrives whole or not at all. A part the command cannot fetch ends in a refusal that names how many, never a folder holding some of the table.

Limit Value Where enforced At the limit
stream capture --seconds 600 recorder hard bound the recorder refuses a request above it rather than lowering the bound
stream capture --max-events 10,000 stream.schema.json sample_limits.events the capture stops and stores what it read
stream capture --max-bytes 8,388,608 (8 MiB) stream.schema.json sample_limits.bytes the capture stops and stores what it read
stream probe --seconds 1 to 900 start_probe_command.seconds refused over it
Members on one recording or subscription 1 to 2,048 stream.schema.json members refused
--member repetitions on one command 256 CLI refused
--digest repetitions on registry create 32 CLI refused
Documents in one registry 1 to 256 create_registry_command.document_digests refused
Member id 256 characters stream.schema.json member_id refused
Series id 64 characters stream.schema.json series_id refused
Subscription volume_policy.max_bytes 1 MiB to 1 TiB start_subscription_command the subscription stores what it read and stops
Subscription volume_policy.max_duration_seconds 5 to 86,400 start_subscription_command the subscription stores what it read and stops
Subscription no_data_timeout_seconds 60 to 604,800 start_subscription_command the subscription goes idle
Events in one batch 1,000,000 worker MAX_BATCH_EVENTS the writer refuses the batch
Batches per recording read by one run 4,096 worker MAX_RECORDING_BATCHES the run cannot read further
Members named on one batch 2,048 worker MAX_RECORDING_BATCH_MEMBERS refused
Recordings listed per page 200 recording_listing.recordings.maxItems page with a cursor
Batches listed per recording 2,000 listed_recording.batches.maxItems page with a cursor
Recording retention 30 days Studio stream_venue_retention_days (1 to 3,650, STUDIO_STREAM_VENUE_RETENTION_DAYS) the batches are gone, and a missed window is a permanent hole

mr-data run has no duration flag. A build reads recorded batches, and those batches bound it.

Limit Value Where enforced At the limit
Secret name 128 characters, ^[a-z][a-z0-9]*(?:[-_.][a-z0-9]+)*$ credential_reference and secret.schema.json 422 SECRET_NAME_INVALID
Enrolled material, base64 4 to 87,384 characters (about 64 KiB raw) enroll_secret_command.material_base64 422 SECRET_MATERIAL_INVALID
One shaped-credential part the vault’s per-value bound CLI THIN_SECRET_MATERIAL_INVALID
Secrets listed per page 500 secret_listing.secrets.maxItems page
Connection parameter_values 32 properties connection_source registration refuses

The command line never carries a credential value. mr-data keys set reads it from a file or stdin and strips one trailing newline.

Limit Value Where
mr-data catalog search --limit 1 to 25, default 10 mr-data catalog --help
Formats per catalogue question 1 mr-data catalog --help
Datasets in one catalog status page 100 dataset.schema.json
Source inventory page position 51,200 dataset.schema.json paged_source_inventory
Limit Value Where
Idempotency-Key 16 to 128 URL-safe characters common.schema.json
A cursor 512 characters common.schema.json
A display name 200 characters common.schema.json
A non-empty string 4,096 characters common.schema.json
An error code 3 to 64 characters error.schema.json
An error message 2,000 characters error.schema.json
An error’s details 20 entries error.schema.json
A safe integer ±9,007,199,254,740,991 common.schema.json

These are deployment settings, not contract constants. A deployment states them in the environment, and these defaults are what Studio ships with.

Setting Default What it governs
STUDIO_V4_DAILY_SPEND_CEILING_COST_MICROS 0 The rolling daily ceiling on a workspace’s projected spend. 0 turns the ceiling off, so Studio ledgers every run and refuses none with DAILY_SPEND_CEILING_EXCEEDED. A deployment that wants a ceiling states one.
STUDIO_V4_CONFIRMATION_THRESHOLD_BYTES 8000000000 Projected bytes at or over which a run waits in awaiting_confirmation.
STUDIO_V4_CONFIRMATION_THRESHOLD_COST_MICROS 100000 Projected cost at or over which a run waits in awaiting_confirmation.
STUDIO_V4_SAMPLE_FIRST_THRESHOLD_BYTES 2000000000 Legacy setting; no longer selects sample/full pairs. Every new full build uses progressive acquisition.

--resource-class has a grammar and no published vocabulary. run.schema.json’s resource_class is common.schema.json’s identifier: lowercase snake_case, at most 63 characters. Which identifiers exist is the workspace’s own resource-class policy, and that policy resolves the worker execution profile a run runs under. A caller may ask for one but does not decide it.