3.1 Batch Loading Data into BigQuery

Key Takeaways

  • Batch loading data into BigQuery from Cloud Storage via load jobs is completely free of charge, with billing applied only to source Cloud Storage storage and subsequent BigQuery table storage.

  • Production workloads should always stage large files in Cloud Storage rather than uploading locally to leverage horizontal parallel slot distribution, wildcards, and resilience.

  • Self-describing binary and columnar formats like Apache Avro and Parquet are splittable across parallel workers, whereas GZIP-compressed CSV files force single-threaded decompression.

  • BigQuery Data Transfer Service (DTS) automates scheduled, managed ingestion from Google SaaS platforms (Google Ads, Campaign Manager) and third-party cloud object storage (AWS S3, Azure Blob Storage).

  • Load job write dispositions (WRITE_TRUNCATE, WRITE_APPEND, WRITE_EMPTY) govern how incoming batches handle existing table data and partition overwrites.

Last updated: October 2026

3.1 Batch Loading Data into BigQuery

Data ingestion is the foundational gateway of any enterprise analytical architecture. In Google Cloud, BigQuery serves as the enterprise data warehouse and lakehouse engine, designed to ingest and analyze petabytes of structured and semi-structured information. When architecting ingestion pipelines, data engineers and practitioners must make critical design decisions regarding ingestion modalities, source file formats, staging architectures, cost optimization, and error tolerance. Batch loading represents the most common, cost-effective, and throughput-optimized pattern for moving historical, scheduled, or bulk datasets into BigQuery.


1. Batch Ingestion Methods & Tooling

Google Cloud provides multiple interfaces for initiating batch load operations into BigQuery, each suited to specific operational contexts and levels of automation:

The bq Command-Line Tool

The bq CLI is the primary workhorse for shell scripting, cron jobs, and CI/CD pipelines. A batch load is executed using the bq load command, which submits an asynchronous load job to the BigQuery control plane.

# Basic bq load command loading Parquet data from Cloud Storage
bq load \
  --source_format=PARQUET \
  --write_disposition=WRITE_APPEND \
  retail_dw.orders \
  gs://company-data-lake-prod/retail/orders/2026/10/*.parquet

The command submits a job configuration to BigQuery, which allocates shared background compute slots to process and convert the source data into BigQuery's proprietary columnar storage format, Capacitor.

BigQuery Web Console

The Google Cloud Console provides an intuitive graphical interface for ad-hoc, exploratory, or one-off data loads. Within the BigQuery studio workspace, users can select a destination dataset, click Create Table, and specify the source data origin (Google Cloud Storage, local file upload, Google Drive, or Google Cloud Bigtable). While convenient for quick analysis, local uploads via the console are subject to browser session timeouts and strict file size constraints (files uploaded from a local source in the console are limited to 100 MB each, and wildcards are not supported).

Cloud Client Libraries

For custom data applications and programmatic pipelines, Google Cloud offers idiomatic client libraries in Python, Java, Go, Node.js, and C#. Programmatic loading allows fine-grained job monitoring, metadata tagging, and programmatic error handling.

from google.cloud import bigquery

client = bigquery.Client()

table_id = "my-project.retail_dw.customer_profiles"
uri = "gs://company-data-lake-prod/profiles/*.avro"

job_config = bigquery.LoadJobConfig(
    source_format=bigquery.SourceFormat.AVRO,
    write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE,
    use_avro_logical_types=True,
)

load_job = client.load_table_from_uri(uri, table_id, job_config=job_config)
load_job.result()  # Blocks until the load job completes

print(f"Successfully loaded {load_job.output_rows} rows into {table_id}.")

BigQuery Data Transfer Service (DTS)

The BigQuery Data Transfer Service is a fully managed, scheduled automation service designed to migrate data into BigQuery on a recurring basis without writing boilerplate pipeline code. DTS provides native connectors for:

  • Google Application & SaaS Sources: Google Ads, Campaign Manager 360, Google Analytics 4, YouTube Channel and Content Owner reports, Google Merchant Center.
  • External Cloud Storage: Amazon S3 (authenticated with AWS access credentials) and Azure Blob Storage (authenticated via Shared Access Signatures).
  • Cloud Storage: Scheduled recurring batch ingestion of files landing in GCS buckets.
  • Data Warehouse Migrations: Managed schema and data offloading from legacy warehouses like Teradata and Amazon Redshift.

2. Staging Architecture: Cloud Storage vs. Local Uploads

One of the most critical architectural principles tested on Google Cloud data certifications is the distinction between local uploads and Cloud Storage staging.

