skip to content

In BigQuery, how does a batch load job from Cloud Storage work, and which formats can it read?

level: juniorimportance: must knowfreq 75%

answer

  1. it is a job, not a query
  2. all-or-nothing commit into the table
  3. the binary formats bring their own schema
  4. no per-byte charge, but a per-table job cap

basics

~20 s

A BigQuery load job reads files from Cloud Storage and commits them into a table atomically — all rows or none. It reads CSV, newline-delimited JSON, Avro, Parquet and ORC, and carries no per-byte ingestion charge.

solid answer

~50 s

A load job is one of BigQuery's job types. You point it at one or more Cloud Storage URIs (wildcards allowed), name a destination table, and BigQuery reads the files in parallel and commits the result as a single atomic operation — if the job fails, the table is untouched. Source formats are CSV, newline-delimited JSON, Avro, Parquet, ORC and Datastore/Firestore exports; Avro, Parquet and ORC are self-describing, so you don't supply a schema. You control what happens to existing data with the write disposition: `WRITE_APPEND`, `WRITE_TRUNCATE` (replace) or `WRITE_EMPTY` (fail if non-empty). You can start it with `bq load`, the `LOAD DATA` SQL statement, or the jobs API. The load itself has no per-byte ingestion charge — it runs on a free shared slot pool — you pay only for the storage it produces.

code

sql · 6 lines
sql
LOAD DATA OVERWRITE mydataset.events
PARTITIONS(_PARTITIONDATE = '2026-08-20')
FROM FILES (
  format = 'PARQUET',
  uris = ['gs://my-bucket/events/dt=2026-08-20/*.parquet']
);

go deeper

for a junior

Be ready to name the supported source formats and write a working bq load or LOAD DATA statement against a Cloud Storage wildcard, and to say that the job commits all rows or none.

for a middle

Explain schema handling per format, what WRITE_TRUNCATE versus WRITE_APPEND does, and why the atomic commit makes truncate-and-reload of a partition an idempotent backfill.

for a senior

Show judgment on --max_bad_records and --ignore_unknown_values hiding data-quality problems, on pinning schemas instead of autodetect, and on the per-table daily job cap shaping your batch cadence.

for a principal

Own the ingestion cadence as a cost and reliability decision: batch loads are free of ingestion charge and atomic, so push freshness requirements toward micro-batching unless the business genuinely needs sub-minute latency.

## What a load job is BigQuery has four job types — query, load, extract and copy. A **load job** is the batch ingestion path: it reads whole files from Cloud Storage (or from a local file through the client, or from a readable data source) and writes them into a BigQuery table. It is asynchronous: you submit it, get a job id, and poll for completion. Unlike a query, it does not produce a result set; its output is rows in a destination table. ## Starting one Three equivalent front doors: ```sql LOAD DATA INTO mydataset.events FROM FILES ( format = 'PARQUET', uris = ['gs://my-bucket/events/2026-08-20/*.parquet'] ); ``` ```bash bq load --source_format=PARQUET mydataset.events 'gs://my-bucket/events/*.parquet' ``` and the `jobs.insert` REST call with a `configuration.load` block, which is what every client library wraps. One wildcard per URI is allowed, and you may list several URIs. The source bucket must be colocated with the dataset's location — you cannot load a EU-region bucket into a US dataset. ## Source formats and what each buys you - **CSV** — ubiquitous, but text: every value must be parsed and coerced, and the file carries no schema. You supply `--skip_leading_rows`, `--field_delimiter`, `--allow_quoted_newlines`, `--allow_jagged_rows`. - **Newline-delimited JSON** — one JSON object per line. Also text, also parsed row by row, but it carries nested structure naturally, which maps onto BigQuery `STRUCT`/`ARRAY` columns. - **Avro** — binary, self-describing, block-structured. Google recommends it for loading compressed data because its internal blocks can be read in parallel. - **Parquet** and **ORC** — binary, self-describing, columnar. Types come from the file, so no schema flag is needed. - **Datastore/Firestore export files** — for restoring exports of those databases. ## Schema handling With a self-describing format the schema is inferred from the file. With CSV or JSON you either pass an explicit schema (a JSON schema file or `col:TYPE,...`) or set `--autodetect`, which samples rows and guesses. Autodetect is convenient for exploration and risky in a pipeline: a column that is all digits today can be inferred `INTEGER` and break tomorrow when an alphanumeric id shows up. Production loads normally pin the schema. When the incoming files legitimately gain a column, `--schema_update_option=ALLOW_FIELD_ADDITION` lets the load extend the table's schema; `ALLOW_FIELD_RELAXATION` lets it turn a `REQUIRED` column into `NULLABLE`. Nothing lets a load drop or retype a column. ## Write disposition and atomicity `WRITE_APPEND` adds rows, `WRITE_TRUNCATE` replaces the table's (or the addressed partition's) contents, `WRITE_EMPTY` fails if the destination already has data. The important property is that the job is **atomic**: readers see either the pre-job table or the fully loaded table, never a half-loaded one. That makes "truncate and reload this partition every night" a safe, idempotent design — a retried job produces the same table. You can address a single ingestion-time partition with the decorator syntax `mydataset.events$20260820`. ## Errors and bad rows By default a single unparseable row fails the job. `--max_bad_records=N` tolerates up to N; `--ignore_unknown_values` drops fields not in the schema instead of erroring. Both are useful and both hide data problems, so set them deliberately. ## Cost and limits Batch loads carry no per-byte ingestion charge — they execute on a free shared pool of slots (and can optionally be routed to a reservation so they get dedicated capacity instead of competing in the shared pool). You pay for the resulting storage. That free-and-atomic combination is why batch loading is the default answer whenever minutes of latency are acceptable. The counterweight is a **daily cap on the number of load jobs per table**, which makes "a load job every few seconds" an unworkable design. Micro-batching every few minutes is fine; sub-minute freshness belongs to the Storage Write API instead. ## When batch is the wrong tool If rows must be queryable seconds after they are produced, or they arrive one at a time from an application rather than as files, a load job is the wrong shape — you would be paying for job orchestration and burning the per-table job quota. That is the streaming path's job.

  • What does --autodetect do on a CSV load, and why do production pipelines usually avoid it?
    It samples rows and infers column names and types. It is fine for exploration, but the inference depends on the data present in that file: a column of digits becomes INTEGER today and breaks the load tomorrow when a value like `00A17` arrives. Production loads pin an explicit schema and use `--schema_update_option=ALLOW_FIELD_ADDITION` when the shape legitimately grows.
  • How do you reload just yesterday's partition without touching the rest of the table?
    Address the partition directly. For an ingestion-time partitioned table, target the decorator `mydataset.events$20260820` with `WRITE_TRUNCATE`; the SQL equivalent is `LOAD DATA OVERWRITE` with a partition filter or a `MERGE`/`DELETE`+insert on a column-partitioned table. Because a load job is atomic, replaying it is idempotent — the standard backfill pattern.
  • Can a load job read a file that is not in Cloud Storage?
    Yes — the clients and `bq load` can stream a local file up as part of the job, and Drive is supported for some formats. Cloud Storage is the normal production source because it is durable, parallel-readable and colocatable with the dataset; local uploads are for one-off and small files.

saying these in an interview costs you the question

  • Thinking a failed load leaves partially committed rows in the table
  • Believing BigQuery charges per byte loaded by a batch load job
  • Assuming a load job can run every few seconds like a stream
  • Supplying a schema for Parquet or Avro because CSV needed one
  • Treating --autodetect as production-safe schema management

context