skip to content

Data Loading and Streaming

The loading paths: batch jobs from GCS, the Storage Write API with its committed, buffered and pending streams, and managed transfer services. Interviewers contrast streaming with batch because pricing, deduplication and freshness guarantees all differ between them.

part ofGoogle BigQueryoverview, primer and where to startread it →
on this pageshow

questions

6

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

open as a page

When should you use the BigQuery Storage Write API instead of a batch load job from Cloud Storage?

level: middleimportance: must knowfreq 65%

basics

~20 s

Use the BigQuery Storage Write API when rows must be queryable within seconds or arrive one at a time from an application. Use batch load jobs otherwise — they carry no per-byte ingestion charge and commit atomically, while streamed bytes are billed.

open as a page

What are the default, committed, buffered and pending streams in the BigQuery Storage Write API?

level: middleimportance: should knowfreq 55%

basics

~20 s

They differ in when rows become visible and what guarantee you get. The default stream is at-least-once and always available; committed streams make rows visible on append and support exactly-once via offsets; buffered streams hold rows until you flush; pending streams hold everything until one atomic commit.

open as a page

A BigQuery streaming pipeline is producing duplicate rows. How do you diagnose and eliminate them?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Duplicates usually mean at-least-once writes: the default stream or legacy insertAll retried an append whose acknowledgement was lost. Fix it upstream with application-created committed streams and per-request offsets, or downstream by deduplicating on a business key.

open as a page

A nightly BigQuery load of one 300 GB gzipped CSV from Cloud Storage takes hours. Why, and what would you change?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Gzip is not splittable, so BigQuery must read that file with a single worker — the load is serialised no matter how much capacity exists. Split the data into many files, or use Avro or Parquet, whose internal blocks can be read in parallel.

open as a page

How do you choose among Pub/Sub BigQuery subscriptions, Dataflow, direct Storage Write API and the Data Transfer Service?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Pick by how much transformation you need and who owns the failure modes. Pub/Sub BigQuery subscriptions for raw pass-through, Dataflow when rows need enrichment or windowing, the Storage Write API when your own service already holds the rows, and the Data Transfer Service for scheduled pulls from SaaS and other warehouses.

open as a page