skip to content

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

level: middleimportance: must knowfreq 65%

answer

  1. freshness is the axis, cost is the price
  2. one path has no per-byte ingestion charge
  3. a job quota caps how often you can batch
  4. tiny rows hit a billing floor when streamed

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.

solid answer

~50 s

The two paths trade cost against freshness. A load job reads files from Cloud Storage, commits atomically, and has no per-byte ingestion charge — but it is a job, so it takes seconds to minutes to schedule and run, and there is a daily cap on load jobs per table that rules out running one every few seconds. The Storage Write API is a gRPC streaming interface: rows are appended from your application and become queryable almost immediately, but you are billed for the bytes ingested, with a per-row minimum size that makes many tiny rows disproportionately expensive. My rule: if the business can tolerate a few minutes of lag, micro-batch to Cloud Storage and load — it is cheaper and simpler to make idempotent. If it genuinely needs sub-minute freshness, stream, and batch rows into large `AppendRows` requests rather than one row per call.

code

sql · 6 lines
sql
-- Which of yesterday's load jobs ran, and what did they touch?
SELECT job_id, destination_table.table_id, total_bytes_processed, state, error_result
FROM `region-us`.INFORMATION_SCHEMA.JOBS
WHERE job_type = 'LOAD'
  AND creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
ORDER BY creation_time DESC;

go deeper

for a junior

Know that BigQuery has a file-based batch path and a row-based streaming path, and that batch loading data from Cloud Storage does not carry a per-byte ingestion charge.

for a middle

Explain the four axes — cost, latency, per-table job quota and atomicity — and be able to justify a recommendation from a stated freshness requirement rather than from preference.

for a senior

Demonstrate that you would go ask the consumer what freshness they actually need, size the streaming bill before committing to it, and know that pending streams give batch-style atomicity when data is not file-shaped.

for a principal

Own the standing cost: a decision to stream everything is a recurring line item and an operational burden. Set the org's default to micro-batching and require a freshness SLA to justify the streaming path.

## The two ingestion shapes BigQuery offers batch ingestion (a **load job** over files in Cloud Storage) and streaming ingestion (the **Storage Write API**, a gRPC service, plus the older `tabledata.insertAll` REST method it replaces). Choosing between them is one of the most common BigQuery design questions because cost, latency, atomicity and operational complexity all move together. ## Cost Batch load jobs carry **no per-byte ingestion charge**. They run on a free shared pool of slots — or, if you have a reservation, can be routed there for dedicated capacity. You pay for the storage the rows occupy afterwards, and that is all. The Storage Write API bills for the **bytes ingested**. Two consequences follow. First, at a steady multi-terabyte-per-day volume, streaming everything is a standing bill that batch loading would not incur. Second, rows are billed against a **minimum row size**, so a firehose of very small rows costs far more than the sum of their payload bytes. Batching many rows into each `AppendRows` request helps throughput and request overhead; it does not change the per-row billing floor, which is a reason to avoid streaming inherently tiny records. ## Latency A load job is scheduled, queued and executed: expect seconds to minutes end-to-end even for a small file, plus whatever latency your upstream took to land the file in Cloud Storage. Streamed rows are queryable within seconds of the append being acknowledged — BigQuery holds them in a write-optimised buffer that queries read, and converts them to columnar storage in the background. So the honest question in an interview is not "batch or stream" but **"what freshness does the consumer actually need?"** Dashboards refreshed hourly do not need streaming. A fraud rule that reads the last 30 seconds of events does. ## Throughput and quotas Load jobs are capped per table per day, which caps your cadence: micro-batching every five minutes is comfortable, every five seconds is not. The Storage Write API is designed for sustained high throughput and is limited by connection- and region-level throughput quotas rather than by a job count — it is the path built for continuous ingestion. ## Atomicity and correctness A load job is all-or-nothing: a failure leaves the table untouched, so retrying is idempotent. That single property removes an enormous amount of pipeline complexity. Streaming's correctness story is per-stream. The **default stream** gives at-least-once delivery, so retries can duplicate rows. Application-created **committed streams with offsets** give exactly-once, at the cost of managing streams and offsets. **Pending streams** recover batch-style atomicity — rows stay invisible until you commit the stream — which is why the Storage Write API can also serve as a programmatic replacement for load jobs when your data is already in memory rather than in files. ## Operational shape Batch pipelines are files, schedules and reruns: easy to inspect, easy to backfill, easy to reason about. `INFORMATION_SCHEMA.JOBS` shows every load, its bytes, its errors. Streaming pipelines are long-lived processes with connections, retries, backpressure and dead-letter handling; you own the client's failure modes. ## A practical decision rule 1. Establish the freshness SLA with the consumer, not the producer. 2. If it is minutes, land files in Cloud Storage and load them — the ingestion is free and the retry story is trivial. 3. If it is seconds, stream, choose the stream type from your duplication tolerance, and batch rows into large appends. 4. If it is a mix, do both: stream into a landing table for the real-time consumers and batch-load the same data into the curated table, or stream and periodically `MERGE` into a deduplicated table. ## The anti-patterns Streaming because "real-time is better" when the dashboard refreshes hourly is the expensive mistake. Running a load job per event, or per minute per table, to "get streaming for free" is the other one — it exhausts the per-table job quota and produces a pile of tiny commits. And streaming individual rows with one `AppendRows` call each maximises both request overhead and the per-row billing floor.

  • A team wants five-minute freshness. Which path do you recommend and why?
    Micro-batch. Land five minutes of data as files in Cloud Storage and run a load job per interval: ingestion is free of per-byte charge, the commit is atomic so reruns are idempotent, and roughly 288 load jobs a day per table sits comfortably under the per-table cap. Streaming would buy latency the consumer does not need and add a standing ingestion bill.
  • Does the Storage Write API replace load jobs entirely?
    No. It can imitate them — a pending stream buffers rows and makes them visible atomically on commit — which is useful when the data is already in your process rather than in files. But when the data is already files in Cloud Storage, a load job is simpler and has no per-byte ingestion charge, so batch remains the default for file-shaped ingestion.
  • What replaced the legacy tabledata.insertAll streaming method, and why?
    The Storage Write API. `insertAll` is a JSON-over-REST method with only best-effort deduplication via `insertId` and no exactly-once guarantee. The Storage Write API is gRPC with binary protocol-buffer rows, higher throughput, explicit stream types, and offset-based exactly-once semantics. New pipelines should use it.

saying these in an interview costs you the question

  • Choosing streaming because real-time sounds better than batch
  • Assuming both paths cost the same per byte ingested
  • Running a load job per minute per table to fake streaming
  • Streaming one row per AppendRows request at high volume
  • Thinking batch loads are also billed per byte processed

context