ParameterLocal File UploadCloud Storage Staging (gs://)
Maximum File Size100 MB per file in the console; one file at a time15 TB per load job; 5 TB per uncompressed CSV or JSON file; 4 GB per gzip-compressed CSV or JSON file
Job ResilienceVulnerable to network drops & browser timeoutsHighly resilient; managed retry on server side
ParallelismSingle stream from client machineMulti-slot distributed parallel read
Wildcard SupportNo (single file selection)Yes (gs://bucket/folder/*.parquet)
Production SuitabilityAd-hoc testing onlyMandatory enterprise production pattern

In enterprise architectures, source systems never upload directly from on-premises drives into BigQuery tables. Instead, pipelines implement a two-step pattern:

  1. Upload or stream source files into a dedicated Google Cloud Storage landing or raw bucket (e.g., gs://landing-bucket-region/domain/year/month/day/).
  2. Trigger a BigQuery load job pointing to the Cloud Storage URI. BigQuery's distributed workers read directly from Cloud Storage across Google's high-speed internal Jupiter network fabric, completing the load in a fraction of the time.

3. The Economics of Batch Loading: Zero Ingestion Cost

A fundamental financial and architectural concept in Google Cloud is that batch loading data into BigQuery is free of charge.

Important

BigQuery does not charge for the compute slots, CPU cycles, or memory utilized to execute batch load jobs. Whether you load 1 gigabyte or 50 terabytes of data via a bq load job or Data Transfer Service, the load job compute cost is exactly $0.00.

What You Are Billed For:

  • Cloud Storage Storage Costs: While the data resides in Cloud Storage prior to ingestion, you pay standard GCS storage pricing (for example, about $0.020 per GB-month for Standard storage in us-central1).
  • Network Transfer / Egress: If your Cloud Storage bucket is located in a different region or multi-region than your BigQuery destination dataset (e.g., GCS bucket in us-central1 and BigQuery dataset in europe-west1), cross-region network egress charges apply. Loading data within the same region or multi-region incurs zero egress fees.
  • BigQuery At-Rest Storage: Once data is successfully loaded into the BigQuery table, it is billed according to BigQuery storage rates: Active Storage ($0.020 per GB/month for tables modified within 90 days) or Long-Term Storage ($0.010 per GB/month for tables or partitions untouched for 90 consecutive days).

4. Supported Source Formats & Splittability Mechanics

BigQuery supports six primary batch ingestion formats. Understanding their internal structures and compression behaviors directly impacts pipeline performance:

+-----------------------------------------------------------------------------------+
|                             SUPPORTED SOURCE FORMATS                              |
+-----------------------------------------------------------------------------------+
|  CSV              | Plain text, tabular, requires delimiter parsing, unindexed   |
|  JSON (NDJSON)    | Newline-delimited JSON only; 1 JSON object per line          |
|  Apache Avro      | Row-oriented binary, self-describing, fast parallel split    |
|  Apache Parquet   | Columnar binary, highly compressed, embedded statistics      |
|  Apache ORC       | Optimized Row Columnar, high compression, Hive ecosystem     |
|  Datastore/Firestore| Specialized multi-file entity exports from Google NoSQL   |
+-----------------------------------------------------------------------------------+

The Splittability Problem and the Compression Trap

When BigQuery loads a dataset, it assigns multiple background worker slots to process chunks of the file in parallel. However, a file format's physical layout determines whether it can be split across multiple workers:

  • Apache Avro & Parquet: These formats contain internal block sync markers and metadata footers. BigQuery can split a single 50 GB Parquet or Avro file into hundreds of distinct byte-range offsets, assigning each byte range to a separate slot for concurrent processing.
  • Uncompressed CSV and JSON: BigQuery can scan for newline characters and split uncompressed text files across parallel workers.
  • GZIP-Compressed CSV and JSON: GZIP compression uses a continuous sliding dictionary. A worker cannot decompress byte offset 1,000,000 without having decompressed all preceding bytes from byte 0. Consequently, GZIP-compressed CSV and JSON files cannot be split. BigQuery is forced to allocate a single slot to sequentially decompress and parse the entire file. BigQuery also caps each gzip-compressed CSV or JSON file at 4 GB, so large exports must be split anyway. Google's guidance is that uncompressed files load significantly faster because they can be read in parallel, and Avro is preferred because its compressed blocks can still be read in parallel.

Tip

If you must ingest text files, prefer uncompressed files split into multiple 100 MB to 1 GB chunks, or compress them using splittable compression codecs like Snappy within container formats like Avro or Parquet.


5. Load Job Configurations & Operational Flags

When executing bq load or configuring a client library load job, specific flags govern schema resolution, data validation, and write behaviors:

Schema Handling: --autodetect vs. Explicit Schemas

  • --autodetect: When enabled, BigQuery samples up to the first 500 rows of the source file to infer field names and data types. While convenient for exploratory analysis, autodetection is an anti-pattern in production pipelines. If early rows contain null values or integer-like strings (e.g., zip codes like "02138"), BigQuery may incorrectly infer INT64 instead of STRING, stripping leading zeros or crashing when subsequent alphanumeric strings appear.
  • Explicit Schemas: Production pipelines should supply an explicit JSON schema file or inline schema definition to guarantee strict type enforcement.
# Loading with an explicit JSON schema definition
bq load \
  --source_format=CSV \
  --skip_leading_rows=1 \
  --schema=/deploy/schemas/orders_schema.json \
  retail_dw.orders \
  gs://company-data-lake-prod/retail/orders/*.csv

Data Cleaning & Error Tolerance Flags

  • --skip_leading_rows=N: Instructs BigQuery to skip header rows at the beginning of CSV files (typically --skip_leading_rows=1).
  • --max_bad_records=N: Sets the maximum number of malformed or invalid rows BigQuery will silently skip before failing the entire load job. The default is 0 (any invalid row immediately aborts the transaction). Setting a non-zero threshold allows resilient ingestion of noisy external logs.
  • --ignore_unknown_values: By default, if a row contains more columns than defined in the target table schema, the load job fails. When --ignore_unknown_values is set to true, BigQuery ignores extra columns and loads the recognized fields.

Write Disposition Modes

The --write_disposition flag specifies what happens when the destination table already contains data:

  • WRITE_APPEND (Default): Appends newly loaded rows to the existing table. If the table does not exist, it is created.
  • WRITE_TRUNCATE (or --replace in bq CLI): Completely drops existing data in the target table or targeted partition and replaces it with the newly loaded rows. If loading into a partitioned table partition (e.g., orders$20261008), only that specific partition is truncated and replaced, leaving other partitions untouched.
  • WRITE_EMPTY: Guarantees that data is only loaded if the destination table is completely empty. If any rows exist, the job fails immediately, preventing accidental duplicate loads.

6. BigQuery Load Quotas and Operational Limits

To ensure multi-tenant stability and fair resource allocation, Google Cloud enforces specific quotas on batch load jobs:

  • Load jobs per table per day: 1,500 load jobs (including failed jobs). Exceeding this quota results in quotaExceeded errors. For high-frequency ingestion exceeding 1,500 batches daily, pipelines must aggregate data into fewer, larger files, or migrate to the BigQuery Storage Write API.
  • Load jobs per project per day: 100,000 load jobs.
  • Maximum row size: 100 MB per individual row for CSV and JSON source files.
  • Maximum load job size: 15 TB across all input files, for CSV, JSON, Avro, Parquet, and ORC alike (the limit does not apply to jobs running on a reservation).
  • Per-file limits: 4 GB for a gzip-compressed CSV or JSON file; 5 TB for an uncompressed CSV or JSON file.
  • Source URIs and files: One load job can list up to 10,000 source URIs and read up to 10 million files.
Test Your Knowledge

An analytics team needs to load 500 GB of historical log files stored in Apache Parquet format in a Cloud Storage bucket into a new BigQuery table. What is the compute cost charged by Google Cloud for running this batch load job?

A

$0.00, because batch load jobs are free

B

$0.025 per GB loaded into the table

C

$0.05 per 1,000 files processed by the load job

D

$5.00 per TB of data scanned during ingestion

Test Your Knowledge

A data engineer is writing an automated nightly ingestion script using the bq command-line tool to load daily CSV files into an existing partitioned table. The script must overwrite the destination partition if it already exists, skip the first row containing column headers, and allow up to 25 malformed rows before failing. Which combination of flags meets these requirements?

A

--write_disposition=WRITE_EMPTY --skip_leading_rows=1 --max_bad_records=25

B

--append=false --ignore_headers --allow_errors=25

C

--replace --skip_leading_rows=1 --max_bad_records=25

D

--replace=true --header=true --bad_records=25

Test Your Knowledge

An organization receives a nightly batch of large gzip-compressed CSV files (each close to BigQuery's 4 GB limit for compressed CSV files) that take an unexpectedly long time to load into BigQuery. Which architectural modification would maximize parallel load throughput and minimize ingestion duration?

A

Recompress the files with BZIP2 instead of GZIP before uploading them to Cloud Storage

B

Load uncompressed CSV, or convert to Avro or Parquet, so BigQuery can read in parallel

C

Switch to the legacy tabledata.insertAll streaming API to push the records concurrently

D

Increase the BigQuery maximum slot reservation assigned to the batch loading pool

Sections you finish are checked off in the contents.